M
Modularโ€ข4mo ago
bunny

Type State Pattern

Does anybody have a code-snippet that demonstrates a standard Type State Pattern in Mojo? For example, a somewhat textbook-example of a traffic light -- please excuse any typos I make in my sloppy hand-jam example.
enum TrafficLight {
Red,
Green,
Yellow,
}

impl TrafficLight {
pub fn new() -> Self::Green { Self::Red }

pub fn next(self: Self::Red) -> Self::Green { Self::Green }
pub fn next(self: Self::Green) -> Self::Yellow { Self::Yellow }
pub fn next(self: Self::Yellow) -> Self::Red { Self::Red }
}

fn main() {
let mut light = TrafficLight::new(); // light is Red
light = light.next(); // light is Green
light = light.next(); // light is Yellow
light = light.next(); // light is back to Red
}
enum TrafficLight {
Red,
Green,
Yellow,
}

impl TrafficLight {
pub fn new() -> Self::Green { Self::Red }

pub fn next(self: Self::Red) -> Self::Green { Self::Green }
pub fn next(self: Self::Green) -> Self::Yellow { Self::Yellow }
pub fn next(self: Self::Yellow) -> Self::Red { Self::Red }
}

fn main() {
let mut light = TrafficLight::new(); // light is Red
light = light.next(); // light is Green
light = light.next(); // light is Yellow
light = light.next(); // light is back to Red
}
Anyway, does anybody have a snippet of Mojo-style Type State Pattern? Google says "no," but I imagine somebody has a personal snippet they might share.
31 Replies
bunny
bunnyโ€ข4mo ago
Here's what I was noodling around with ... granted, I'm wrapping the concept in a container-struct:
from utils.variant import Variant

struct Red: pass
struct Green: pass
struct Yellow: pass

alias TrafficLightState = Variant[Red, Green, Yellow] # Error #1 here

@value
struct TrafficLight:
var state: TrafficLightState

fn __init__(inout self):
self.state = Red # Error #2 here

fn next(self):
if self.state.isa[Red]():
self.state = Green
elif self.state.isa[Green]():
self.state = Yellow
elif self.state.isa[Yellow]():
self.state = Red

fn main() raises:
var light = TrafficLight() # light is Red
light.next() # light is Green
light.next() # light is Yellow
light.next() # light is back to Red
from utils.variant import Variant

struct Red: pass
struct Green: pass
struct Yellow: pass

alias TrafficLightState = Variant[Red, Green, Yellow] # Error #1 here

@value
struct TrafficLight:
var state: TrafficLightState

fn __init__(inout self):
self.state = Red # Error #2 here

fn next(self):
if self.state.isa[Red]():
self.state = Green
elif self.state.isa[Green]():
self.state = Yellow
elif self.state.isa[Yellow]():
self.state = Red

fn main() raises:
var light = TrafficLight() # light is Red
light.next() # light is Green
light.next() # light is Yellow
light.next() # light is back to Red
However, there are two errors: 1. 'Variant' parameter #0 has 'CollectionElement' type, but value has type 'AnyStruct[Red]' (Mojo LSP marks term Red in the line alias TrafficLightState...) 2. dynamic type values not permitted yet (Mojo LSP marks term Red in the line self.state = Red in the init function) And I know that an easy solution is using placeholder values to represent the state, like ints or strings:
alias RED = "red"
alias GREEN = "green"
alias YELLOW = "yellow"
alias RED = "red"
alias GREEN = "green"
alias YELLOW = "yellow"
But this pattern does not prevent the mistake of doing something in the code like:
var state = StringLiteral
...
self.state = "RED" # <- error; should be RED (no quotes) ... and no compile-time error :(
var state = StringLiteral
...
self.state = "RED" # <- error; should be RED (no quotes) ... and no compile-time error :(
While that error can be prevented by having rigid adherence to defined values, such as Rust Enum values or a Mojo Variant of acceptable types, or something. Just not sure what my "something" is going to be, so I'm looking for some hand-holding. ๐Ÿ˜Š I should also add that I'm really not doing the Type State Pattern in the Mojo example, but rather a modified "use a placeholder of state" shortcut. And not thrilled about that. next flailing attempt is this, which still has the above "error #1"
from utils.variant import Variant

struct Red:
fn __init__(inout self): pass
fn next(self) -> Green: return Green()

struct Green:
fn __init__(inout self): pass
fn next(self) -> Yellow: return Yellow()

struct Yellow:
fn __init__(inout self): pass
fn next(self) -> Red: return Red()

alias ValidTrafficLightColor = Variant[Red, Green, Yellow]

struct TrafficLight:
var state: ValidTrafficLightColor
fn __init__(inout self):
self.state = Red()
fn next(inout self):
self.state = self.state.next()

fn main() raises:
var light = TrafficLight() # light is Red
light.next() # light is Green
light.next() # light is Yellow
light.next() # light is back to Red
from utils.variant import Variant

struct Red:
fn __init__(inout self): pass
fn next(self) -> Green: return Green()

struct Green:
fn __init__(inout self): pass
fn next(self) -> Yellow: return Yellow()

struct Yellow:
fn __init__(inout self): pass
fn next(self) -> Red: return Red()

alias ValidTrafficLightColor = Variant[Red, Green, Yellow]

struct TrafficLight:
var state: ValidTrafficLightColor
fn __init__(inout self):
self.state = Red()
fn next(inout self):
self.state = self.state.next()

fn main() raises:
var light = TrafficLight() # light is Red
light.next() # light is Green
light.next() # light is Yellow
light.next() # light is back to Red
sora
soraโ€ข4mo ago
This is incorrect rust. Your TL should have been a parametric structure with phantom
Ryulord
Ryulordโ€ข4mo ago
just add @value to Red, Green, and Yellow
bunny
bunnyโ€ข4mo ago
I'm not a heavy-duty Rust dev, but I've seen both structs with phantom and enums for typestate pattern. So, might be a holy war. Dunno. But beyond any Rust holy wars, what is pattern in Mojo? will try.. gimme sec
Ryulord
Ryulordโ€ข4mo ago
can also drop the empty __init__s if you do that
ModularBot
ModularBotโ€ข4mo ago
Congrats @Ryulord, you just advanced to level 5!
sora
soraโ€ข4mo ago
What do you mean by holy war? I was merely pointing out that your rust code is invalid, not the approach you chose is wrong. They both could work, which one you choose depends on what you want to achieve. If you want to see your state in the type, you will use phantom type. Otherwise, itโ€™s just good old adt.
bunny
bunnyโ€ข4mo ago
ok, so then there are a bunch of Rust tutorials, repos, etc that are using Algebraic Data Types as pseudo-typestate. I was misled in terminology by the resources I encountered in my limited Rust exposure. Moving forward, what would you say is an ideal Type State Pattern in Mojo? How would you achieve something that uses parametric structure with phantom data that allows you to see your state? I feel like I'm fumbling and just adding random []s and what not to see what new errors I can stumble into. ๐Ÿ˜‚
sora
soraโ€ข4mo ago
Proper type state not achievable yet I think, because we donโ€™t have conditional conformance. Wait, actually, there is a horrible hack you can use in todayโ€™s Mojo. Will get back to you when Iโ€™m on my laptop.
bunny
bunnyโ€ข4mo ago
โค๏ธ Horrible hacks are my jam! ๐Ÿ˜‚ And I'm grateful for your willingness to do some hand-holding here.
sora
soraโ€ข4mo ago
Since Mojo supports overload (unlike rust), you could write the transition function as a free function. I think you can encode any finite automata this way
fn f(s: State[A], arg: ...) -> State[B]:
...
fn f(s: State[B], arg: ...) -> State[C]:
...
# etc.
fn f(s: State[A], arg: ...) -> State[B]:
...
fn f(s: State[B], arg: ...) -> State[C]:
...
# etc.
bunny
bunnyโ€ข4mo ago
I am really thankful for your thoughts. I appreciate all the coaching and guidance. .....I'm pretty sure I'll find a way to mess-it-up when I try to implement, but I'm ok with "learning opportunities" ๐Ÿ˜‚ Jests aside: sincere gratitude. Thank you.
sora
soraโ€ข4mo ago
@bunny Now, the horrible hack:
from sys.intrinsics import _type_is_eq

