Interface makes libraries extensible. Now we can use function pointers to simulate interface
in Nim such as StreamObj.
However, when we want to use its function calls, we must duplicate object's name such as s.closeImpl(s).
If we heavily use interface(and even nested) to create reusable components, it may be a little tedious. Especially we have a nested object structure, we may not want to write D.c.b.a twice.
type
A = object
p: function pointer
B = object
a: A
C = object
b: B
D = object
c: C
I suggest making a helper macros(s.ucall(closeImpl)) to deduplicate object's name(Maybe put it in sugar.nim).
Make a ucall macros and automatically call object's name.
macro ucall(obj: typed, call: untyped, params: varargs[untyped]): untyped =
result = newStmtList()
var tmp = newNimNode(nnkCall)
tmp.add(newDotExpr(obj, call))
tmp.add(obj)
for param in params:
tmp.add(param)
result.add tmp
# before
echo a.barkImpl(a, 27, c = 14)
# after
echo a.ucall(barkImpl, 27, c = 14)
# before
echo people.longAndUgly.pet.barkImpl(people.longAndUgly.pet, 27, c = 14)
# after
echo people.longAndUgly.pet.ucall(barkImpl, 27, c = 14)
# before
people.longAndUgly.pet.sleepImpl(people.longAndUgly.pet)
# after
people.longAndUgly.pet.ucall(sleepImpl)
# before
echo people.longAndUgly.pet.danceImpl(people.longAndUgly.pet, "Nim")
# after
echo people.longAndUgly.pet.ucall(danceImpl, "Nim")
# echo Animal(id: 12, barkImpl: bark, danceImpl: dance).
# barkImpl(Animal(id: 12, barkImpl: bark, danceImpl: dance), 27, c = 16)
# echo Animal(id: 12, barkImpl: bark, danceImpl: dance).ucall(barkImpl, 27, c = 16)
# echo newAnimal(13).barkImpl(newAnimal(13), 66, 99)
# echo newAnimal(13).ucall(barkImpl, 66, 99)
Full examples in https://play.nim-lang.org/#ix=2qKq
Another idea would be to create a macro that generates templates to allow using this syntax s.closeImpl() directly.
Experimental codes: https://github.com/xflywind/shene
Make interface based on function pointers and generics support better polymorphism.
import shene
import strformat
type
Animal*[T] = object of RootObj
id: int
sleepImpl: proc (a: T) {.nimcall, gcsafe.}
barkImpl: proc (a: T, b: int, c: int): string {.nimcall, gcsafe.}
danceImpl: proc (a: T, b: string): string {.nimcall, gcsafe.}
Cat* = object of Animal[Cat]
cid: int
People*[T] = object
pet: Must[Animal[T], T]
proc sleep*(a: Cat) =
discard
proc bark*(a: Cat, b: int, c: int): string =
result = fmt"{a.cid + b + c = }"
proc dance*(a: Cat, b: string): string =
result = fmt"{a.id = } |-| {b = }"
proc newCat*(id, cid: int): Must[Animal[Cat], Cat] =
result.id = id
result.cid = cid
result.sleepImpl = sleep
result.barkImpl = bark
result.danceImpl = dance
let p = People[Cat](pet: newCat(id = 12, 13))
echo p.pet.call(barkImpl, 13, 14)
p.pet.call(sleepImpl)
echo p.pet.id
echo p.pet.cid
I have done this before: https://github.com/b3liever/protocoled however it needs to be split, so protocol and impl are separate blocks, don't have the time, so feel free to take over.
impl and dataImpl represents Impl Class and data stands for Data Class. Impl Class supplies all interfaces which should be satisfied. It shouldn鈥檛 be inherited. Data Class supplies all attributes that can be extended by users. It supports inheritance.
type
Must*[U; T: object | ref object] = object
impl: U
data: T
type
Animal*[T] = object of RootObj
sleepImpl: proc (a: T) {.nimcall, gcsafe.}
barkImpl: proc (a: var T, b: int, c: int): string {.nimcall, gcsafe.}
danceImpl: proc (a: T, b: string): string {.nimcall, gcsafe.}
type
Dog = object
id: string
did: int
name: string
proc bark(d: var Dog, b: int, c: int): string =
doAssert d.name == "OK"
d.did = 777
doAssert d.did == 777
d.id = "First"
doAssert d.id == "First"
# must(Impl class, Data class)
proc newDog(): must(Animal, Dog) =
result.name = "OK"
result.did = 12
result.barkImpl = bark
type
People*[T] = object
pet: must(Animal, T)
var d = newDog()
var p1 = People[Dog](pet: d)
discard p1.pet.call(barkImpl, 13, 14)
Find vTable in docs and close this issue.
Find
vTablein docs and close this issue.
Vtables are not really implemented yet :)