-
Notifications
You must be signed in to change notification settings - Fork 23
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC] Interfaces in Nim #39
Comments
I have one, https://github.com/andreaferretti/interfaced to which I have not contributed much, if anything. Actual work done by @krux02 and @RSDuck. Example of usage: import interfaced
type
Human = ref object
name: string
Dog = ref object
proc makeNoise(human: Human): string =
"Hello, my name is " & human.name
proc legs(human: Human): int = 2
proc greet(human: Human, other: string): string =
"Nice to meet you, " & other
proc makeNoise(dog: Dog): string = "Woof! Woof!"
proc legs(dog: Dog): int = 4
proc greet(dog: Dog, other: string): string = "Woof! Woooof... wof!?"
createInterface(Animal):
proc makeNoise(this: Animal): string
proc legs(this: Animal): int
proc greet(this: Animal, other: string): string
proc interact(animal: Animal) =
echo animal.makeNoise
echo animal.greet("James Bond")
proc interactAll(animals: varargs[Animal, toAnimal]) =
for animal in animals:
animal.interact()
when isMainModule:
var
me = Human(name: "Andrea")
bau = Dog()
for animal in @[me.toAnimal, bau.toAnimal]:
echo "Number of legs: ", legs(animal)
interactAll(me, bau) A nice addition (which is not there right now) would be to link interfaces to concepts (somehow derive one from the other) |
Existing OOP/interface macros:
I have tried to implement elegant interfaces using macros. (https://github.com/zielmicha/collections.nim/blob/master/collections/iface.nim). It works, but requires interface definition outside of type section:
Unless I'm missing something, macro system has a limitation - you couldn't emit code in type section.
|
What about planned vtref? Are they cancelled? |
Given the current status of concepts, they are too fragile to base yet another complex feature on top of them. They are delayed, as far as I'm concerned. Probably @zah will chim in and disagree. :-) |
Maybe it would be useful to take a simple example of usage and express it using the different libraries in order to compare them |
@andreaferretti whay are there Aparently the inteface libary has been spoiled with ref types, I would like to point you to the original version that I wrote in the forum that does not need any ref types at all: |
I changed it to |
@RSDuck probably we should discuss this somewher else, but it is "that bad". I desigend the interfaces carefully, so that they can be used without any GC active. I am very careful about this, I don't want any |
The issue tracker of interfaced can be a good place to discuss this |
I think we can and should give the |
Araq: well I don't think that's necessary. The macro can check if it is based on a ref type or anything else. My original implementation just didn't care for ref types, but I don't think it is necessary to have an additional parameter. |
Maybe, but whether it produces a |
These library-based approaches will become unnecessary once the VTable types are ready, but otherwise I'm not against them especially if they will give us some experience. Some aspects of the VTable types are already implemented. In particular @Araq hasn't merged yet one of my patches enabling the "Converter Concepts" feature, which can be used here as well to get a bit easier to use interface for the interface-based procs :) (i.e. you can have a converter concept automatically calling |
I posted a $1000 bounty for this issue: https://app.bountysource.com/issues/68171483-rfc-interfaces-in-nim This is the feature I miss the most for nim, but it doesn't seem to get much traction, so I'm creating some :) |
Interfaces can almost be implemented with
type
Human = ref object
name: string
Dog = ref object
proc makeNoise(human: Human): string =
"Hello, my name is " & human.name
proc legs(human: Human): int = 2
proc greet(human: Human, other: string): string =
"Nice to meet you, " & other
proc makeNoise(dog: Dog): string = "Woof! Woof!"
proc legs(dog: Dog): int = 4
proc greet(dog: Dog, other: string): string = "Woof! Woooof... wof!?"
type
Animal = concept x
x.makeNoise() is string
x.legs() is int
x.greet(string) is string
assert(Human is Animal, "Human is Animal")
assert(Dog is Animal, "Dog is Animal")
proc interact(animal: Animal) =
echo animal.makeNoise
echo animal.greet("James Bond")
# proc interactAll(animals: varargs[Animal]) =
# for animal in animals:
# animal.interact()
echo Human is Animal # true
echo Dog is Animal # true
when isMainModule:
var
me = Human(name: "Andrea")
bau = Dog()
me.interact
bau.interact
# for animal in @[me, bau]:
# echo "Number of legs: ", animal.legs()
# interactAll(me, bau)
|
These are the same issue: concepts don't support vtables, they're just constraints. For some concept A, you can't have a value "of type A". If you have a |
With type
MAnimal = ref object of RootObj
Human = ref object of MAnimal
name: string
Dog = ref object of MAnimal
proc makeNoise(human: Human): string =
"Hello, my name is " & human.name
proc makeNoise(dog: Dog): string = "Woof! Woof!"
type
CAnimal = concept x
x.makeNoise() is string If we need assert(Human is MAnimal, "Human is Animal")
assert(Dog is MAnimal, "Dog is Animal")
assert(Human is CAnimal, "Human is Animal")
assert(Dog is CAnimal, "Dog is Animal")
proc interact(animal: CAnimal) =
echo animal.makeNoise In those examples, So I don't see any need for vtables/
We can't expect the compiler to test all the concepts to find the common concept. We need a way to tell that this type or that type are supporting this or that concept. implements CAnimal for Human:
proc makeNoise(human: Human): string =
"Hello, my name is " & human.name
proc makeNoise(dog: Dog): string = "Woof! Woof!"
implements CAnimal for Dog
I'm ok to implement those changes. |
Because you are using inheritance? :-) Interfaces are a different way to do dynamic dispatch, and some indirection, using vtables or other means, has to be used if you want to derive interfaces from concepts |
Because that is sugar for generics: proc interact[T: CAnimal](animal: T) =
echo animal.makeNoise You can't have a variable of type
Yes, you misunderstood how concepts currently work. You can't have an array/seq of different types implementing the same concept, just like you can't have an array/seq of ints and strings. That would require converting them to some common type (which would be a vtable for the concept).
A solution to what problem exactly? The problem isn't that the compiler has to "find the common concept". |
Since this hasn't been mentioned yet, some prior art (in order of relevance for Nim):
|
@andreaferretti No, because my previous example is also working and it isn't using inheritance. |
@konsumlamm Let's say we have In the following code,
Oh, I understand that.
To have interfaces in Nim. Interfaces are essentially concepts limited to functions and fields. Interfaces or traits are used to shared common features between objects. This is why I tried to do with What I'm confused is that |
@konsumlamm My |
There seem to be a lot of confusion between runtime & compile time here This is how it would work under the hood # How concept currently works
block:
proc greet(a: CAnimal): string = "Animal: " & a.makeNoise()
echo dog.greet()
# compiled to
proc greet[T: CAnimal](a: T): string = "Animal: " & a.makeNoise()
echo dog.greet[Dog]() # the compiler knows `dog` is a `Dog`
# A VAnimal would look like this instead
block:
type
VAnimal = vtype CAnimal
# Compiled to ---
VAnimal = object
makeNoise: proc: string
converter toVAnimal(canimal: CAnimal): VAnimal =
result(makeNoise: proc: string = canimal.makeNoise())
# -------
proc greet(a: VAnimal): string = "Animal: " & a.makeNoise()
echo dog.greet()
# Compiled to (thanks to the converter)
echo greet(toVAnimal(dog))
# Once we have a VAnimal, it's a real value which can be used anywhere (stored in object, etc)
var s: @[toVAnimal(dog), toVAnimal(human)]
for an in s: s.greet() # this would be impossible with concepts, since the procedure
# is used with different types depending on the runtime value |
The difference would be in how the parameter is represented. A
It's not used as a type, concepts just also work with
But you don't need an explicit
It's used as a type in none of these cases, the first is just sugar for generics and the second is builtin. |
@Menduist Thx The under the hood is helpful.
@konsumlamm You are right. Multiple generated functions, a sugar for generics. So the compiler already have the list of compatible types with the concept. This is what I was missing... Please correct me if I'm wrong: type
VAnimal = vref CAnimal
# would be compiled to
VAnimal = ref object
makeNoise: proc: string and type
VAnimal = vptr CAnimal
# would be compiled to
VAnimal = ptr object
makeNoise: proc: string |
@Menduist What are you missing in the library interfaced? |
I've recently had a discussion about this with @Araq, so this is more of a reminder for myself and him, but comments are still welcome.
Araq's ultimate goal for Nim is to have a simple language with a powerful macro system. As such, interfaces in Nim should be implemented using macros.
Goals:
streams
modulePotential further research:
The text was updated successfully, but these errors were encountered: