type
ITest = tuple[
setter: proc(v: int) {.closure.},
getter1: proc(): int {.closure.},
getter2: proc(): int {.closure.}]
proc getInterf(): ITest =
var shared1, shared2: int
return (setter: proc (x: int) =
shared1 = x
shared2 = x + 10,
getter1: proc (): int = result = shared1,
getter2: proc (): int = return shared2)
var i = getInterf()
i.setter(56)
echo i.getter1(), " ", i.getter2()
I looked at Type Classes but they looked more like constraints than interfaces. Although a thread in the forum showed the possibility of Type Classes being more than what's in the manual at some point in the future so who knows.
The tuple closure approach looks interesting, but puts the code in the interface rather than delegating it to the associated class. Someone else had an interesing approach here: http://forum.nimrod-code.org/t/31. It was a bit convoluted so I'm seeing how I might clean it up a bit and use that approach, but I'm having some trouble with "method pointers" style references. Will continue this thread with code I have so far.
Here is the code I have so far in my attempt to create some cross cutting interface type. The places where I'm having problems I've marked with # ERROR:. Any help would be appreciated, even if this is a bad direction to be going and there's a better way that Nimrod has for creating these cross cutting concerns. Please forgive my lack of Nimrod knowledge, I'm only a handful of hours into coding Nimrod at this point. I could be way off here.
type
IMoverAndShaker[T] = object
owner: T
Move: ref proc(item: T)
Shake: ref proc(item: T)
proc MoveAndShake[T](item: IMoverAndShaker[T]) =
item.Move() # defer back to class where defined
item.Shake() # defer back to class where defined
# =============================================== #
# Made up sample class to show "interface" methods
type
TCat = object
name: string
clawSharpness: int
moverAndShaker: ref IMoverAndShaker[TCat] # must be ref or get Error: illegal recursion in type 'TCat'
method SayHello(item: TCat) =
echo "Hello from Cat named: " & item.name
method Move(item: TCat) =
echo "Cat '", item.name, "' is Moving"
method Shake(item: TCat) =
echo "Cat '", item.name, "' is Shaking"
# =============================================== #
# More of the same
type
TDog = object
name: string
barkLevel: int
moverAndShaker: ref IMoverAndShaker[TDog]
method SayHello(item: TDog) =
echo "Hello from Dog named: " & item.name
method Move(item: TDog) =
echo "Dog '", item.name, "' is Moving"
method Shake(item: TDog) =
echo "Dog '", item.name, "' is Shaking"
# =============================================== #
echo "Starting"
var cat = TCat(name: "Barfield", clawSharpness: 5)
cat.SayHello()
cat.Move()
# Build an explicit IMoverAndShaker
# Eventually this would be a single line constructor/factory, but explicit here while learning.
var temp: ref IMoverAndShaker[TCat]
temp.Owner = cat # so we know the concrete object
# Need to get "method pointer" type of thing
# temp.Move = Move # ERROR: Can't get reference to the Move method
# temp.Shake = Shake # ERROR: Can't get reference to the Move method
cat.moverAndShaker = temp # set the mover and IMoverAndShaker instance
# finally, see if I can call a method with param type IMoverAndShaker
# MoveAndShake(cat.moverAndShaker) # ERROR: Can't figure out this call
# cat.moverAndShaker.MoveAndShake() # ERROR: Can't figure out this call
I don't get it. What's so hard about this:
type
IMoverAndShaker = object # no generic parametrization here as that's what the closures provide
Move: proc ()
Shake: proc ()
proc MoveAndShake(item: IMoverAndShaker) =
item.Move()
item.Shake()
# Made up sample class to show "interface" methods
type
Cat = ref object
name: string
clawSharpness: int
proc SayHello(item: Cat) =
echo "Hello from Cat named: " & item.name
proc Move(item: Cat) =
echo "Cat '", item.name, "' is Moving"
proc Shake(item: Cat) =
echo "Cat '", item.name, "' is Shaking"
proc `!!`(c: Cat): IMoverAndShaker =
IMoverAndShaker(Move: proc () = c.Move, Shake: proc () = c.Shake)
# More of the same
type
Dog = ref object
name: string
barkLevel: int
method SayHello(item: Dog) =
echo "Hello from Dog named: " & item.name
method Move(item: Dog) =
echo "Dog '", item.name, "' is Moving"
method Shake(item: Dog) =
echo "Dog '", item.name, "' is Shaking"
proc `!!`(c: Dog): IMoverAndShaker =
IMoverAndShaker(Move: proc () = c.Move, Shake: proc () = c.Shake)
# =============================================== #
echo "Starting"
var c = Cat(name: "Barfield", clawSharpness: 5)
c.SayHello()
c.Move()
var d = Dog(name: "Bello", barkLevel: 32)
d.SayHello()
d.Move()
# some polymorphism:
var animals = [!!c, !!d]
for a in animals:
a.MoveAndShake()
The !! procs could be generated via a macro. Left as an exercise for the reader. Beware I didn't test the code.
An interface is just a class with only abstract methods. So, as long as you don't require multiple inheritance, you don't need to laboriously emulate dynamic dispatch, just provide a base class without an implementation. Note that Nimrod doesn't have abstract methods (they are difficult to reconcile with multiple dispatch).
You'll also need ref object instead of object, or extraneous instance variables will be destroyed upon assigning to variables of type IMoverAndShaker.
Try this:
type
IMoverAndShaker = ref object {. inheritable .}
proc notImplemented() =
raise newException(EInvalidValue,
"method not implement for abstract base class")
method Move(item: IMoverAndShaker) =
notImplemented()
method Shake(item: IMoverAndShaker) =
notImplemented()
method SayHello(item: IMoverAndShaker) =
notImplemented()
proc MoveAndShake(item: IMoverAndShaker) =
item.Move() # defer back to class where defined
item.Shake() # defer back to class where defined
type
TCat = ref object of IMoverAndShaker
name: string
clawSharpness: int
method SayHello(item: TCat) =
echo "Hello from Cat named: " & item.name
method Move(item: TCat) =
echo "Cat '", item.name, "' is Moving"
method Shake(item: TCat) =
echo "Cat '", item.name, "' is Shaking"
# =============================================== #
type
TDog = ref object of IMoverAndShaker
name: string
barkLevel: int
method SayHello(item: TDog) =
echo "Hello from Dog named: " & item.name
method Move(item: TDog) =
echo "Dog '", item.name, "' is Moving"
method Shake(item: TDog) =
echo "Dog '", item.name, "' is Shaking"
# =============================================== #
var cat = TCat(name: "Barfield", clawSharpness: 5)
var dog = TDog(name: "T-Rex", barkLevel: 5)
var catsAndDogs = @[cat, dog]
for critter in catsAndDogs:
critter.MoveAndShake
While I don't need multiple "inheritance" I do need multiple interfaces available for a given class. I'm not trying to just solve this particular Cat/Dog example but a Nimrod technique for static typing across multiple classes. I guess one classic example would be to have a "ILogger" interface that can be applied to any arbitrary class or hierarchy.
My "real life" example is to have interfaces for financial objects like IBar and ISeries. An IBar interface (simplified) would contain methods for High, Low, Open, and Close. These could be applied to stock ticks, option ticks, volume ticks, etc. An ISeries would be used when grouping across time, volume, index series, etc. And or course you can have an ISeries that also implements IBar.
In short, I don't care that a TCat is an IMoverAndShaker but I do care than I can operate on an object/class that implements the methods of IMoverAndShaker without having to know anything about the class doing the implementation. The solution doesn't need to be classic inheritance (or even a classic interface), but the "ownership knowledge" at the interface level must be irrelevant.
Something like this should work for you then:
template UseBarInterface {. immediate .} =
mixin high, low, open, close
template UseSeriesInterface {. immediate .} =
mixin dates, ...
proc showAsBar[Bar](finObj: Bar) =
UseBarInterface
echo finObj.high, finObj.low, finObj.close
proc showAsSeries[Series](finObj: Series) =
UseSeriesInterface
echo finObj.dates, ...
proc financialWizardry[BarAndSeries](finObj: BarAndSeries): MagicData =
UseBarInterface; UseSeriesInterface
result = conjureData(finObj.high, finObj.low, finObj.dates)
Araq: > "I don't get it. What's so hard about this"
It's probably not hard, but it's hard for me. It's hard for me because I'm new to Nimrod and because I'm admittedly just an average and self taught coder. I suspect most people here at this stage of things are considerably better than average and probably comfortable with compiler theory, type theory, and all that jazz.
In general, I get about an hour a day to play with new languages and just recently ran across Nimrod. I'm here to look at the language and see if it "clicks" for me. Is Nimrod something I could use to begin stepping away from C# and Windows?
However, Nimrod is still pre Ver 1. Perhaps I'm here too early?
Anyway, with that background here are the kinds of things I'm struggling with (i.e. _why_ it's so hard for me).
A few somewhat more concrete examples:
- o.MyMethod() # This executes the method
- o.MyMethod # This also executes the method ... except when sometimes it returns a method reference???
Please note: I'm not asking for answers to this. It's the kind of thing I expect to figure out or get help with a piece at a time if Nimrod and I are going to synch.
Hope this provides an answer and thanks to the forum members for your patience.
The user defined type class feature looks really good. However, the syntax mixes values and types in function calls - this is a confusing quirk.
E.g. f(x) could mean that the type of the argument is either x itself or the type of x, depending on how x is defined. This hurts readability and doesn't quite feel right. Could this be fixed with a required ':' prefix that means 'the type of this argument is..'?
So you write either f(:x) or f(x) and the meaning is clearer. The choice of ':' is important because x:T in nimrod usually means a value x of type T. So just :T could represent an unnamed value of type T. Also, it looks more like the function definition that it verifies.
Take this example:
type OutputStream = generic S
write(var S, string)
The call could be rewritten as:
write(:var OutputStream, :string)
Note you don't really need the named parameter S above.
Or, perhaps:
write(S:var, :string)
---updated, additions below this line ---
Taking this idea further, you don't need 'is'. Instead of (x > y) is bool you just write _: Bool = x > y. Observe this is a syntactically correct nimrod code. Taking it even further, you can say the that the body of generic is just normal nimrod code with some flexibility:
If the body compiles, the test passes. Converting the examples from the manual:
type
Comparable = generic x, y
smallerx: bool = (x < y)
Comparable2 = generic x, y
if x < y:
...
Container[T] = generic c
lenc: ordinal = c.len
itr: iterator = items(c)
item: T
for item in c:
...
Container2[T] = generic c
(:ordinal) = c.len
(:iterator) = items(c)
for (:T) in c:
...
Type classes written this way can also serve as good usage examples. Since this is imperative rather than declarative, you could use a new top level keyword generic, although I prefer spec.
spec Container[T](c):
item: T
for item in c:
...
The advantage of this is you can defined two related types in one definition, although I'm not sure of the utility there.
--- possible implementation ---
Is it possible to just reuse the compiler to implement this? E.g. replace each unique unnamed placeholder (:T) with a variable x_UNIQUEID and insert the declaration x_UNIQUEID: T at the top of the function. Then the standard compiler could injest and compile this as function.
Well, my original proposal was to use a dedicated operator that denotes a dummy value of a given type and thus not requiring any special rules in the body of the type class.
The operator I used back then was @ (just as an example, but also because it has the right precedence) and people seemed to be really turned off by it, hence we arrived at our current design. You can find the discussions here: http://forum.nimrod-lang.org/t/208
The ":" operator is a minor improvement aesthetically, but it will require more work since ":" is not treated as a unary operator at the moment.
One downside of the operator approach in general is that it doesn't play very nice with nkCommand type of calls:
foo :string, x
For this reason, I've grown to like the current design, even tho initially it was born as a compromise.
Regarding _: Bool = x < y vs (x < y) is bool vs bool(x < y) and so on, we cannot really enforce a style, since all of them are valid Nimrod code and people will simply pick the one they like the most.
Ah I didn't know you can use 'is' without 'when' in nimrod. If a statement such as 'x is int' in a proc will cause compilation to fail when x is not of type int, then the 'is' form is equivalent inside and outside the generic. The 'is' form is very readable though, and my main issue is not with 'is' but with finding types where values are expected.
I see how :T doesn't work well with nkCommand type calls (which shouldn't be allowed in the first place, but that's besides the point).
So here's another suggestion - require the user to define variable names that represent values of specific types. E.g.
type Stream = generic S
where:
data: string
write(var S, data)
data = read(var S)
This also allows the user to choose meaningful names. E.g.
type File = generic f
where:
filename, mode, data: String
var f = open(filename, mode)
write(var f, data)
Ah - you're right. I was going more by what I read in the docs (incomplete, I know). I reread the original thread and it makes sense.
FWIW, I think the nimrod generics feature is not only much more powerful than the typical Java-esque interface, it is in a way much simpler. The definitions are much more readable as they show 'this is how you use the functions' rather than 'this is how the functions are declared'.