Хотя верно, что конечный автомат может моделироваться состоянием императивной функции, в результате получается код, который больно читать, и который можно обобщить с помощью шаблона switch( state )
, приведенного в качестве примера вФинальный пример кода моего исходного сообщения.
Я понял, что решение состоит в том, чтобы использовать AnyOf
для представления текущего состояния, используя его метод Match
для обработки входа в определенное состояние независимо от предыдущего состояния - илюбые конкретные переходы состояний могут быть обработаны, когда они происходят безопасным для типов образом.
Таким образом, используя тот же самый пример циклического конечного автомата сверху:
График:
A -> B -> C1 -> D -> E
-> A
-> C2 -> B
Типы:
class A {
public B Next();
}
class B {
public OneOf<C1,C2> Next();
}
class C1 {
public OneOf<D,A> Next();
}
class C2 {
public B Next();
}
class D {
public E Next();
}
class E {
// Terminal state
}
Может быть безопасно реализовано как:
using AnyState = OneOf<A,B,C1,C2,D,E>; // for brevity
public E Run( A initialState )
{
AnyState state = initialState;
E terminal = null;
while( terminal == null ) )
{
state = state.Match(
a => AnyState.FromT0( a .Next() ), // B
b => b.Next().Match(
c1 => AnyState.FromT2( c1 ),
c2 => AnyState.FromT3( c2 )
)
}
c1 => c1.Next().Match(
d => AnyState.FromT4( d ),
a => AnyState.FromT1( a )
)
}
c2 => AnyState.FromT2( c2.Next() ), // B
d => AnyState.FromT4( d .Next() ), // E
e => AnyState.FromT5( terminal = e )
);
}
}
Используя дополнительные преимущества оператора OneOf
implicit
, это можно упростить до:
using AnyState = OneOf<A,B,C1,C2,D,E>; // for brevity
public E Run( A initialState )
{
AnyState state = initialState;
while( !( state.IsT5 ) ) )
{
state = state.Match<AnyState>(
a => a .Next(), // B
b => b .Next() // C1 | C2
.Match<AnyState>(
c1 => c1,
c2 => c2
),
c1 => c1.Next() // D | A
.Match<AnyState>(
d => d,
a => a
)
c2 => c2.Next(), // B
d => d .Next(), // E
e => e
);
}
}
И мы можем заменить magic IsT5
методом расширения, чтобы указать состояние терминала, при условии, что последний элемент OneOf
используется для состояний терминала:
static Boolean IsTerminal<T0,T1,T2,T3,T4,T5>( this OneOf<T0,T1,T2,T3,T4,T5> state )
{
return state.IsT5;
}
Предоставление:
using AnyState = OneOf<A,B,C1,C2,D,E>; // for brevity
public E Run( A initialState )
{
AnyState state = initialState;
while( !state.IsTerminal() ) ) )
{
state = state.Match<AnyState>(
a => a .Next(), // B
b => b .Next() // C1 | C2
.Match<AnyState>(
c1 => c1,
c2 => c2
),
c1 => c1.Next() // D | A
.Match<AnyState>(
d => d,
a => e
)
c2 => c2.Next(), // B
d => d .Next(), // E
e => e
);
}
}
И это, вероятно, можно упаковать как универсальное расширение конечного автомата поверх OneOf
.