Open PMunch opened 9 months ago
Ooh looks nice! It seems to be calling for meta programming :)
Definitely something which could be tidied up a bit by some nice metaprogramming, yes!
Played around with this concept a bit more, it's a bit less general, and a bit more hacky, but even more type-safe. With this you can't even unpack the value from those fields which don't have a value:
import std / options
type
StaticOpt[C: static bool, T] = object
when C:
data: T
else:
_: array[sizeof(T), byte] # Add this so that the object is always the same size
proc `$`[C: static bool, T](s: StaticOpt[C, T]): string =
when C: "some(" & $s.data & ")"
else: "none(" & $typeof(T) & ")"
proc get[C: static bool, T](s: StaticOpt[C, T]): T =
when C: s.data
else: {.error: "Data not available".}
type Button[IconState, BorderState: static bool] = object
label: string
icon: StaticOpt[IconState, string]
border: StaticOpt[BorderState, string]
func initButton(label: string): Button[false, false] =
result.label = label
func withIcon[B](button: Button[false, B], icon: string): Button[true, B] =
result = cast[Button[true, B]](button)
result.icon.data = icon
func withBorder[I](button: Button[I, false], color: string): Button[I, true] =
result = cast[Button[I, true]](button)
result.border.data = color
echo initButton("Hi").withIcon("π")
echo initButton("Hi").withBorder("blue").withIcon("π")
echo initButton("Hi").withIcon("π").withBorder("red")
echo initButton("Hi").withIcon("π").icon
echo initButton("Hi").icon
echo initButton("Hi").withIcon("π").icon.get
assert not compiles(initButton("Hi").icon.get)
assert not compiles(initButton("Hi").withIcon("π").withIcon("β"))
assert not compiles(initButton("Hi").withIcon("π").withBorder("red").withBorder("blue"))
Had a look at your phantom types test. Here is a small fix for the generic casting, as well as an attempt to answer the "what happens if you have multiple phantom types" question.