diff options
Diffstat (limited to 'doc/tut2.txt')
-rwxr-xr-x | doc/tut2.txt | 174 |
1 files changed, 101 insertions, 73 deletions
diff --git a/doc/tut2.txt b/doc/tut2.txt index cc67c3400..4515ffbd5 100755 --- a/doc/tut2.txt +++ b/doc/tut2.txt @@ -1,6 +1,6 @@ -============================= -The Nimrod Tutorial (Part II) -============================= +========================= +Nimrod Tutorial (Part II) +========================= :Author: Andreas Rumpf :Version: |nimrodversion| @@ -73,9 +73,9 @@ section. Inheritance is done with the ``object of`` syntax. Multiple inheritance is currently not supported. If an object type has no suitable ancestor, ``TObject`` -should be used as its ancestor, but this is only a convention. +can be used as its ancestor, but this is only a convention. -Note that aggregation (*has-a* relation) is often preferable to inheritance +**Note**: Aggregation (*has-a* relation) is often preferable to inheritance (*is-a* relation) for simple code reuse. Since objects are value types in Nimrod, aggregation is as efficient as inheritance. @@ -84,9 +84,9 @@ Mutually recursive types ------------------------ Objects, tuples and references can model quite complex data structures which -depend on each other. This is called *mutually recursive types*. In Nimrod -these types need to be declared within a single type section. Anything else -would require arbitrary symbol lookahead which slows down compilation. +depend on each other; they are *mutually recursive*. In Nimrod +these types can only be declared within a single type section. (Anything else +would require arbitrary symbol lookahead which slows down compilation.) Example: @@ -147,7 +147,7 @@ An example: TNode = object case kind: TNodeKind # the ``kind`` field is the discriminator of nkInt: intVal: int - of nkFloat: floavVal: float + of nkFloat: floatVal: float of nkString: strVal: string of nkAdd, nkSub: leftOp, rightOp: PNode @@ -176,18 +176,25 @@ bound to a class. This has disadvantages: * Adding a method to a class the programmer has no control over is impossible or needs ugly workarounds. -* Often it is unclear where the procedure should belong to: Is +* Often it is unclear where the method should belong to: Is ``join`` a string method or an array method? Should the complex ``vertexCover`` algorithm really be a method of the ``graph`` class? -Nimrod avoids these problems by not distinguishing between methods and -procedures. Methods are just ordinary procedures. However, there is a special -syntactic sugar for calling procedures: The syntax ``obj.method(args)`` can be -used instead of ``method(obj, args)``. If there are no remaining arguments, the -parentheses can be omitted: ``obj.len`` (instead of ``len(obj)``). +Nimrod avoids these problems by not assigning methods to a class. All methods +in Nimrod are `multi-methods`:idx:. As we will see later, multi-methods are +distinguished from procs only for dynamic binding purposes. + + +Method call syntax +------------------ + +There is a syntactic sugar for calling routines: +The syntax ``obj.method(args)`` can be used instead of ``method(obj, args)``. +If there are no remaining arguments, the parentheses can be omitted: +``obj.len`` (instead of ``len(obj)``). This `method call syntax`:idx: is not restricted to objects, it can be used -for any type: +for any type: .. code-block:: nimrod @@ -196,9 +203,17 @@ for any type: echo({'a', 'b', 'c'}.card) stdout.writeln("Hallo") # the same as write(stdout, "Hallo") -If it gives you warm fuzzy feelings, you can even write ``1.`+`(2)`` instead of -``1 + 2`` and claim that Nimrod is a pure object oriented language. (But -that's not true. :-) +(Another way to look at the method call syntax is that it provides the missing +postfix notation.) + +So code that looks "pure object oriented" is easy to write: + +.. code-block:: nimrod + import strutils + + stdout.writeln("Give a list of numbers (separated by spaces): ") + stdout.write(stdin.readLine.split.each(parseInt).max.`$`) + stdout.writeln(" is the maximum!") Properties @@ -261,60 +276,75 @@ already provides ``v[]`` access. Dynamic dispatch ---------------- -In Nimrod procedural types are used to implement dynamic dispatch. The -following example also shows some more conventions: The ``self`` or ``this`` -object is named ``my`` (because it is shorter than the alternatives), each -class provides a constructor, etc. + +Procedures always use static dispatch. To get dynamic dispatch, replace the +``proc`` keyword by ``method``: .. code-block:: nimrod type - TFigure = object of TObject # abstract base class: - fDraw: proc (my: var TFigure) # concrete classes implement this proc - - proc init(f: var TFigure) = - f.fDraw = nil + TExpr = object ## abstract base class for an expression + TLiteral = object of TExpr + x: int + TPlusExpr = object of TExpr + a, b: ref TExpr + + method eval(e: ref TExpr): int = + # override this base method + quit "to override!" - proc draw(f: var TFigure) = - # ``draw`` dispatches dynamically: - f.fDraw(f) + method eval(e: ref TLiteral): int = return e.x + + method eval(e: ref TPlusExpr): int = + # watch out: relies on dynamic binding + return eval(e.a) + eval(e.b) - type - TCircle = object of TFigure - radius: int + proc newLit(x: int): ref TLiteral = + new(result) + result.x = x - proc drawCircle(my: var TCircle) = echo("o " & $my.radius) + proc newPlus(a, b: ref TExpr): ref TPlusExpr = + new(result) + result.a = a + result.b = b + + echo eval(newPlus(newPlus(newLit(1), newLit(2)), newLit(4))) - proc init(my: var TCircle) = - init(TFigure(my)) # call base constructor - my.radius = 5 - my.fdraw = drawCircle +Note that in the example the constructors ``newLit`` and ``newPlus`` are procs +because they should use static binding, but ``eval`` is a method because it +requires dynamic binding. + +In a multi-method all parameters that have an object type are used for the +dispatching: + +.. code-block:: nimrod type - TRectangle = object of TFigure - width, height: int + TThing = object + TUnit = object of TThing + x: int + + method collide(a, b: TThing) {.inline.} = + quit "to override!" + + method collide(a: TThing, b: TUnit) {.inline.} = + echo "1" - proc drawRectangle(my: var TRectangle) = echo("[]") + method collide(a: TUnit, b: TThing) {.inline.} = + echo "2" - proc init(my: var TRectangle) = - init(TFigure(my)) # call base constructor - my.width = 5 - my.height = 10 - my.fdraw = drawRectangle - - # now use these classes: var - r: TRectangle - c: TCircle - init(r) - init(c) - r.draw() - c.draw() + a, b: TUnit + collide(a, b) # output: 2 + + +As the example demonstrates, invokation of a multi-method cannot be ambiguous: +Collide 2 is prefered over collide 1 because the resolution works from left to +right. Thus ``TUnit, TThing`` is prefered over ``TThing, TUnit``. -The code uses a ``draw`` procedure that is bound statically, but inside it -the dynamic dispatch happens with the help of the ``fdraw`` field. Even though -this solution has its advantages compared to traditional OOP-languages, it is -a **preliminary** solution. Multimethods are a planned language feature that -provide a more flexible and efficient mechanism. +**Perfomance note**: Nimrod does not produce a virtual method table, but +generates dispatch trees. This avoids the expensive indirect branch for method +calls and enables inlining. However, other optimizations like compile time +evaluation or dead code elimination do not work with methods. Exceptions @@ -497,8 +527,7 @@ simple proc for logging: debug = True proc log(msg: string) {.inline.} = - if debug: - stdout.writeln(msg) + if debug: stdout.writeln(msg) var x = 4 @@ -506,7 +535,7 @@ simple proc for logging: This code has a shortcoming: If ``debug`` is set to false someday, the quite expensive ``$`` and ``&`` operations are still performed! (The argument -evaluation for procedures is said to be *eager*). +evaluation for procedures is *eager*). Turning the ``log`` proc into a template solves this problem: @@ -514,21 +543,20 @@ Turning the ``log`` proc into a template solves this problem: const debug = True - template log(msg: expr): stmt = - if debug: - stdout.writeln(msg) + template log(msg: string) = + if debug: stdout.writeln(msg) var x = 4 log("x has the value: " & $x) -The "types" of templates can be the symbols ``expr`` (stands for *expression*), -``stmt`` (stands for *statement*) or ``typedesc`` (stands for *type -description*). These are no real types, they just help the compiler parsing. -However, real types are supported too. +The parameters' types can be ordinary types or the meta types ``expr`` +(stands for *expression*), ``stmt`` (stands for *statement*) or ``typedesc`` +(stands for *type description*). If the template has no explicit return type, +``stmt`` is used for consistency with procs and methods. -The template body does not open a new scope. To open a new scope -use a ``block`` statement: +The template body does not open a new scope. To open a new scope use a ``block`` +statement: .. code-block:: nimrod template declareInScope(x: expr, t: typeDesc): stmt = @@ -573,7 +601,7 @@ In the example the two ``writeln`` statements are bound to the ``actions`` parameter. The ``withFile`` template contains boilerplate code and helps to avoid a common bug: To forget to close the file. Note how the ``var fn = filename`` statement ensures that ``filename`` is evaluated only -once. +once. Macros |