fn main():
var s = S[A]()
var t = s.next[B]()

trait State: ...

@value
struct A(State): ...

@value
struct B(State): ...

@value
@register_passable
struct S[T: State]:
fn next[return_type: State](self) -> S[return_type]:
@parameter
if _type_is_eq[Self.T, A](): # A -> B
return rebind[S[return_type]](S[B]())
elif _type_is_eq[Self.T, B](): # B -> A
return rebind[S[return_type]](S[A]())
else:
constrained[False, "unreachable"]()
while True: pass # we don't have a proper unreachable in Mojo yet
from sys.intrinsics import _type_is_eq

fn main():
var s = S[A]()
var t = s.next[B]()

trait State: ...

@value
struct A(State): ...

@value
struct B(State): ...

@value
@register_passable
struct S[T: State]:
fn next[return_type: State](self) -> S[return_type]:
@parameter
if _type_is_eq[Self.T, A](): # A -> B
return rebind[S[return_type]](S[B]())
elif _type_is_eq[Self.T, B](): # B -> A
return rebind[S[return_type]](S[A]())
else:
constrained[False, "unreachable"]()
while True: pass # we don't have a proper unreachable in Mojo yet
This is different form using enum in that the state info is encoded in the type. It's less convenient than using a free function, since the return type is not inferred. But is does provide the compile time guarantee (with @parameter if).
bunny
bunnyโ€ข4mo ago
@sora , it looks like the result of next is wholly controlled by the type passed to it. I.e., the B in s.next[B](). What is to prevent somebody from typo'ing as s.next[A]()? I was messing around with what you provided, and realized I could just pass any state change desired. In this example, the traffic light goes from Red to Yellow.
from sys.intrinsics import _type_is_eq

trait State: ...
@value
struct Red(State): ... #, Stringable):
# fn __str__(self) -> String: return "Red"
@value
struct Green(State): ... #, Stringable):
# fn __str__(self) -> String: return "Green"
@value
struct Yellow(State): ... #, Stringable):
# fn __str__(self) -> String: return "Yellow"

@value
@register_passable
struct S[T: State]:
fn next[return_type: State](self) -> S[return_type]:
@parameter
if _type_is_eq[Self.T, Red](): # Red -> Green
return rebind[S[return_type]](S[Green]())
elif _type_is_eq[Self.T, Green](): # Green -> Yellow
return rebind[S[return_type]](S[Yellow]())
elif _type_is_eq[Self.T, Yellow](): # Yellow -> Red
return rebind[S[return_type]](S[Red]())
else:
# we don't have a proper unreachable in Mojo yet
constrained[False, "unreachable"]()
while True: pass

fn main():
var s = S[Red]()
var t = s.next[Yellow]()
from sys.intrinsics import _type_is_eq

trait State: ...
@value
struct Red(State): ... #, Stringable):
# fn __str__(self) -> String: return "Red"
@value
struct Green(State): ... #, Stringable):
# fn __str__(self) -> String: return "Green"
@value
struct Yellow(State): ... #, Stringable):
# fn __str__(self) -> String: return "Yellow"

@value
@register_passable
struct S[T: State]:
fn next[return_type: State](self) -> S[return_type]:
@parameter
if _type_is_eq[Self.T, Red](): # Red -> Green
return rebind[S[return_type]](S[Green]())
elif _type_is_eq[Self.T, Green](): # Green -> Yellow
return rebind[S[return_type]](S[Yellow]())
elif _type_is_eq[Self.T, Yellow](): # Yellow -> Red
return rebind[S[return_type]](S[Red]())
else:
# we don't have a proper unreachable in Mojo yet
constrained[False, "unreachable"]()
while True: pass

fn main():
var s = S[Red]()
var t = s.next[Yellow]()
How do I prevent this "dev typo'd and now we have improper proper order of change" error?
sora
soraโ€ข4mo ago
Hmmmm, that's not what i expected I think it's a bug in rebind, or I got its semantics wrong. Two hacks you could use for the moment
@parameter
if _type_is_eq[Self.T, Red]() and _type_is_eq[return_type, Green](): # Red -> Green
return rebind[S[return_type]](S[Green]())
elif _type_is_eq[Self.T, Green](): # Green -> Yellow
constrained[_type_is_eq[return_type, Yellow](), "invalid state transition"]()
return rebind[S[return_type]](S[Yellow]())
@parameter
if _type_is_eq[Self.T, Red]() and _type_is_eq[return_type, Green](): # Red -> Green
return rebind[S[return_type]](S[Green]())
elif _type_is_eq[Self.T, Green](): # Green -> Yellow
constrained[_type_is_eq[return_type, Yellow](), "invalid state transition"]()
return rebind[S[return_type]](S[Yellow]())
bunny
bunnyโ€ข4mo ago
So here's where I'm at:
from sys.intrinsics import _type_is_eq

trait State: ...
@value
struct Red(State): ...
@value
struct Green(State): ...
@value
struct Yellow(State): ...

@value
@register_passable
struct S[T: State](Stringable):
fn next[return_type: State](self) -> S[return_type]:
@parameter
if _type_is_eq[Self.T, Red]() and _type_is_eq[return_type, Green](): # Red -> Green
return rebind[S[return_type]](S[Green]())
elif _type_is_eq[Self.T, Green]() and _type_is_eq[return_type, Yellow](): # Green -> Yellow
return rebind[S[return_type]](S[Yellow]())
elif _type_is_eq[Self.T, Yellow]() and _type_is_eq[return_type, Red](): # Yellow -> Red
return rebind[S[return_type]](S[Red]())
else:
# we don't have a proper unreachable in Mojo yet
constrained[False, "unreachable"]()
while True: pass
fn __str__(self) -> String:
if _type_is_eq[Self.T, Red](): return "Red"
elif _type_is_eq[Self.T, Green](): return "Green"
elif _type_is_eq[Self.T, Yellow](): return "Yellow"
else: return "ERROR:Unknown"

fn main():
var a = S[Red]()
print(a)

# LSP is ok with this, but `mojo run` will fail with `unreachable`
# mojo: error: failed to run the pass manager
# var b = a.next[Yellow]()

var b = a.next[Green]()
print(b)

var c = b.next[Yellow]()
print(c)
from sys.intrinsics import _type_is_eq

trait State: ...
@value
struct Red(State): ...
@value
struct Green(State): ...
@value
struct Yellow(State): ...

@value
@register_passable
struct S[T: State](Stringable):
fn next[return_type: State](self) -> S[return_type]:
@parameter
if _type_is_eq[Self.T, Red]() and _type_is_eq[return_type, Green](): # Red -> Green
return rebind[S[return_type]](S[Green]())
elif _type_is_eq[Self.T, Green]() and _type_is_eq[return_type, Yellow](): # Green -> Yellow
return rebind[S[return_type]](S[Yellow]())
elif _type_is_eq[Self.T, Yellow]() and _type_is_eq[return_type, Red](): # Yellow -> Red
return rebind[S[return_type]](S[Red]())
else:
# we don't have a proper unreachable in Mojo yet
constrained[False, "unreachable"]()
while True: pass
fn __str__(self) -> String:
if _type_is_eq[Self.T, Red](): return "Red"
elif _type_is_eq[Self.T, Green](): return "Green"
elif _type_is_eq[Self.T, Yellow](): return "Yellow"
else: return "ERROR:Unknown"

fn main():
var a = S[Red]()
print(a)

# LSP is ok with this, but `mojo run` will fail with `unreachable`
# mojo: error: failed to run the pass manager
# var b = a.next[Yellow]()

var b = a.next[Green]()
print(b)

var c = b.next[Yellow]()
print(c)
Thank you, @sora ! ...I might rethink what I'm making to use a different pattern, because this looks a bit fragile to me. I cannot explain why, just a spidey-sense thing. Perhaps my inner-paranoiac is saying "will this be compatible with Mojo v1.0, or am I setting myself up for a lot of refactoring as features change"..... really don't know. Serious HUGE thank you for helping me navigate through some of Mojo's features.
No description
sora
soraโ€ข4mo ago
@bunny I'd probably stick to this
bunny
bunnyโ€ข4mo ago
so more like?
from sys.intrinsics import _type_is_eq

