-
-
Save matfournier/65247e627b1a7cdf77e74d655b698d09 to your computer and use it in GitHub Desktop.
import | |
# map, | |
# entity, | |
strformat | |
type | |
Terminal* = ref TerminalObj | |
TerminalObj* = object of RootObj | |
layer*: proc (i: int) | |
EchoTerminal* = ref EchoTerminalObj # version that echos to console | |
EchoTerminalObj* = object of TerminalObj | |
id*: string | |
layerValue*: int | |
BearTerminal* = ref BearTerminalObj # version that uses BearLibTerminal | |
BearTerminalObj* = object of TerminalObj | |
# constructor for console version | |
proc newEchoTerminal*(id: string): EchoTerminal = | |
var t = EchoTerminal() | |
t.id = id | |
# if you didn't implement t.layer it compiles but then doStuff explodes at runtime :( | |
t.layer = proc(i: int) = | |
t.layerValue = i | |
return t | |
# actually use it | |
proc doStuff(terminal: Terminal) = | |
terminal.layer(1) | |
when isMainModule: | |
let terminal = newEchoTerminal("id") | |
echo fmt"terminal id is {terminal.id}" | |
doStuff(terminal) | |
echo fmt"terminal layer is still {terminal.layerValue}" | |
terminal.layer(4) | |
echo fmt"terminal layer is now {terminal.layervalue}" |
What @deech said, that's why I don't end up needing interfaces or any run time business.
Between avoiding ref types unless I really need the many to one and procs which are statically dispatched, it's pretty great.
If you wanted proc doStuff(t:Terminal)
what should it do when called with Terminal()
? Nim doesn't have the concept of a set of functions that a type has to implement.
Edit: it has concepts but that's not stable yet and I haven't had good luck with them.
Do your terminals have to open for extension outside this module? If not, why not use a variant and dispatch on that particular kind?
import strformat
type
TerminalKind = enum
tkEcho, tkBear
Terminal = object
layer: proc (i: int) # everything above the case is always present and needs to be part of construction
case kind*: TerminalKind
of tkEcho:
id: string
layerValue: int
of tkBear: discard
# write your procs as if it's one thing, if you care about terminal type use a case
Yeah. This all stemmed from trying to figure out how to unit test proc's that return void. I have a ton of code doing stuff with BearLibTerminal and it all returns void --I have no way to test a ton of my codebase atm.
e.g.
type
TerminalKind = enum
tkEcho, tkbear
ITerminal = object
layer: proc (i: int)
case kind*: TerminalKind
of tkEcho:
layerValue: int
of tkBear:
discard
proc newEchoIterminal: ITerminal =
ITerminal(
kind: tkEcho,
layerValue: 0,
layer: proc(i: int) =
echo fmt"{i} was passed in"
# can't access layervalue to set it here in this proc?
)
Yup, that's right.
For testing in Nim, I recommend a few ways:
- since it's compiled you can just run things, fire up terminals or a way to observe it -- doing things purely in memory ain't that fast
- use a
when defined(testMode)
swap in observable data structures/loggers and get observability that way - have a non-void layer and test
@saem if you went down this route:
how would you implement layer
that also updates layerValue
of tkEcho?
type
TerminalKind = enum
tkEcho, tkbear
ITerminal = object
layer: proc (i: int)
layerValue: ptr[int] # this feels super hacky and not GC'd.
case kind*: TerminalKind
of tkEcho:
id: string
of tkBear:
discard
proc newEchoITerminal(s: string): ITerminal =
var layerValue = 0
ITerminal(
kind: tkEcho,
layerValue: addr layerValue,
layer: proc(i: int) =
echo fmt"{i} was passed in"
layerValue = i,
id: s
)
seems wrong to have to use the pointer but no other way I can think of ti implement layer which also stores a value in layerValue
type
TerminalKind = enum
tkEcho, tkbear
ITerminal = object
case kind*: TerminalKind
of tkEcho:
layerValue: int
of tkBear:
discard
proc layer(t: ITerminal, i: int) =
echo fmt"{i} was passed in"
case t.kind
of tkEcho:
t.layerValue = 13
of tkBear: discard
# presuming you don't need a per terminal interface proc behaviour
proc newEchoIterminal: ITerminal =
ITerminal(
kind: tkEcho,
layerValue: 0
)
How's this?
import strformat
type
TerminalKind = enum
tkEcho, tkbear
ITerminal = object
layer: proc (i: int)
layerValue: ref int
case kind*: TerminalKind
of tkEcho:
id: string
of tkBear:
discard
proc newEchoITerminal(s: string): ITerminal =
var layerValue : ref int
new layerValue
layerValue[] = 0 # some default value
proc updateLayerValue(i:int) =
layerValue[] = i
result =
ITerminal(
kind: tkEcho,
layerValue: layerValue,
layer: updateLayerValue,
id: s
)
when isMainModule:
let t = newEchoITerminal("echo terminal")
echo fmt"Should be 0: {t.layerValue[]}"
t.layer(1)
echo fmt"Should be 1: {t.layerValue[]}"
the simplest record of functions appears to be the easiest?
type
TerminalFunctions = object
layer: proc(i: int)
proc echoTerminalFunction(): TerminalFunctions =
TerminalFunctions(layer: proc (i: int) = echo fmt"layer was {i}")
proc recordingTerminalFunction(layerValue: ref int): TerminalFunctions =
proc updateLayerValue(i: int) =
layerValue[] = i
result = TerminalFunctions(layer: updateLayerValue)
when isMainModule:
echo "trying recordinfFunctions"
let echoTerminal = echoTerminalFunction()
echoTerminal.layer(1)
var layerValue: ref int
new layerValue
let recordLayerTerminal = recordingTerminalFunction(layerValue) # e.g. for a test
recordLayerTerminal.layer(10)
echo fmt"recording terminal layer is {layerValue[]}"
Something like this? No runtime issues because we use
var
and notref
: