Hi
I needed some sort of polymorphism, but did not want to use inheritance and methods because it needs heap allocation and I have quite small objects which are created and destroyed frequently.
Finally I came out with setup which I was comfortable with, and here is example:
message.nim
type MessageType = enum msgHit, msgDeath, msgDayNight
type Message* = object
case messageType*: MessageType
of msgHit:
target*: string
hp*: int
of msgDeath:
died*: string
of msgDayNight:
isDay*: bool
# normally queue
var messages*: Message
proc hit*(target: string, hp: int): Message = Message(messageType: msgHit, target: target, hp: hp)
proc death*(died: string): Message = Message(messageType: msgDeath, died: died)
proc dayNight*(isDay: bool): Message = Message(messageType: msgDayNight, isDay: isDay)
proc getTextHit(m: Message): string = "You hit " & m.target & " for " & $m.hp & " HP."
proc getTextDeath(m: Message): string = m.died & " just died."
proc getTextDayNight(m: Message): string =
if m.isDay:
result = "The day has just begun."
else:
result = "Beware! Night is coming."
proc getText*(m: Message): string =
case m.messageType:
of msgHit:
result = getTextHit(m)
of msgDeath:
result = getTextDeath(m)
of msgDayNight:
result = getTextDayNight(m)
else:
result = "UNKNOWN MESSAGE"
main.nim
from message import dayNight, messages, getText
# game time system
proc informAboutDay() =
messages = dayNight(isDay = true)
# player informer
proc update() =
echo( getText(messages) )
when isMainModule:
informAboutDay()
update()
It is simple, it is clear but it is very verbose. What would you suggest to preserve merits while allowing addition of new MessageTypes easier. Currently it consist in:
Also I would not like to solve this problem introducing another overly complicated layer (like not simple templates for instance).
All suggestions appreciated.
Regards
Michal
"Very bad coder." - You have been warned!
I have created two macros exactly for this purpose! https://github.com/andreaferretti/patty
The first one, called variant or variantp (for public), generate the types and the three constructor functions.
The second one, called match, can be used to implement getText
but did not want to use inheritance and methods because it needs heap allocation
You are using strings so your objects are doing heap allocations anyway. You should replace the strings with IDs of the target which got hit / died if you don't want any heap allocation.
You can of course do something similar to dynamic dispatching without polymorphism:
type MessageType = enum msgHit, msgDeath, msgDayNight
type Message* = object
textImpl: proc(m: Message): string
case messageType*: MessageType
of msgHit:
target*: string
hp*: int
of msgDeath:
died*: string
of msgDayNight:
isDay*: bool
proc hit*(target: string, hp: int): Message =
Message(messageType: msgHit, target: target, hp: hp,
textImpl: proc(m: Message): string =
"You hit " & m.target & " for " & $m.hp & " HP."
)
proc death*(died: string): Message =
Message(messageType: msgDeath, died: died,
textImpl: proc(m: Message): string =
m.died & " just died."
)
proc dayNight*(isDay: bool): Message =
Message(messageType: msgDayNight, isDay: isDay,
textImpl: proc(m: Message): string =
if m.isDay: result = "The day has just begun."
else: result = "Beware! Night is coming."
)
proc getText*(m: Message): string = m.textImpl(m)
Thank you andrea.
I like your variant but match is for me similar in verbosity to my getText and I think it could be compressed further.
My attempt:
import patty
variant Message:
hit(hp: int)
death(died: string)
proc hhit(m: Message) = echo "HIT " & $m.hp
proc hdeath(m: Message) = echo "DEATH " & m.died
template choose(t: typed, handlers: openArray) =
var x = ord(t.kind)
handlers[x](t)
proc test() =
var h = hit(5)
var d = death("Bob")
choose(d, [hhit, hdeath])
test()
What do you think?
Thank you flyx.
When death message is dispatched entity is already removed so having ID I would not be able to fetch name, and you will have to make string somewhere anyway to display it - so this is not my concern.
Your solution is also very interesting.
import patty
variant Message:
Hit(hp: int)
Death(died: string)
proc writeMsg(m: Msg): string =
match m:
Hit(hp):
result = "HIT " & $hp
Death(died):
result = "DEATH " & died
Cannot get much shorter :-)
So final version from my first post would now look like:
(I took variantp from andrea's patty and added my version of match)
template match*(t: typed, handlers: openArray): untyped =
var x = ord(t.kind)
handlers[x](t)
message.nim
import patty
variantp Message:
msgHit(target: string, hp: int)
msgDeath(died: string)
msgDayNight(isDay: bool)
# normally queue
var messages*: Message
proc getTextHit(m: Message): string = "You hit " & m.target & " for " & $m.hp & " HP."
proc getTextDeath(m: Message): string = m.died & " just died."
proc getTextDayNight(m: Message): string =
if m.isDay:
result = "The day has just begun."
else:
result = "Beware! Night is coming."
proc getText*(m: Message): string =
match(m, [getTextHit, getTextDeath, getTextDayNight])
main.nim
from message import msgDayNight, messages, getText
# game time system
proc informAboutDay() =
messages = msgDayNight(isDay = true)
# player informer
proc update() =
echo( getText(messages) )
when isMainModule:
informAboutDay()
update()
Sweet. Except two things. First andrea's version is complicated and I do not understand it, so if by accident it stops working with new version of nim i am doomed. Second, I do not understand how my simple match version is working, even though I wrote it and it is working... :)
BTW: Is it possible instead of playing with AST in macros to spit just code built from strings? Like in static block i could create few lines of code which would be then compiled as rest of normal code? I know, this is wrong approach and AST is proper but still... :)