trait State: ...
@value
struct Red(State): ...
@value
struct Green(State): ...
@value
struct Yellow(State): ...

@value
@register_passable
struct S[T: State](Stringable):
fn __str__(self) -> String:
if _type_is_eq[Self.T, Red](): return "Red"
elif _type_is_eq[Self.T, Green](): return "Green"
elif _type_is_eq[Self.T, Yellow](): return "Yellow"
else: return "ERROR:Unknown"

fn next(s: S[Red]) -> S[Green]: return S[Green]()
fn next(s: S[Green]) -> S[Yellow]: return S[Yellow]()
fn next(s: S[Yellow]) -> S[Red]: return S[Red]()

fn main():
var a = S[Red]()
print(a)
var b = next(a)
print(b)
var c = next(b)
print(c)
from sys.intrinsics import _type_is_eq

trait State: ...
@value
struct Red(State): ...
@value
struct Green(State): ...
@value
struct Yellow(State): ...

@value
@register_passable
struct S[T: State](Stringable):
fn __str__(self) -> String:
if _type_is_eq[Self.T, Red](): return "Red"
elif _type_is_eq[Self.T, Green](): return "Green"
elif _type_is_eq[Self.T, Yellow](): return "Yellow"
else: return "ERROR:Unknown"

fn next(s: S[Red]) -> S[Green]: return S[Green]()
fn next(s: S[Green]) -> S[Yellow]: return S[Yellow]()
fn next(s: S[Yellow]) -> S[Red]: return S[Red]()

fn main():
var a = S[Red]()
print(a)
var b = next(a)
print(b)
var c = next(b)
print(c)
Which results in the pic:
No description
bunny
bunnyโ€ข4mo ago
Pretty sure I owe you a coffee or smth. โ˜• First Mojo-CON, your AM coffee is on me. ๐Ÿ˜„
sora
soraโ€ข4mo ago
trait State:
@staticmethod
fn str() -> String: ...

@value
struct Red(State):
@staticmethod
fn str() -> String: return "Red"

struct S[T: State]:
fn __str__(self) -> String:
return Self.T.str()
trait State:
@staticmethod
fn str() -> String: ...

@value
struct Red(State):
@staticmethod
fn str() -> String: return "Red"

struct S[T: State]:
fn __str__(self) -> String:
return Self.T.str()
bunny
bunnyโ€ข4mo ago
Every bit of guidance just makes me explore new junk. ๐Ÿ˜‚ Ok, so I thought "what if I just make each color Stringable"...? But I'm seriously borking up something very basic here and feeling rather sheepish. Any idea what magical ASCII I need in the line return str(Self.T) to make things work? Code:
from sys.intrinsics import _type_is_eq

trait _TrafficLightState:
@staticmethod # TODO: rm when get str() working in TrafficLight
fn str() -> String: ... # TODO: rm when get str() working in TrafficLight
fn __str__(self) -> String: ...
@value
struct Red(_TrafficLightState, Stringable):
@staticmethod # TODO: rm when get str() working in TrafficLight
fn str() -> String: return "Red" # TODO: rm when get str() working in TrafficLight
fn __str__(self) -> String: return "Red"
@value
struct Green(_TrafficLightState, Stringable):
@staticmethod # TODO: rm when get str() working in TrafficLight
fn str() -> String: return "Green" # TODO: rm when get str() working in TrafficLight
fn __str__(self) -> String: return "Green"
@value
struct Yellow(_TrafficLightState, Stringable):
@staticmethod # TODO: rm when get str() working in TrafficLight
fn str() -> String: return "Yellow" # TODO: rm when get str() working in TrafficLight
fn __str__(self) -> String: return "Yellow"

@value
@register_passable
struct TrafficLight[T: _TrafficLightState](Stringable):
fn __str__(self) -> String:
return Self.T.str() # TODO: rm when get str() working in TrafficLight
# return str(self.T) # how do I get this to work so the R/G/Y are printed?

fn next_color(t: TrafficLight[Red]) -> TrafficLight[Green]: return TrafficLight[Green]()
fn next_color(t: TrafficLight[Green]) -> TrafficLight[Yellow]: return TrafficLight[Yellow]()
fn next_color(t: TrafficLight[Yellow]) -> TrafficLight[Red]: return TrafficLight[Red]()

fn main():
print("test of str(Red()):", str(Red()))
var a = TrafficLight[Red]()
print("a:", a)
var b = next_color(a)
print("b:", b)
var c = next_color(b)
print("c:", c)
from sys.intrinsics import _type_is_eq

trait _TrafficLightState:
@staticmethod # TODO: rm when get str() working in TrafficLight
fn str() -> String: ... # TODO: rm when get str() working in TrafficLight
fn __str__(self) -> String: ...
@value
struct Red(_TrafficLightState, Stringable):
@staticmethod # TODO: rm when get str() working in TrafficLight
fn str() -> String: return "Red" # TODO: rm when get str() working in TrafficLight
fn __str__(self) -> String: return "Red"
@value
struct Green(_TrafficLightState, Stringable):
@staticmethod # TODO: rm when get str() working in TrafficLight
fn str() -> String: return "Green" # TODO: rm when get str() working in TrafficLight
fn __str__(self) -> String: return "Green"
@value
struct Yellow(_TrafficLightState, Stringable):
@staticmethod # TODO: rm when get str() working in TrafficLight
fn str() -> String: return "Yellow" # TODO: rm when get str() working in TrafficLight
fn __str__(self) -> String: return "Yellow"

@value
@register_passable
struct TrafficLight[T: _TrafficLightState](Stringable):
fn __str__(self) -> String:
return Self.T.str() # TODO: rm when get str() working in TrafficLight
# return str(self.T) # how do I get this to work so the R/G/Y are printed?

fn next_color(t: TrafficLight[Red]) -> TrafficLight[Green]: return TrafficLight[Green]()
fn next_color(t: TrafficLight[Green]) -> TrafficLight[Yellow]: return TrafficLight[Yellow]()
fn next_color(t: TrafficLight[Yellow]) -> TrafficLight[Red]: return TrafficLight[Red]()

fn main():
print("test of str(Red()):", str(Red()))
var a = TrafficLight[Red]()
print("a:", a)
var b = next_color(a)
print("b:", b)
var c = next_color(b)
print("c:", c)
Errors:
.../typestate/via-fn-overloading.mojo:28:19: error: no matching function in call to 'str'
return str(self.T)
~~~^~~~~~~~
.../typestate/via-fn-overloading.mojo:1:1: note: candidate not viable: callee expects 1 parameter, but 0 were specified
from sys.intrinsics import _type_is_eq
^
.../typestate/via-fn-overloading.mojo:1:1: note: candidate not viable: argument #0 cannot be converted from '_TrafficLightState' to 'None'
from sys.intrinsics import _type_is_eq
^
.../typestate/via-fn-overloading.mojo:1:1: note: candidate not viable: callee expects 1 parameter, but 0 were specified
from sys.intrinsics import _type_is_eq
^
mojo: error: failed to parse the provided Mojo source module
.../typestate/via-fn-overloading.mojo:28:19: error: no matching function in call to 'str'
return str(self.T)
~~~^~~~~~~~
.../typestate/via-fn-overloading.mojo:1:1: note: candidate not viable: callee expects 1 parameter, but 0 were specified
from sys.intrinsics import _type_is_eq
^
.../typestate/via-fn-overloading.mojo:1:1: note: candidate not viable: argument #0 cannot be converted from '_TrafficLightState' to 'None'
from sys.intrinsics import _type_is_eq
^
.../typestate/via-fn-overloading.mojo:1:1: note: candidate not viable: callee expects 1 parameter, but 0 were specified
from sys.intrinsics import _type_is_eq
^
mojo: error: failed to parse the provided Mojo source module
ModularBot
ModularBotโ€ข4mo ago
Congrats @bunny, you just advanced to level 4!
bunny
bunnyโ€ข4mo ago
fwiw, I've tried a few "magical ASCII" permutations that all are wrong. I'm just not grasping what I need to pass inside the str() to make it trigger the Self.T's __str__() method.
sora
soraโ€ข4mo ago
__str__ won't work be because it needs an instance of T, which is why I opt to make a static method on the state types.
bunny
bunnyโ€ข4mo ago
ok, I guess I was hoping I could trick the compiler with some slight of hand or something ๐Ÿ˜‚
bunny
bunnyโ€ข4mo ago
Thank you for all of this hand-holding. Right now, I'm finding myself really more confused than not with Mojo. But, I've learned a bunch of languages over the years -- maybe not to "mastery" but to Good Enough (tm) and to a level where I could contribute to production code in those languages. I'll get there with Mojo. I think that I just really want to contribute to some foundational stuff too, and that requires me to go quite a bit farther in my understanding. Thank you, sora.
Three chickens in the green bag
@bunny @sora
from utils.variant import Variant

