5

I'm trying to create a simple state machine in F# but having trouble getting two states with circular dependencies to work.
I have this state factory:

open System
let createState produceInput stateSwitchRule nextState = 
    let rec stateFunc() = 
        match produceInput() with
        | x when x = stateSwitchRule -> printfn "%s" "Switching state"; nextState()
        | _  -> printfn "%s" "Bad input. Try again"; stateFunc()
    stateFunc

which I use to create two mutually recursive states:

let rec pongState() = createState Console.ReadLine "go to ping" pingState
      and pingState = createState Console.ReadLine "go to pong" (pongState())

[<EntryPoint>]
let main argv = 
    pingState()
    0

When invoking pingState()and inputting "go to pong" the state is switched to pong. But when invoking inputting "go to ping" a null reference exception is thrown.
Is there anyway around this with the chosen approach or should I model it differently?

sgtz
  • 8,849
  • 9
  • 51
  • 91
Christian
  • 7,433
  • 4
  • 36
  • 61
  • Your sample is not compiling - it gives me Value restriction error on 'and pingState' – Petr Oct 25 '14 at 13:07
  • That's odd; I get the same compile error in an fsx file but not in an fs file (added program code to the example above) – Christian Oct 25 '14 at 13:34

1 Answers1

3

This is what I did:

#nowarn "40"

open System

let createState produceInput stateSwitchRule nextState = 
    let rec stateFunc () = 
        match produceInput() with
        | x when x = stateSwitchRule -> printfn "%s" "Switching state"; (nextState()) ()
        | _  -> printfn "%s" "Bad input. Try again"; stateFunc()
    stateFunc

let rec pongState : unit -> (unit -> string) = createState Console.ReadLine "go to ping" (fun () -> pingState)
    and pingState : unit -> (unit -> string) = createState Console.ReadLine "go to pong" (fun () -> pongState)

#nowarn "40" to suppress the warning about checking recursively defined objects for initialization soundness, different type for the nextState function, otherwise the compiler complained about a value being evaluated as a part of its definition, and superfluous type annotations on states because FSI complained about them being inferred to be generic. Lots of complaints ;)

As for modelling it differently - I think I would wrap it in a type instead of using solely functions, it seems more natural. I guess using functions was the whole point here though.

scrwtp
  • 13,437
  • 2
  • 26
  • 30
  • Thanks! In an fs file the above also works without explicitly specifying the return types of pongState and pingState, no idea why it differs between fs and fsx, – Christian Oct 26 '14 at 08:03
  • In an fs file there's enough of a context to infer the concrete types of those states. That's often not the case when you try out stuff in fsi. If you had a function defined later on in fsx that uses those states, so that the types could be inferred from it, you could drop the annotations in fsx as well. – scrwtp Oct 26 '14 at 08:48
  • I see, but what's in the fs-context with respect to type inference that's missing from the fsi-case? – Christian Oct 26 '14 at 18:57