Hi all! I am beginning to learn this wonderful language and currently trying to understand some of the finer points. This snippet is intended to be a simple state machine that transitions from state to state by calling state functions that return next state etc, until the nil state is reached.
type
stateFunc = ref proc (): stateFunc
var
state: stateFunc
proc running(): stateFunc =
echo("running")
return nil
proc starting(): stateFunc =
echo("running")
return running
state = starting
while state != nil:
state = state()
What I get is: Error: internal error: cannot generate C type for: stateFunc Can anybody tell me what am I doing wrong?
You are defining a cyclic type that the compiler cannot instantiate. The solution is to introduce an intermediary object type.
type
stateFunc = object
next: proc(): stateFunc {.nimcall.}
var
state: stateFunc
proc running(): stateFunc =
echo("running")
return stateFunc(next: nil)
proc starting(): stateFunc =
echo("running")
return stateFunc(next: running)
state = stateFunc(next: starting)
while state.next != nil:
state = state.next()
Note also the use of {.nimcall.}. Otherwise, the type of next would be a {.closure.}.
The error message should be generated during type checking rather than when code is being emitted, though. The underlying problem is a bit more complex; see this link if you don't mind a bit of type theory.
Excellent explanation and link, thanks! But now another problem. I begin to refactor my code, because it will be nicer to have all states in separate module:
# types.nim
type
stateFunc* = object
next*: proc(): stateFunc {.nimcall.}
# states.nim
import types
proc running*(): stateFunc =
echo("running")
return stateFunc(next: nil)
proc starting*(): stateFunc =
echo("running")
return stateFunc(next: running)
# machine.nim
import types, states
var
state: stateFunc
state = stateFunc(next: starting)
while state.next != nil:
state = state.next()
Now I have: machine.nim|6 col 25 error| 'starting' cannot be passed to a procvar What makes this code different from the previous version? I am confused!
You need to declare the state transition functions as procvars, e.g.:
# states.nim
import types
proc running*(): stateFunc {.procvar.} =
echo("running")
return stateFunc(next: nil)
proc starting*(): stateFunc {.procvar.} =
echo("running")
return stateFunc(next: running)
An alternative is to use include instead of import, as procedures local to the module get special treatment. Note that include is basically textual inclusion and should only be used if you (1) use the module in just this one place or (2) don't mind the replication of code.
The rationale is a bit complicated. The purpose of this requirement (described in detail in this section of the manual) is that extending procedures with optional parameters with default arguments doesn't break the API.
For example, let's say you have a module with the following contents:
proc foo() = echo 0
You can now change foo to have additional optional arguments:
proc foo(x: int = 0) = echo x
This does not change the implementation in a backwards-incompatible way as long as foo() is only called normally, but foo now describes a procedure with a different type signature. To acknowledge that you will not do this, you add the {.procvar.} pragma to the procedure declaration.
There is an ongoing debate as to whether this should be changed, if the compiler should automatically generate wrapper code, or something else should be done to avoid this issue.
Oh. Please bear with me, but I am running into trouble again. Consider making this state thingy into field of some object:
# types.nim
type
stateFunc* = object
next*: proc(self: statefulObj): stateFunc {.nimcall.}
statefulObj* = object
state*: stateFunc
template state*(x: expr, b: stmt) {.immediate.} =
proc x*(self: statefulObj): stateFunc {.procvar.}=
b
template transition*(x: expr) {.immediate.} =
return stateFunc(next: x)
# states.nim
import types
state running:
echo("running")
transition nil
state starting:
echo("running")
transition running
# machine.nim
import types, states
var
machine: statefulObj
machine.state = stateFunc(next: running)
while machine.state.next != nil:
machine.state = machine.state.next(machine)
This code checks ok, but when compiling with
nim --parallelBuild:1 c machine
now I get:
gcc -c -w -I/opt/nim/lib -o nimcache/states.o nimcache/states.c
Whyyyyyy!!!!
It's a fairly subtle bug in the code generator because you have two mutually recursive value types without a reference in the recursion. This messes up the order in which they are being output. There are a number of workarounds:
First, you can use the {.byref.} pragma to inform the compiler that the statefulObj is to be passed by reference:
type
stateFunc* = object
next*: proc(self: statefulObj): stateFunc {.nimcall.}
statefulObj* = object {.byref.}
state*: stateFunc
Second, you can use var to annotate the statefulObj argument (which will also make it mutable):
type
stateFunc* = object
next*: proc(self: var statefulObj): stateFunc {.nimcall.}
statefulObj* = object
state*: stateFunc
template state*(x: expr, b: stmt) {.immediate.} =
proc x*(self: var statefulObj): stateFunc {.procvar.}=
b
template transition*(x: expr) {.immediate.} =
return stateFunc(next: x)
Or you can replace one or the other of the object types with a ref object type (though that may introduce heap allocations that you do not want).
Note that the bug also disappears if you introduce, e.g.:
var unused: statefulObj
in states.nim. But that's an extremely fragile workaround, because it depends on code generator internals and may fail if there are changes to the code.
Brilliant, thanks! Thins bug is fairly tolerable, because I was going to make statefulObj a ref object anyway. It's long-lived, while states are going to be changed very frequently, hence value object states. Now I've made whole thing a bit more abstract, to avoid code repetition with different stateful objects:
# types.nim
type
stateFunc*[T] = object
next*: proc(self: T): stateFunc[T] {.nimcall.}
statefulObj* = ref object
state*: stateFunc[statefulObj]
template state*(T: typedesc, x: expr, b: stmt) {.immediate.} =
proc x*(self: T): stateFunc[T] {.procvar.} =
b
template transition*(T: typedesc, x: expr) {.immediate.} =
return stateFunc[T](next: x)
# states.nim
import types
state statefulObj, running:
echo("running")
transition statefulObj, nil
state statefulObj, starting:
echo("starting")
transition statefulObj, running
# machine.nim
import types, states
var
machine: statefulObj
new machine
machine.state = stateFunc[statefulObj](next: starting)
while machine.state.next != nil:
machine.state = machine.state.next(machine)
While this is already looks ok (and works ok too!) I can't help but wonder if there is some way to inject self object into transition macro, so it will look more natural in state context: something like
state statefulObj, someState:
transition someOtherState
Try this:
# types.nim
type
stateFunc*[T] = object
next*: proc(self: T): stateFunc[T] {.nimcall.}
statefulObj* = ref object
state*: stateFunc[statefulObj]
template state*(T: typedesc, x: expr, b: stmt) {.immediate.} =
proc x*(self: T): stateFunc[T] {.procvar.} =
template ST: typedesc = T
b
template transition*(x: expr) {.immediate.} =
return stateFunc[ST](next: x)
# states.nim
import types
state statefulObj, running:
echo("running")
transition nil
state statefulObj, starting:
echo("starting")
transition running
goto isn't necessary for building efficient state machines. Just put your case statement in a while true loop ... most compilers are smart enough to turn state = n; continue; into a direct branch to case n (or even fallthrough if the cases are so organized), rather than branching back to the switch. Take a look at the assembler generated for
proc foo(): int =
var state = 0
while true:
case state
of 0:
state = 1
of 1:
state = 2
of 2:
state = 3
of 3:
state = 4
else:
return state
gcc -O3 boils it down to return 4.
Well there's no end to the cunning stunts the ingenious programmer can come up with in order to fool the compiler into creating a goto. All because some pedagogue said a while back that goto's were the root of all programming evil, and the politically correct meekly followed suit. Coming up with these cunning stunts can be fun and educational, but some of us just want to have a tool suited to the job at hand; and if that tool is sharp and dangerous - well we are grown-ups, are we not?
Araq, why hide it away in an undocumented pragma? Are you also being politically correct? Go on, get your goto out in the open.
<rant over>
Duncan: Araq, why hide it away in an undocumented pragma?
It's not a general-purpose goto; it's limited to a case-like structure. Example:
type T = enum A, B, C
proc main =
var p {.goto.} = A
case p
of A:
echo "0"
p = B
of B:
echo "1"
p = C
of C:
echo "2"
main()
When you declare a variable as goto, a case statement that dispatches on that variable annotates its case branches with jump labels. Assigning a value to a goto variable will then perform a jump to the corresponding case label. You can only assign constant values to such a variable.
Note that if you make a mistake, broken C code may be emitted; the feature is still unstable.