trait TrafficLightNext(CollectionElement):
fn next(borrowed self) -> TrafficLightNext:
...

@value
struct Red(TrafficLightNext):
alias state: String = "R"

fn next(borrowed self) -> TrafficLightNext:
return Green()

@value
struct Green(TrafficLightNext):
alias state: String = "G"

fn next(borrowed self) -> TrafficLightNext:
return Yellow()
@value
struct Yellow(TrafficLightNext):
alias state: String = "Y"

fn next(borrowed self) -> TrafficLightNext:
return Red()

alias ValidTrafficLightColor = Variant[Red, Green, Yellow]

struct TrafficLight:
var state: ValidTrafficLightColor
fn __init__(inout self):
self.state: TrafficLightNext = Red()
fn next(inout self):
self.state = self.state.next()

fn main() raises:
var light = TrafficLight() # light is Red
light.next() # light is Green
light.next() # light is Yellow
light.next() # light is back to Red
from utils.variant import Variant

trait TrafficLightNext(CollectionElement):
fn next(borrowed self) -> TrafficLightNext:
...

@value
struct Red(TrafficLightNext):
alias state: String = "R"

fn next(borrowed self) -> TrafficLightNext:
return Green()

@value
struct Green(TrafficLightNext):
alias state: String = "G"

fn next(borrowed self) -> TrafficLightNext:
return Yellow()
@value
struct Yellow(TrafficLightNext):
alias state: String = "Y"

fn next(borrowed self) -> TrafficLightNext:
return Red()

alias ValidTrafficLightColor = Variant[Red, Green, Yellow]

struct TrafficLight:
var state: ValidTrafficLightColor
fn __init__(inout self):
self.state: TrafficLightNext = Red()
fn next(inout self):
self.state = self.state.next()

fn main() raises:
var light = TrafficLight() # light is Red
light.next() # light is Green
light.next() # light is Yellow
light.next() # light is back to Red
Any thoughts?
bunny
bunnyโ€ข4mo ago
Sorry slow to respond. Been busy, busy. As it is, I get compilation errors. In the TrafficLight.__init__() method, I changed it to self.state = Red() to remove a couple errors. The remaining errors are:
.../typestate/via-variant.mojo:14:21: error: cannot implicitly convert 'Green' value to 'TrafficLightNext' in return value
return Green()
~~~~~^~
{{same error for Red & Yellow }}

.../typestate/via-variant.mojo:36:32: error: 'Variant[Red, Green, Yellow]' value has no attribute 'next'
self.state = self.state.next()
~~~~~~~~~~^~~~~
mojo: error: failed to parse the provided Mojo source module
.../typestate/via-variant.mojo:14:21: error: cannot implicitly convert 'Green' value to 'TrafficLightNext' in return value
return Green()
~~~~~^~
{{same error for Red & Yellow }}

.../typestate/via-variant.mojo:36:32: error: 'Variant[Red, Green, Yellow]' value has no attribute 'next'
self.state = self.state.next()
~~~~~~~~~~^~~~~
mojo: error: failed to parse the provided Mojo source module
I really need to carve out some time to really explore Mojo's Variants a bit more. I read the docs and made a couple trite little code snippet test Variants, but I need to play a lot more when time permits.
Three chickens in the green bag
Yeah, Iโ€™m confused why itโ€™s saying it isnโ€™t confirming to the type