import os, typetraits, strutils
type GenWriter = concept w
write(w,string)
proc sayHelloX(w:GenWriter) =
w.write("Hello\n")
sayHelloX(stdout)
type Talker = ref object of RootObj
name:string
writer:GenWriter
proc newTalker(name:string,writer:GenWriter):Talker =
new(result)
result.name = name
result.writer = writer
var joe:Talker = newTalker("joe",stdout)
joe.writer.write("Hello, im $#\n" % joe.name)
#proc sayHello(t:Talker) =
# t.writer.write("Hello, im $#\n" % t.name)
#joe.sayHello()
Every thing works down to the last three (commented out) lines where I try to define a procedure for a Talker which makes use of its writer. Uncommenting those three lines I get the following error message:
invalid type: 'GenWriter' in this context: 'proc (t: Talker)'
Even worse, commenting the offending lines back out and changing the declaration of joe to be
var joe:Talker = new Talker("joe",stdout)
produces a seemingly impossible error message:
type mismatch: got (Talker) but expected 'Talker'
HELP !!! AND ... What is the difference between type (Talker) and type 'Talker' ???
O BTW Merry Christmas and Happy New Year - to all
#
#var joe: Talker = newTalker("joe",stdout)
var joe = newTalker("joe", stdout)
joe.writer.write("Hello, im $#\n" % joe.name)
sayHelloX(joe.writer)
though the sayHello proc will still fail to compile. Looks like it's failing the typeAllowed check in sem.nim part of compiler.
File a github issue? Found https://github.com/nim-lang/Nim/issues/3167 which could be related.
import os, strutils
type Writer = proc(s:string)
type Talker = ref object of RootObj
name:string
writer:Writer
proc newTalker(name:string,writer:Writer):Talker =
new(result)
result.name = name
result.writer = writer
var joe:Talker = newTalker("joe",proc(s:string) = write(stdout,s) )
proc sayHello(t:var Talker) =
t.writer("Hello, im $#\n" % t.name)
joe.sayHello()
The technique that I've observed (from Araq's NimCon presentation) is to model an interface as a tuple of procs with the {.closure.} calling convention, kind of a hand rolled vtable. In your case, the Writer only has one method so a tuple is overkill. So, what you wrote is fine, just the degenerate case where the interface has a single method.
Oberon does something very similar; here's my attempt at a direct translation of the first example from Wikipedia on OO in Oberon, https://en.wikipedia.org/wiki/Oberon_(programming_language)#Object_orientation . Note that Nim complains that Warning: top level '.closure' calling convention is deprecated [Deprecated] but if I remove those pragmas compilation fails. What's the Nimble (Nimic? Nimish?) way to fix this?
import os, figures, rectangles, circles
proc main() =
var
f0 : Figure = newRectangle(0,0,2,2)
f1 : Figure = newCircle(0,0,2)
f0.draw()
f0.move(3,5)
f0.draw()
f1.draw()
f1.move(3,5)
f1.draw()
when isMainModule:
main()
and here are figures.nim, rectangles.nim, and circles.nim. Yes, I should have used floats and not ints, but this is a demo.
# module figures, the abstract interface description
type
Figure* = ref object of RootObj
ifc*: Interface
Interface* = ref InterfaceDesc
InterfaceDesc* = object
draw* : proc (f : Figure) {.closure.}
clear* : proc (f : Figure) {.closure.}
mark* : proc (f : Figure) {.closure.}
move* : proc (f : Figure; dx, dy : int) {.closure.}
proc newInterface*(draw: proc (f : Figure),
clear: proc (f : Figure),
mark: proc (f : Figure),
move: proc (f : Figure; dx, dy : int)):
Interface =
new(result)
result.draw = draw
result.clear = clear
result.mark = mark
result.move = move
proc draw* (f : Figure) =
f.ifc.draw(f)
proc clear* (f : Figure) =
f.ifc.clear(f)
proc mark* (f : Figure) =
f.ifc.mark(f)
proc move* (f : Figure, dx: int, dy: int) =
f.ifc.move(f, dx, dy)
import figures
type
Rectangle* = ref object of Figure
x, y, w, h : int
proc draw (f : Figure) {.closure.} =
let r = Rectangle(f)
echo "<Rectangle[",$r.x,",",$r.y,",", $r.w,",",$r.h,"]>"
proc clear(f : Figure) {.closure.} =
discard
proc mark(f : Figure) {.closure.} =
discard
proc move(f : Figure; dx, dy : int) {.closure.} =
let r = Rectangle(f)
r.x += dx
r.y += dy
let
ifc : Interface = newInterface(draw, clear, mark, move)
proc newRectangle*(x, y, w, h: int) : Rectangle =
new(result)
result.ifc = ifc
result.x = x
result.y = y
result.w = w
result.h = h
import figures
type
Circle* = ref object of Figure
x, y, r : int
proc draw (f : Figure) {.closure.} =
let c = Circle(f)
echo "<Circle[",$c.x,",",$c.y,",", $c.r,"]>"
proc clear(f : Figure) {.closure.} =
discard
proc mark(f : Figure) {.closure.} =
discard
proc move(f : Figure; dx, dy : int) {.closure.} =
let c = Circle(f)
c.x += dx
c.y += dy
let
ifc : Interface = newInterface(draw, clear, mark, move)
proc newCircle*(x, y, r: int) : Circle =
new(result)
result.ifc = ifc
result.x = x
result.y = y
result.r = r
No doubt you've also read about method and multi methods. Having just gotten back to Nim, I looked at the last few days of IRC logs, and I noticed that Araq wrote that he considers method to be a mistake in Nim's design and advocates for the tuple of closures approach in its place. Is it too late to jettison method?
For the explanation, thanks to brianrogoff. Functional programming trumps OO ???
Using a ref to a method tuple allows efficient sharing of the vtable - I did not bother for just one method.
Would the vtable be much faster??? - although wouldn't one would lose multi-dispatch?
Couldn't the "interface" construction be automated with a template or macro?
I still wish I could have done it the original way using concepts.
type Foo = object
x: ref SomeConcept
let a: A = ...
let b: B = ...
assert a is SomeConcept
assert b is SomeConcept
var foo: Foo
if someArbitraryCondition:
foo.x = a
else
foo.x = b
Unless the compiler can evaluate at compile time someArbitraryCondition (which in general cannot), it does not know whether foo stores an instance of A or an instance of B. This means that it cannot use static dispatch to call functions on foo.x, even if it knows that foo.x satisfies the contraints imposed by SomeConcept.
The only thing that one can do in such a situation is to use dynamic dispatch, which in Nim is unrelated to concepts. Nim does support dynamic dispatch through inheritance, and one can use that to simulate interfaces. Or, as said above, one can use a tuple of closures, a technique that has some popularity in Javascript land
Type classes and concepts are used to constrain or 'put bounds on' parametric polymorphism. Dynamic dispatch is currently implemented by method, which appears unloved by its father, or variants of the tuple/object of closures, which I described. @geezer9, how do you think the manual section on concepts http://nim-lang.org/docs/manual.html#generics-concepts could be modified to be clearer? If you're just learning Nim now your perspective here is valuable.
I'm fighting a bit with the Nim compiler translating some examples to that 'method-suite' style; the problem being that I had to use {.closure.} for the procs in rectangles.nim and circles.nim even though I think {.procvar.} should have been correct, at least according to my reading of the manual. Perhaps I should stick to the 'tuple of closure' technique and not the 'object of closure' one?
Reponse to @brianrogoff
In the manual section referenced - just state that:
Concepts are NOT useful for creating "interfaces".
I probably got led down the "garden path" because the compiler was smart enough to make the command:
joe.writer.write("Hello, im $#n" % joe.name)
work because joe was static (as @andrea explained?). I tend to learn a new language by gradual generalization - but that failed in this case.
Thanks for help.
Here's a simple example of how to do objects via closures:
import math
type Shape = ref object
name: string
area: proc(): float
circumference: proc(): float
proc makeCircle(radius: float): Shape =
proc area(): float = radius * radius * PI
proc circumference(): float = 2 * radius * PI
Shape(name: "circle", area: area, circumference: circumference)
proc makeRectangle(a, b: float): Shape =
proc area(): float = a * b
proc circumference(): float = 2*(a+b)
Shape(name: "rectangle", area: area, circumference: circumference)
let c = makeCircle(10)
let r = makeRectangle(4, 5)
for x in [c, r]:
echo "area of ", x.name, " = ", x.area()
echo "circumference of ", x.name, " = ", x.circumference()
Procedures/closures and classes are mostly one and the same thing (a fact first observed by BETA). For more modern examples, see how OCaml and Scala do classes. Classes are procedures that return their inner state, and expose access to that inner state (plus, allow polymorphism/reuse via inheritance). Note how above the "methods" are inside the "constructors".
The problem with the usual classes-via-closures implementation is that it creates a lot of memory overhead (because method tables are produced on a per instance, not per class basis). Factoring out the interface is possible, but cumbersome (as Brian observed). This is mostly because of how dispatch is unnatural (needing to pass the "self" parameter twice, dispatching through the interface). Lua is an example of a language that works around this by providing special mechanisms to aid with this type of dispatch (the f:m() calling style and overloading the indexing operation via a table); in Nim this could in principle be done via overloading the () operator with a macro, too, but would be tedious (and probably slow down compile times, given how frequent method dispatches are). The simple approach still isn't a problem if the state is large and method tables are comparatively small.
Note that you as long as all your classes inherit from the same base class (e.g. RootObj), you can always add methods to them after the fact (at the expense of type safety). This is because methods are not constrained to exist inside of classes and sort of obviates the need for interfaces in many use cases. Caveat: Method dispatch can be slow currently if the number of subclasses is large (this could be greatly improved if you opt out of Nim DLLs and could probably be improved even with DLLs). Example:
# types.nim
type
Circle* = ref object of RootObj
radius*: float
Rectangle* = ref object of RootObj
a*, b*: float
type
UndefinedMethodError =
object of Exception
template undefined*() =
raise newException(UndefinedMethodError, "undefined method")
# area.nim
import types, math
method area*(self: RootRef): float {.base.} = undefined
method area*(self: Circle): float = self.radius * self.radius * PI
method area*(self: Rectangle): float = self.a * self.b
# circumference.nim
import types, math
method circumference*(self: RootRef): float {.base.} = undefined
method circumference*(self: Circle): float = 2 * self.radius * PI
method circumference*(self: Rectangle): float = 2 * self.a * self.b
# main.nim
import types, area, circumference
for x in [Circle(radius: 10), Rectangle(a: 4, b: 5)]:
echo "area of ", x.name, " = ", x.area
echo "circumference of ", x.name, " = ", x.circumference
Note how this effectively emulates dynamic typing (selectively). Also, you don't have to have all your objects inherit directly from RootRef/RootObj; indirect inheritance is enough.
Finally, don't forget the adapter pattern, which can make any data conform to any interface.
One thing to be aware of is that Araq has said that he considers method to be a mistake and would like to deprecate it. So, when modeling OO techniques in Nim it would be prudent to avoid using it, and just stick with classes via closures and inheritance for now. In my own experiments I ran into issues mixing method with generics, though I don't recall the specifics now.
If I'm not mistaken, objects which inherit from RootObj carry metadata, so something like Ada 95/05 style OOP should be doable in Nim, perhaps with a bit of language support.
@brianrogoff: For what it's worth, I wouldn't be opposed to deprecating method (at least in its current form). Multimethods tend to look good on paper (because they fix covariant typing and solve the expression problem), but in practice create a lot of implementation headaches and have hidden downsides (such as breaking modularity and sometimes dispatching to implementations that surprise the programmer) that generally more than offset those presumed advantages [1].
That said, systems that do not have an OOP-like polymorphism tend to reinvent it, and in general, do it poorly. While one can debate the pros and cons of certain aspects of object-oriented programming (e.g. whether to use traditional inheritance, or to separate subtyping and implementation inheritance, or just do structural subtyping), what OOP gives you is polymorphism that is (1) post-hoc extensible by other types and (2) can abstract away the specifics of the implementations of those types.
For example, algebraic data types (ADTs) are often sold as alternatives to objects. But in the end, both objects and ADTs are tagged unions underneath, and ADTs are strictly less powerful (you can do pattern matching etc. on objects, too; see Scala, for example [2]). For example, consider implementing abstract syntax trees with location information as an ADT. This tends to lead to constructs like the following:
type location = { line: int; column: int; }
type ast = location * ast_node
and ast_node =
Literal of string
| Variable of string
| Expression of string * ast list
The location information then makes actual pattern matching (i.e. how you do dynamic dispatch on ADTs) considerably more convoluted and unnecessarily exposes the location information. You can alternatively stuff the location information in the record or tuple of each ADT constructor, but then you end up writing a complicated function just to extract location information [3]. If you were to use objects + inheritance, location information (and functions that use it) would be encoded in the root AST class and be trivially accessible in each subclass, yet wouldn't get in the way when you don't need it.
Variant records tend to do better than ADTs, since they can at least share part of the object layout; however, like ADTs, they also cannot be extended post-hoc with additional alternatives [4]. OCaml's polymorphic variants also fix this issue, but don't solve the problem with sharing implementation details across ADT constructors.
[1] Computer algebra systems – and some other aspects of mathematics – are an exception, but that's because of the specific needs of their type systems. Hence why Julia has a good reason to include them.
[2] Also, pattern matching can be a bit overrated in real-world situations. Try to put together a PPX rewriter in OCaml – i.e. doing pattern matching on the actual abstract syntax tree of a non-toy language that also carries significant semantic information – and you may reconsider whether pattern matching actually makes life easier or whether exhaustiveness checking is worth all that much. In practice, applications that need anything more advanced than Sather's typecase statement tend to be relatively rare outside specific application domains, such as symbolic computing, and you can argue that what you actually want for these application domains are full-blown tree parsers.
[3] We're leaving aside the fact that overloading record fields in Haskell and OCaml can be a bit problematic, because SML shows that you can do it (at least in many situations where it matters in practice).
[4] Obviously, OOP-style subtyping solves only half of the expression problem; you can add nouns, but to add verbs, you still need pattern matching, a typecase construct, the ability to add methods to classes after the fact (as in Ruby or Ada), or something similar.
I like the general idea of multimethods too, but if there's a nascent 'Nim way' I don't think they'll fit. They're quite central in Julia and Clojure, I know. I'm aware that there are issues with pattern matching and abstraction. What I was suggesting is looking at how Ada does it, since Ada is 'like' Nim in some ways (values and references, static typing, overloading, ...) and it supports simple OO. It even supports interfaces now too I think. That would mean using double dispatch and similar tricks to deal with problems multimethods solve.
The OCaml experience is interesting. Xavier Leroy gave a talk once pauillac.inria.fr/~xleroy/talks/icfp99.ps.gz which laid out the important features of class based OO languages in simple terminology for the working MLer, including the idea of 'open recursion', what you describe as post-hoc extensibility, having old functions call new ones. He had a few examples where classes were 'better', including some codegen passes in the compiler. Despite all that, the object system is hardly used in OCaml! Take a look at Jane Street's Core library, or Coq, or Infer, or just about any OCaml OSS project. Even though a lot of people think the module system is overkill, you'll see that a lot of projects use functors.
I think few would complain if OCaml reverted to something more like Caml Special Light and dropped the object system, or changed it so that it was just row typed records as a dual to polymorphic variants. Now, OCaml is in a different space than Nim, but I think the experience from many language communities (Go, JavaScript, Clojure, Haskell, ML, Erlang) is that the benefits of class based OO systems are overrated, and many languages do just fine without. I realize that it's largely aesthetics, but what level of 'OO support' belongs in Nim? I'd rather see more support for advanced C++/D like template capabilities than more OOP.
As a long time Java programmer (no stones please), I would be really thrown for a loss if methods were deprecated without some feature to condition proc calls on the actual type of a value rather than its declared (i.e. static) type. Don't macros/etc tend to produce code bloat (why I stopped using C++)? I moved my project from go to nim just because the OO model in go was so awkward - but also because its code size was passing 3M, whereas in nim it is currently down around 0.75M (including the GUI component).
If methods ARE deprecated, I surely hope there is some code automation made available to replace them.
The trouble with success is that people start depending on it.