diff options
author | michele.simionato <devnull@localhost> | 2007-12-02 14:12:36 +0000 |
---|---|---|
committer | michele.simionato <devnull@localhost> | 2007-12-02 14:12:36 +0000 |
commit | b672466c102677799fcaf10cc98580c5537735c1 (patch) | |
tree | 8bce07517ba93f759280fa008bab45a15474ac18 /ml | |
parent | 07279fd0cb5e642a4068ffa808f381d01674eccf (diff) | |
download | micheles-b672466c102677799fcaf10cc98580c5537735c1.tar.gz |
Added my article on SML to the repository
Diffstat (limited to 'ml')
-rw-r--r-- | ml/article1.txt | 362 | ||||
-rw-r--r-- | ml/article2.txt | 324 | ||||
-rw-r--r-- | ml/article3.txt | 347 | ||||
-rw-r--r-- | ml/article4.txt | 453 | ||||
-rw-r--r-- | ml/article5.txt | 44 |
5 files changed, 1530 insertions, 0 deletions
diff --git a/ml/article1.txt b/ml/article1.txt new file mode 100644 index 0000000..5b28c23 --- /dev/null +++ b/ml/article1.txt @@ -0,0 +1,362 @@ +Functional Programming For Dynamic Programmers - Part 1 +======================================================= + +:author: Michele Simionato +:date: December 2007 + +This is the first of a series of articles on functional programming in +statically typed languages. It is intended for dynamic programmers, i.e. +for programmers with a background in dynamically typed languages, such as Perl, +Python, Ruby, or languages in the Lisp family. The approch is eminently practical +and example-based; the main goal is to see if we can stole some good idea from +statically typed languages. In order to be concrete, I will consider languages +in the ML family, because they are pretty nice and much easier to understand +that Haskell. + +Declaration of intents +---------------------- + +One may rightly wonder why to study functional languages, in a +particular statically-typed ones [#]: after all, their relevance in +enterprise programming is negligible, they are unknown for system +administration task, and unless you are a computer science researcher +you have no need to know them. All that is true; on the other hand, if +you believed it, you would not be a dynamic programmer, you would have +stick with C and you would not be a reader of my papers. +dynamic programmer is the kind of person who, when everbody says +";the Earth is flat", will wonder: "is the Earth *really* flat?". So, +even if everybody says that object orientation is good and that +inheritance is the best thing after sliced bread, a dynamic programmer +will be suspicious of these claims and will seek for alternatives. +Exactly in the same way he escaped from the enslaving by static typing +in old fashioned languages, he will escape from the enslaving by +dynamic typing languages, if he finds valid alternatives. A dynamic +programmer has a kind of Casanova complex, and he will never be loyal +to a single language. +In particular, a dynamic programmer will judge academic languages +worth of investigation: he may decided not to use them, but still he may get +some good ideas from them. After all, Python stole the list comprehension +idea from Haskell and iterators from Icon; Ruby stole many ideas from Scheme +and Smalltalk; Perl stole from everybody. +I always wanted to learn a modern statically-typed functional +language, at least to have something intelligent to say in the endless +usenet discussions on dynamic typing vs. static typing; recently, by +accident, I run into Standard ML (SML, ML stands for Meta Language) +and I found out it has a very clean syntax, so I decided to learn it +[#]. +SML is practically unknown outside academia (its cousins Caml and F# are a +little more known, but they are certainly very far away from being +mainstream languages) and I am not interested in advocating it for use +in enterprise programming; after all in enterprise programming the technical +advantages of a language count very little compared to thing like the support +you can get, the ability to find skilled programmers, the interoperability with +your existent codebase and development environment (I mean the tools and +libraries you use, the database, etc). My motivation here is to learn something +from SML, and to try to avoid mistakes in mainstream languages which have +been already fixed in SML. Let me give a concrete example: in the past I have been +using Zope and Twisted, which are enterprise-level Python frameworks. They +share a common implementation of interfaces. I have always had a bad gut +feeling about Zope interfaces: I always thought they were overkill, complex, and +not doing by default what they (In my opinion) should do, i.e. interface checking. +I always thought there should be a better solution (Python 3.0 actually will +have a simple implementation of interfaces in the core language) +but until I studied SML I had no idea of how the solution should look like. +I think SML has the better module system I have ever seen; and my goal +here is to popularize it, trying to get some of SML ideas in Python frameworks. + +Micro-introduction to functional languages +------------------------------------------------------------ + +Simply put, functional languages are programming languages that try very hard to +avoid mutation and side effects. There are no pure functional +languages in use, since an absolutely pure language would have to rule +out input and output and it would be pretty much useless; +nevertheless, some functional languages are purer that others. For +instance, there are languages which have some support for functional +programming, but are not considered functional (i.e. Python); others, +like Lisp, which is regarded as the grandfather of functional +languages, that are pretty much regarded as impure nowadays; Scheme is +somewhat perceived as more functional than Common Lisp, but this is +debatable, and in any case it is not very pure; Standard ML (SML) is +often perceived as purer than Scheme, but mostly because of +syntactical convenience, not of technical merit; Haskell is possibly +the purest functional language commonly used, but still can be used in +an imperative way. This classification is sloppy and wrong in more +than one way; nevertheless it is useful (in my personal opinion) in +the sense that it gives and indication of how much the language +departs from a mainstream imperative language, and how difficult it is +to learn it. Functional languages come in two kinds: dynamically +typed (Lisp, Scheme, Erlang ...) and statically typed (SML, Haskell +...). Programmers used to dynamic typing (i.e Pythonistas, Rubystas, +etc) will have less trouble to understand dynamically typed functional +language, so it is more interesting (in my opinion) to look at +statically typed languages. After all, I think a programmer should have at +least a statically typed language in his toolbox (C++ and Java do not +count). SML is the simplest statically language out there, so l decided to +start with it + +SML is a language with many implementations, so if you want to try it, +you must decide which implementation to use. I have played with some +of them, including SML of New Jersey (http://www.smlnj.org) and MLton +(http://mlton.org) but at the end I have decided to use Alice ML, +since it has a lot of innovative features, an interesting set of libraries, and +some clean syntax extensions. It is also a very dynamic version of SML, +so that dynamic programmers will not feel too constrained ;-) +On Ubuntu, you can install it with +``apt-get install alice-runtime`` (having added +``deb http://www.ps.uni-sb.de/alice/download/debian stable contrib`` to your +``/etc/apt/sources.list``) where on the Mac, you can download the +``.dmg`` installer and add ``/Applications/Alice.app/Contents/Resources/bin`` +to your PATH. There is also an installer for Windows, but if you are a dynamic +programmer I do not expect you to use Windows, unless you are forced +to ;) + +Having installed Alice you can start the "interpreter" or REPL +(read-eval-print-loop) [#]_ as follows:: + + $ alice + Alice 1.4 ("Kraftwerk 'Equaliser' Album") mastered 2007/04/24 + ### loaded signature from x-alice:/lib/system/Print + ### loaded signature from x-alice:/lib/tools/Inspector + ### loaded signature from x-alice:/lib/distribution/Remote + +Now you can write your first mandatory "Hello world" program:: + + - print "Hello world\n"; + Hello world + val it : unit = () + +and you are ready to start ;) If you are an old timer Emacs user, you +may also want to install (Aqua)Emacs and the sml-mode for Emacs (Alice +has a custom version of it, that you can download from here_ ). + +.. _here: http://www.ps.uni-sb.de/alice/download/sml-mode-4.0+alice.zip + +.. [#] Modern statically-typed language here means a language with + type-inference, such as SML and Haskell. We all know that + there are no good ideas to be extracted from ancient + statically typed languages such as Java, C and C++ ;) + +.. [#] In principle, one may argue that choosing a language because of + its syntax is stupid and that one should look at its features, + libraries, reliability, etc; in practice, many languages are + more or less equivalent in terms of features, libraries and + reliability, so that the main factor for the choice of the + language is it syntax. Also, when you are a beginner and you + need to write only simple programs, syntax is all that + matters. + +.. [#] Technically the Alice REPL is not an interpreter: the + expressions you enter are compiled on the fly and executed + immediately, not interpreted. However, the overall + user-experience is the same as if you had an + interpreter. This approach is commons in ML and Lisp implementations. + +"Hello world", Part I +--------------------------------------- + +In the previous section I have shown how the traditional "Hello world" +program looks in ML. ML is a language without statements (as usual in +functional languages; statements are actually a bad idea IMO, even +for imperative languages [#]_) so *print* is a function taking a string in +input and returning an empty tuple in output, as you +can see from the last line of output + +``val it: unit = ()`` + +The empty tuple is of type ``unit`` and it is the sole representative of that +type (i.e. it is a singleton). In general, any object has a type; +for instance, if you enter the number 42 at the prompt you will get + +:: + + - 42; + val it: int = 42 + +The REPL just prints a string representation of the value +you entered, together with its type (in this case, int). + +A point to notice in the "Hello world" program +is the lack of parenthesis: we wrote ``print +"Hello world\n";`` and not ``print("Hello world\n")``. This is +possible since the ``print`` function in ML takes only a *single* +argument; actually *all* functions in SML takes a single argument! + +You may wonder how is it possible to program anything serious if all +functions take a single argument: the answer is that the single +argument can be of a composite type, for instance a tuple. So you may +define functions like the following + +:: + + - fun print2 (arg1, arg2)= (print arg1; print arg2); + val print2 : string * string -> unit = _fn + +Here ``print2`` takes a single argument, which is a 2-tuple (a pair), +and returns the null (unit) value. The interesting thing is that the +compiler has been able to figure out by itself that the arguments must +be of string type, since ``print`` expects a string, and that +``print2`` will return an ``unit`` value, since ``print`` does so: we +are seeing here the type -inferencer at work. We also see that a +function is just a value as any other value, i.e. functions are first +class objects, as usual in dynamics languages; its type is +``string * string -> unit``. + +Let us check that ``print2`` works as expected:: + + - print2 ("hello ", "world"); + hello world + val it: unit = () + +If you want to print a generic number of arguments, you can use a list:: + + - fun printList lst = app print lst; + val printList : string list -> unit =_fn + - printList["Hello ", "World", "!", "\n"]; + Hello World! + val it : unit = () + +The builtin function ``app`` apply the ``print`` function to each element of the +list from left to right. + +ML does not have default arguments, so you cannot write the analogous +of this Python function:: + + >>> def printUser(user="Michele"): + ... print "Hello,", user + >>> printUser() + Hello, Michele + >>> printUser("Mario") + Hello, Mario + +In Python, ``printUser`` can be called with zero or one arguments, but in ML this +is not possible, since functions with different arity are of a different type and +ML does not support function overloading; however, you can however work around +this feature [#]_ by using options. ML has a standard *option* type, used for +objects which may have a NONE value; any object can be converted into an option +by wrapping it with SOME. An example will make it clear. Let me define:: + + - fun printUser(userOpt: string option) = + printList ["Hello, ", getOpt(userOpt, "Michele"), "\n"]; + val printUser : string option -> unit = _fn + +where *getOpt(opt, default)* is a builtin function which returns the +value of the option, or the default if the option have value NONE. We +may call *printUser* as follows:: + + - printUser NONE; + Hello, Michele + - printUser (SOME "Mario"); + Hello, Mario + +The syntax is definitively ugly, and there are actually clever tricks +to implement default arguments in a nicer way, but they are too +advanced for this introduction. But stay tuned for the next issues ;) + +.. [#] Incidentally, in current Python ``print`` is a statement and it + does not return anything, but in Python 3000 ``print`` will be + function returning None + +.. [#] The lack of function overloading may be seen as a feature or as + a limitation, depending on the reader. + +Functional programming and Input/Output +----------------------------------------------------- + +Input/output is the black ship of functional programming and it is +often relegated at the end of tutorials and sometimes even omitted, as +if shameful. Sometime, it is not even standardized and +implementation-dependent. On the other hand, *all* programs must +perform some kind of input/output, or at least of output, otherwise we +would have no way to see what a program did. Therefore input/output +*must* be explained at the beginning of any tutorial, not at the +end. I always hated books not telling me how to read a file until the +appendix! In this section I will explain how you can read a file in +SML. Reading a file is for some reason seen as "inpure" since it is +not functional. Let me explain that a bit more: in mathematics, a +function takes an argument in input, and returns an output; if I call +twice the function with the same argument, I get twice the *same* +output. In programming, a procedure reading a line from a file returns +a *different* line at each invocation, even if called with the same +arguments (the arguments maybe simply the empty tuple), so it is not a +function in the mathematical sense. On the other hand, a procedure +writing a line into a file, is also impure, since it is modifying an +object, the file itself. +In strict functional programming there is no +mutation, objects are declared once and for all times they do not +change. Functional programming in its purest form is therefore useless: +notice that even the paradigmatic "Hello world" program is not a functional +program, since the "Hello world" string is printed via an imperative side +effect. In other words, every functional programming language must find a +non-functional way to manage input-output. Haskell tries very hard to hide +non-functional constructs into monads; SML just uses traditional +imperative constructs to perform input/output. We already saw the +``print`` function to write on stdout; to read from stdin, you can +define the following utility:: + + - fun input prompt = (print prompt; getOpt(TextIO.inputLine TextIO.stdIn, "")) + val input : string -> string = _fn + +The parenthesis here are needed since the right hand side of a function definition +must be an expression, and the parenthesis (plus the expression separator ";") +allows you to compose expressions in a single expression returning as value +the value of the last expression. ``TextIO`` is a module in the standard library +managing textual I/O and ``TextIO.inputLine inputFile`` is a function +returning a line wrapped into an option object or NONE if we arrived at the +end of the input file. In particular, in the case of standard input, +we arrive at the end when we give CTRL-D (in a Unix-like system); in +this case ``input`` will return the empty string, otherwise it returns the +input line, comprehensive of the newline character. + +Here is an example of usage:: + + - input "Enter a string: "; + Enter a string: hello + val it : string = "hello\n" + - input "Enter CTRL-D "; + - Enter CTRL-D + val it : string = "" + +I we want to read a text file (assuming it fits in memory) +the simplest way is to use ``TextIO.inputAll``:: + + - fun fileToString filename = let + val file = TextIO.openIn filename + in + TextIO.inputAll file finally TextIO.closeIn file + end + +Conversely, if we want to write a text on a file we can define:: + + - fun saveFile (fname, content) = let + val out = TextIO.openOut fname + in + TextIO.output(out, content) finally TextIO.closeOut out + end; + +The examples here use the standard *let* expression, which has the form:: + + let + <bindings> + in + <expression; .. ; expression> + end + +and returns as value the value of the last expression. Moreover, the examples +here use the *finally* construct, which is an Alice extension to SML, and +works as you would expect:: + + <expression possibly raising exceptions> finally <cleanup expression> + +the finally clause if executed always, even +if there is an exception in the first expression. + +The examples here are very simple and do not address the issue or reading +or writing large files which do not fit in memory. But don't worry, +we will address those situations in the next issues, have faith! + +---- + +*A long journey starts with the first step* + + -- old Chinese saying diff --git a/ml/article2.txt b/ml/article2.txt new file mode 100644 index 0000000..400b364 --- /dev/null +++ b/ml/article2.txt @@ -0,0 +1,324 @@ +Functional Programming For Dynamic Programmers - Part 2 +======================================================= + +:author: Michele Simionato +:date: 10 November 2007 + +This is the second of a series of articles on functional programming in +statically typed languages. It is intended for dynamic programmers, i.e. +for programmers with a background in dynamically typed languages, such as Perl, +Python, Ruby, or languages in the Lisp family. The approch is eminently practical +and example-based; the main goal is to see if we can stole some good idea from +statically typed languages. In order to be concrete, I will consider languages +in the ML family, because they are pretty nice and much easier to understand +that Haskell. + +Loops +---------------------------------------------------- + +Perhaps the most common construct in imperative programming is the *for* loop; +in spite of that, *for* loops are usually missing in functional languages. In this +section I will explain and the way to work around this omission. +To start with a practical example, suppose we want to define a function +to count the number of lines in a text file, something akin to the following +Python code [#]_:: + + def countLines(fname): + counter = 0 + with file(fname) as f: + for line in f: + counter += 1 + return counter + +.. [#] We are using here the ``with`` statement, available in Python 2.5 by + importing it from the future: ``from __future__ import with_statement``. + +How can we implement it without a *for* loop? +One solution is to cheat and to use a *while* loop, which exists in SML:: + + fun countLines fname = let + val counter = ref 0 + val inp = TextIO.openIn fname + in + while not (TextIO.endOfStream inp) do + (TextIO.inputLine inp; counter := !counter + 1) + finally TextIO.closeIn inp; + !counter + end + +Some explanation about ML references is in order here. +ML does not allow to mutate a variable directly (as a matter of fact +most types in ML are immutable); if you want to increment an integer, +you must wrap it into a reference object, and you must mutate the +reference. Some simple experiment at the prompt should give you +the gist of what references are. + +Put the integer 0 into a memory location and returns a reference to it:: + + - val counter = ref 0; + val counter : int ref = ref 0 + +Return the value in the location pointed by the reference:: + + - !counter; + val it : int = 0 + +Put in that location the value 0+1; return the empty tuple:: + + - counter := !counter + 1; + val it : unit = () + +Return the new value of the counter:: + + - !counter + val it : int = 1 + +From this explanation, it should be obvious what ``countLines`` does; +the implementation works, but it is very ugly and strongly discouraged. I +show it here, to prove that SML can be used in an imperative way, not +to suggest to code in this way. However, it may be +not obvious to re-implement ``countLines`` without mutation, unless +you have already coded in functional languages. + +There is a standard tecnique to solve this kind of problem, i.e. the conversion of an +imperative loop into a functional recursion: *the accumulator tecnique*. + +The accumulator is the mutating parameter in the imperative loop, in this case, the +counter. The accumulator tecnique consists in rewriting the loop +as a recursive function, promoting the accumulator to the rank of additional +parameter in the function signature:: + + - fun countLines' (file, counter) = + case TextIO.inputLine file + of NONE => counter + | SOME line => countLines'(file, counter + 1); + val countLines' : TextIO.instream * int -> int = _fn + +As you see, ``countLines'`` is a recursive function calling itself +with an incremented counter, until the file is read completely, then +it returns the final value of the accumulator. You can then rewrite the +original function as + +:: + + - fun countLines (fname) = let + val file = TextIO.openIn fname + in + countLines' (file, 0) + finally TextIO.closeIn file + end; + val countLines : string -> int = _fn + +Recursion and accumulators are ubiquitous in functional programming, so the +time spent understanding this example is worth it. I will close this paragraph +by noting that the accumulator tecnique is also used to convert non-tail-call +recursive functions like the good old factorial:: + + - fun fact 0 = 1 + | fact n = n*fact (n-1); + +into a tail-call recursive function, i.e. a function returning either a value without +making a recursive call, or directly the result of a recursive call:: + + - fun fact n = fact' (n, 1) + and fact' (0, acc) = acc + | fact' (n, acc) = fact' (n-1, acc*n); + +Here I have use the ``and`` syntax to define two or more conceptually connected +functions in a single step. + +Tail-call recursive functions are equivalent to imperative loops, in this case to +the Python code:: + + def fact(n): + acc = 1 + while n > 0: + acc = acc*n + n = n-1 + return acc + +Internally, the compiler of functional languages are able to translate tail-call +recursive functions back to imperative loops, so that the implementation is +very efficient. In a language like Python instead, which is not a truly functional +language, it is possible to write:: + + def fact(n, acc): + if n == 0: + return acc + else: + return fact(n-1, acc*n) + +but this function is not converted into a loop, you will have a loss of performance +and you will incur in the recursion limit if try to compute the factorial of a +large enough integer (say 1000) [#]_ . + +.. [#] Notice that Python does not make tail call optimization *on + purpose*, essentially for two reasons: 1) the language has a + philosophy of preferring iteration over recursion; 2) keeping the + recursive calls in the stack gives more informative tracebacks and + helps debugging. Since I am at that, let me notice that Python error + reporting and debugging features are infinitely superior to the ones + of any SML or Scheme implementation I have seen. This is not because + of lack of tail call optimization, it is because as a general + philosophy Python does not care about speed but cares about + programmer; moreover, despite the fact that SML + and Scheme are twice as old as Python, a lot more of work went into + Python than in SML/Scheme for what concerns practical issues. + +Higher order functions +--------------------------------------------------------- + +Just as recursion is pervasive in functional programming, so are +higher order functions. You may judge how much functional is a language +by measuring how good is the support for recursion and for higher order +functions. In this respect, ML shines since it has a particularly elegant syntax to +define higher order functions i.e. functions returning functions. + +.. higher order functions: http://en.wikipedia.org/wiki/Higher_order_functions + +The canonical example of higher order function is the adder:: + + - fun adder n = fn x => x+n; + val adder : int -> int -> int = _fn + +The notation ``fn x => x+n`` denotes what is usually called a lambda +function, according to the Lisp tradition; in this example it means +that the ``adder`` returns a function which adds *n* to any integer +number; for instance:: + + - val add1 = adder 1; (* adds 1 to any number *) + val add1 : int -> int = _fn + - add1 1; + val it : int = 2 + +Notice that ML use the notation ``int -> int -> int`` to denote the type of +the adder, which should be read as + + ``int -> (int -> int)`` + +i.e. *the arrow associates on the right* and should be interpreted as +"the adder is a function taking an *int* and returning a function *int->int*". +On the other hand, *function application associates on the left* and +`` adder 1 2`` should be read as + + ``(adder 1) 2`` + +An equivalent, but cleaner notation for the adder is + +:: + + - fun adder n x = x+n; + val adder : int -> int -> int = _fn + +Notice the difference betwen ``adder(n, x)``, denoting a function of +two arguments, and ``adder n x`` denoting an higher order function, +so that ``adder n`` is a function itself. + +The ``adder`` is a simple example, +but it does not make justice to the usefulness of higher order functions. +To give a practical example, we may consider the problem of avoiding +the boilerplate when opening/closing a file. Lisp use for that a few +macros (``with-input-from-file``, ``with-output-to-file``) whereas +Python use a special syntax (the ``with`` statement); in ML it is quite +natural to define a higher order function such as the following:: + + - fun ` manage fname = let + val file = TextIO.openIn fname + in + manage file + finally TextIO.closeIn file + end; + val ` : (TextIO.instream -> 'a) -> string -> 'a = _fn + +Here ````` is just an identifier for the higher order function; I could have used +for it a longer name, such as +``higherOrderFunctionAddingAutomaticOpenCloseFunctionality``, but I am lazy, +and also I wanted to show that in ML one has a considerable freedom in the choice +of valid identifiers. The action of ````` is clear: it takes a function which operates +on TextIO.stream objects (i.e. text files) and converts it into a function that +operates on strings (i.e. file names) and has opening/closing functionality +embedded. The type of the return value is not specified at this stage, and +this is indicated by the notation *'a* (to be read alpha), which denotes a +generic type. +Using ````` the ``fileToString`` function defined in the first +article of this series could be written as simply as:: + + - val fileToString = `TextIO.inputAll; + val fileToString : string -> string = _fn + +whereas ``countLines`` could be written as:: + + - val countLines = `(fn file => countLines'(file, 0)) + val countLines : string -> int = _fn + +You should begin to appreciate the power of higher order functions, now ;) +An evident weakness of the approach used here, is that it works only +for text files (actually only for files opened for input; we would need to +define a different higher order function for files opened for output); +if we wanted to wrap binary files, we would need +to define an equivalent higher order function using the ``BinIO`` library +instead of ``TextIO``; then, if we wanted to use it for sockets, we would need +to define yet another higher order function; in general there +are infinite resources which can be opened and closed, and we could define +an infinite number of higher order functions doing all the same thing. +This is bad; fortunately this potentially infinite code duplication can be solved +using functors, but you will have to wait for the next articles to see how to do it ;) + +Higher order functions can also be used to implement poor man's object +systems; consider for instance this example:: + + class Counter(object): + def __init__(self, initial_value): + self.initial_value = self.value = 0 + def incr(self): + self.value += 1 + def show(self): + print "The value is %d" % self.value + def reset(self): + self.value = self.initial_value + +We can get the same effect via a stateful higher order function (a.k.a. a closure):: + + exception MissingMethod + + fun makeCounter initialValue = let + val value = ref initialValue + in + fn "reset" => value := initialValue + | "show" => (print The value is "; print (Int.toString (!value)); print "\n") + | "incr" => value := !value + 1 + | _ => raise MissingMethod + end + + val counter = makeCounter 0; + + do counter "incr"; + do counter "show"; (* The value is 1 *) + do counter "reset"; + do counter "show"; (* The value is 0 *) + +This example also shows the usage of pattern matching and of exceptions. + +You can rewrite it in a functional way as follows: + +---- + +*The venerable master Qc Na was walking with his student, Anton. Hoping to +prompt the master into a discussion, Anton said "Master, I have heard that +objects are a very good thing - is this true?" Qc Na looked pityingly at +his student and replied, "Foolish pupil - objects are merely a poor man's +closures."* + +*Chastised, Anton took his leave from his master and returned to his cell, +intent on studying closures. He carefully read the entire "Lambda: The +Ultimate..." series of papers and its cousins, and implemented a small +Scheme interpreter with a closure-based object system. He learned much, and +looked forward to informing his master of his progress.* + +*On his next walk with Qc Na, Anton attempted to impress his master by +saying "Master, I have diligently studied the matter, and now understand +that objects are truly a poor man's closures." Qc Na responded by hitting +Anton with his stick, saying "When will you learn? Closures are a poor man's +object." At that moment, Anton became enlightened.* + + -- Anton van Straaten diff --git a/ml/article3.txt b/ml/article3.txt new file mode 100644 index 0000000..3991f89 --- /dev/null +++ b/ml/article3.txt @@ -0,0 +1,347 @@ +Functional Programming For Dynamic Programmers - Part 3 +======================================================= + +:author: Michele Simionato +:date: November 2007 + +This is the third of a series of articles on functional programming in +statically typed languages. It is intended for dynamic programmers, i.e. +for programmers with a background in dynamically typed languages, such as Perl, +Python, Ruby, or languages in the Lisp family. The approch is eminently practical +and example-based; the main goal is to see if we can stole some good idea from +statically typed languages. In order to be concrete, I will consider languages +in the ML family, because they are pretty nice and much easier to understand +that Haskell. + +Structures, modules, packages, components and all that +------------------------------------------------------ + +Usually programs start small, but they have an unfortunate tendency to +grow, getting larger and larger and eventually to explode. To control +this tendency, programmers have invented various techniques, which are +essentially all variations of a same idea: large programs should be +decomposed in smaller, more manageable, conceptually connected units +of code. These units of code go under various names, depending on the +language you are using; common terms are structures, classes, +namespaces, modules, packages, libraries, components etc. In this +article I will focus on the ML terminology and tecniques. + +In SML, the basic mechanism of code control is the structure, which +takes the place of what is called a module in other languages. We +already encountered structures before, such as the ``TextIO`` +structure, containing functions such as ``TextIO.openIn``, +``TextIO.closeIn`` etc. + +It is also possible to define custom structures. For instance, suppose +we want to implement Python-like file-iteration in SML in order to be +able to write things like ``Iter.app print (Iter.file fname)`` to +print all the lines in a text file. We can do so with the following +structure:: + + - structure Iter = struct + + exception StopIteration + + fun app func iter = + (while true do func (iter ())) handle StopIteration => () + + fun map func iter = + fn () => func (iter ()) + + fun file fname = let + val inp = TextIO.openIn fname + in fn () => + (case TextIO.inputLine inp + of NONE => raise StopIteration + | SOME line => line) + handle err => (TextIO.closeIn inp; raise err) + end + end; + structure Iter : + sig + exception StopIteration + val app : ('a -> 'b) -> (unit -> 'a) -> unit + val map : ('a -> 'b) -> (unit -> 'a) -> unit -> 'b + val file : string -> unit -> string + end + +We see many interesting things here. First of all, the REPL returns us +a string representation of the so-called *signature* of the structure, +i.e. a description of the types of the objects encapsulated by the +structure. Iterators have been implemented as thunks, i.e. functions +of type ``unit -> 'a``; in particular, ``file`` is a higher order +function taking a filename and returning a string-value thunk: at each +call of the closure``Iter.file fname`` we get a different line of the +file. All types have been inferred correctly: ``Iter.app`` is an +higher order function taking a generic function ``'a->'b`` (which +means a function taking an unspecified type ``'a`` and returning an +unspecified type ``'b``, possibly different from ``'a``) and +converting it into a function ``iterator -> unit``, whereas +``Iter.map`` applies a generic function to an iterator and returns an +iterator. For instance, suppose we want to define an utility to +convert to upper case a text file, by applying the function [#]_ + + ``- fun upper str = String.implode (map Char.toUpper (String.explode str));`` + +to each line; we can test it by writing a file like the following:: + + $ cat three-lines.txt + line 1 + line 2 + line 3 + +and by defining + +:: + + - val next = Iter.map upper (Iter.file "three-lines.txt"); + val next : unit -> string = _fn + +With this definitions, we get the same behavior as in Python [#]_ :: + + - next (); + val it : string = " LINE 1\n" + - next (); + val it : string = " LINE 2\n" + - next (); + val it : string = " LINE 3\n" + - next (); + Uncaught exception + StopIteration + +.. [#] In this example I am using the ``String`` and ``Char`` structures of the + SML standard library, documented here + http://www.standardml.org/Basis/string.html and here + http://www.standardml.org/Basis/char.html + +.. [#] In Python we would write + +:: + + >>> it = itertools.imap(str.upper, file("three-lines.txt")) + >>> it.next() + ' LINE 1\n' + >>> it.next() + ' LINE 2\n' + >>> it.next() + ' LINE 3\n' + >>> it.next() + Traceback (most recent call last): + File "<stdin>", line 1, in <module> + StopIteration + +Structures in the standard library are automatically available; on the other +hand, if you write your own structure and save it into a file, you need to +import the file before you can use it. The simplest way to import a file is +through the ``use`` expression:: + + - use "<filename.aml>"; + +This simply includes the imported file in the current program, and everything +is compiled together. In most SML implementation it is also possible to compile +a file as a standalone component; the details vary with the implementation. +In Alice you can just save the above structure into a file called ``iter.aml`` +and compile it as + + ``alicec iter.aml`` + +This creates a bytecode compiled file called ``iter.alc``. +The compiled structure can be imported in your programs with the line + + ``import structure Iter from "iter"`` + +assuming ``iter.alc`` is in the same directory as your main program. +If you want to import in the program namespace all the objects defined +inside the structure, you can open it: + + ``- open Iter;`` + +this is however actively discourages, to prevent namespace pollution, i.e. +name conflicts, since an exported name can shadow a pre-existent name. +There is actually only a good use case for opening a structure, i.e. inside +another structure. In particular, it is possible to redefine structures, augmenting +them with additional objects as in this example:: + + - structure Iter = struct + open Iter + fun binfile (fname, chunksize) = let + val inp = BinIO.openIn fname + in + fn () => let + val vec = BinIO.inputN (inp, chunksize) + in + if Word8Vector.length(vec) = 0 then raise StopIteration else vec + handle err => (BinIO.closeIn inp; raise err) + end + end + end; + +Here I have redefined the ``Iter`` structure by adding a routine to read +binary files in chunks. Notice that ``BinIO.inputN`` returns a vector +of bytes, not a string; however, you can get a *bona fide* string by +applying the standard library function Byte.bytesToString_ to +the returned chunks. Here is an example:: + + - Byte.bytesToString (Iter.binfile("three-lines.txt", 40) ()); + val it : string = " line 1\n line 2\n line 3\n" + +An importanting to notice is that *you can redefine even standard library +structures*, augmenting them with new features, or replacing objects +with others, even *with a different signature*. This is similar to what +happens in Ruby, where you can add methods even to builtin classes, +and should be done with care. + +.. _Byte.bytesToString: http://www.standardml.org/Basis/byte.html + +Structures are not first class values +----------------------------------------------------------- + +The problem with structures is that they are not first class values, so they cannot +be passed to functions and they cannot be inspected. This is the reason why giving at +the prompt the name of a structure does not work:: + + - TextIO; + 1.0-1.6: unknown value or constructor `TextIO' + +``TextIO`` is not recognized as the name of a know value. Structures live +in a completely separate namespace, so that you can associate any value +to the name ``TextIO`` [#]_ + +:: + + - val TextIO = "astring"; + val TextIO : string = "astring" + +and still you can access the contents of the structure without any problem:: + + - TextIO.print; + val it : string -> unit = _lazy + +Structures can be given name and aliases via the ``structure`` declaration; +for instance, if you want to give a shorter alias to TextIO you can define + + ``- structure T=TextIO;`` + +Structures can be arbitrarily nested, i.e they can contain +substructures at any level of nesting. For instance, if you want to +extract the substructure ``StreamIO`` from ``TextIO`` you can define + + ``- structure S=TextIO.StreamIO;`` + +The lack of first class structures is motivated by reasons of (premature) optimization. +Suppose structure where first class values: then you could write things like + + ``val S = if someRuntimeCondition() then struct ... end else struct ... end`` + +and that would make life pretty hard for the compiler: basically, it would be +impossible to compile the structure once and for all, and you would be forced +to recompile it at runtime. In these days of just-in-time +compilation this is less of an issue than in the past, and in particular +Alice ML allows you to do that, by wrapping structures in packages. +But in order to discuss that, we must first discuss signatures, which is +the SML name for interfaces. + +Signatures as a form of restricted import +-------------------------------------------- + +Signatures are another kind of non-first-class objects. +Every structure has a principal signature, which is the most specific signature +for that structure. In practice, the principal signature +the one shown by the prompt if you do no specify a custom signature; +however, it is possible to define more +generic versions of the principal signature. To be concrete, let me +consider two structures of the standard library,``TextIO`` and ``BinIO``; +their principal signatures are different since they have different implementations, +but still they share many +features and it is possibile to define subsignature(s) matching both. +For instance, you could define a simplified interface for IO as follows:: + + signature SIMPLE_IO = sig + type instream + type outstream + type vector + val openIn: string-> instream + val closeIn: instream -> unit + val openOut: string-> outstream + val closeOut: outstream -> unit + val inputAll: instream -> vector + val output: outstream * vector -> unit + end; + +and you could import only a subset of the features provided by ``TextIO`` +and ``BinIO`` as follows:: + + structure T = TextIO:SIMPLE_IO; + structure T : + sig + type vector = vector + type instream = TextIO.instream + type outstream = TextIO.outstream + val openIn : string -> TextIO.instream + val inputAll : TextIO.instream -> vector + val closeIn : TextIO.instream -> unit + val openOut : string -> TextIO.outstream + val output : TextIO.outstream * vector -> unit + val closeOut : TextIO.outstream -> unit + end = TextIO + + + structure B = BinIO:SIMPLE_IO; + structure B : + sig + type vector = vector + type instream = BinIO.instream + type outstream = BinIO.outstream + val inputAll : BinIO.instream -> vector + val closeIn : BinIO.instream -> unit + val output : BinIO.outstream * vector -> unit + val closeOut : BinIO.outstream -> unit + val openIn : string -> BinIO.instream + val openOut : string -> BinIO.outstream + end = BinIO + + - open BinIO:SIMPLE_IO; + - val f = B.openIn "three-lines.txt"; + val f : BinIO.instream = _val + - val x = Byte.bytesToString (B.inputAll f); + - B.closeIn f; + +.. [#] You will get a warning about violating standard naming conventions, since + a regular value such as a string should be denoted with a non-capitalized + identitifier, whereas capitalized identitifiers should be reserved to structures. + +Opaque signatures and information hiding +---------------------------------------------------- + +The rationale behind opaque signatures +is that they make easier to replace an implementation +with another without breaking the interface. + +(which are a way of implementing the +facade pattern) +http://en.wikipedia.org/wiki/Facade_pattern + +Another advantage of signatures is that they allow to implement information +hiding + +http://en.wikipedia.org/wiki/Information_hiding + +i.e. if your structure contains private methods which are not meant to be +exposed to the application programmer you can just provide a signature not exporting +them. On the other hand, the application program has still the choice to ignore +your proposed signature and use the principal signature: in this case of course, +he will take his risks, since the interface of private methods is not guaranteed +and could change in future versions of the library, but he can do that if he +needs it. + +:: + + - structure T=TextIO:>SIMPLE_IO; + structure T : SIMPLE_IO + + +---- + + *divide et impera* + + -- old Roman saying diff --git a/ml/article4.txt b/ml/article4.txt new file mode 100644 index 0000000..6c42a18 --- /dev/null +++ b/ml/article4.txt @@ -0,0 +1,453 @@ +Functional Programming For Dynamic Programmers - Part 4 +======================================================= + +:author: Michele Simionato +:date: November 2007 + +This is the fourth of a series of articles on functional programming in +statically typed languages. It is intended for dynamic programmers, i.e. +for programmers with a background in dynamically typed languages, such as Perl, +Python, Ruby, or languages in the Lisp family. The approch is eminently practical +and example-based; the main goal is to see if we can stole some good idea from +statically typed languages. In order to be concrete, I will consider languages +in the ML family, because they are pretty nice and much easier to understand +that Haskell. + +Functors +------------------------------------------------------ + +As we saw in the previous article, structures are not first class objects and +they cannot be passed to and returned from regular functions. +To circumvent this restriction, the authors of ML invented +the concept of *functor*, which is basically a *sui generis* function +taking structures in input and to returning structures in output. Functors +are not-first-class objects themselves and therefore they require a specific +declaration, such as the following:: + +- functor Wrap(SimpleIO:SIMPLE_IO) = struct + val inputAll = SimpleIO.inputAll + output = SimpleIO.output + fun fileIn manage fname = let + val file = openIn fname + in + manage file finally closeIn file + end + fun fileOut manage fname = let + val file = openOut fname + in + manage file finally closeOut file + end + end + +The functor here is important, since it shows how it is possible to write +generic code in SML. In this case, I have just written a library which is +able to wrap *any* structure matching the ``SimpleIO`` interface, and I have +avoided a potentially infinite code duplication. +The specification of which specific structure to use will be the job of the client +code, not of the library author; in particular a particular user of the library +may be interested in specializing it both for ``TextIO`` and ``BinIO`` +by writing: + +- structure T = Wrap(TextIO) +- structure B = Wrap(BinIO) + +The operation of specializing a functor is also called *functor instantiation*; +since it happens in a structure declaration it is performed by the compiler +*at compile time*. The advantage is that the compiler can generate different optimized +code for the structures``T`` and ``B`` in the *client* program. + +do print (IO.fileIn IO.inputAll "three-lines.txt") + +The Standard ML type system +------------------------------------------------------------------- + +We have already seen many builtin types, such integers, strings, lists, etc. +The type system of SML is extensible, and the user can define new types in terms +of primitive types. + +In SML any value has a type, but types itself are not values, i.e. they are +not first class values. That means that passing the name of a type to the +prompt gives an error:: + + - string; + 1.0-1.6: unknown value or constructor `string' + +Just as structures, types live in a separated namespace. The lack of +first class types also means the lack of metaclasses which are common +in dynamic languages such as Smalltalk, Lisp, Perl, Python, Ruby, +etc. Besides, SML types are not classes in any OO sense of the term, +you cannot introspect them, there are no methods, and no inheritance; +so, the first step in order to learn the SML type system is to forget +everything you know about OOP ;) Having said that, you can certainly +program in an OO style in ML, using closures as we saw in our previous +essay, or using functors as we will show in the next paragraph; the +point here is that you don't use types for OOP, types are used +for pattern matching. + +Pattern matching in turn (among many other things) can be used to implement +runtime type dispatching. For instance, suppose, you want to define an utility +function casting integer to strings. The first thing to do is to define an +int_or_string datatype with two constructors INT and STR: + +- datatype int_or_string = INT of int | STR of string; + + +Now it is possible to implement the casting function via pattern +matching on the constructors + + - fun valueToString (INT x) = Int.toString x + | valueToString (STR x) = x; + val valueToString : int_or_string -> string = _fn + +Let me check that it works: + + - valueToString (INT 1); + val it : string = "1" + - valueToString (STR "1"); + val it : string = "1" + +With this trick it is possible to emulate C++-style function overloading. + +In particular he may define composite types, like the following:: + + - datatype string_int = STRING_INT of string * int; + datatype string_int = STRING_INT of string * int + - + +The uppercase identifier is called the constructors of the datatype, and can +be used to make concrete instances of the type:: + + - STRING_INT("hello", 1); + val it : string_int = STRING_INT ("hello", 1) + - + +Notice is that the type itself is not a first class value:: + + - string_int; + 1.0-1.10: unknown value or constructor `string_int' + + + +where the constructor is a first class value, being simply a function:: + + - STRING_INT; + val it : string * int -> string_int = _fn + +It is also possible to define polymorphic types, where the constructor can +accept any type as argument. For instance, suppose we want to define a generic +container type:: + + - datatype 'a container = CONTAINER of 'a; + datatype 'a container = CONTAINER of 'a + - + +#- CONTAINER + + + +a *named_function* type corresponding +to a pair *(function, name)*, where *function* can be of any functions, whereas +*name* is a string. +We can do it as follows:: + + - datatype ('a, 'b) named_function = NAMED_FUNCTION of ('a->'b) * string; + datatype ('a, 'b) named_function = NAMED_FUNCTION of ('a -> 'b) * string + - + +*named_function* is a parametric type with parameter 'a (to be read *alpha*), +which corresponds to a generic type, and NAMED_FUNCTION is its associated +constructor:: + + - NAMED_FUNCTION; + val it : ('a -> 'b) * string -> ('a, 'b) named_function = _fn + - + +In other words, NAMED_FUNCTION is a function converting a pair (value, name), +where *value* can be of any type, into a *named_function* parametric type. +Here is an example:: + + - NAMED_FUNCTION (fn x=>2*x, "double"); + (* giving a name to the function x=>2*x *) + val it = NAMED_FUNCTION (fn,"double") : (int,int) named_function + - + +Finally, let me notice that SML also allows to define enumeration +types, like the following one:: + + - datatype color = RED|GREEN|BLUE; + datatype color = BLUE | GREEN | RED + - + +but for +enumeration types the name is rather improper, since they are just values:: + + - RED; + val it : color = RED + - + +.. + + - GREEN; + val it : color = GREEN + - +.. + + - BLUE; + val it : color = BLUE + - + +Polymorphism +------------------- + + - functor Sequence(ListOrVector:) = funct + end + + +Two examples of simple structures in the standard library as List and Vector; +they act as modules with a mostly compatible interface, providing functions +to manipulate lists and vectors respectively. Since SML is a functional language, +both lists and vectors are immutable. Here are a few examples of usage:: + + - val lst = [0,1,2]; + val lst : int list = [0, 1, 2] + - val vec = #[0,1,2]; + val vec : int vector = #[0, 1, 2] + + - List.length(lst); + val it : int = 3 + - Vector.length(vec); + val it : int = 3 + + -List.sub(lst, 1) (*) take the second element of the list + val it : int = 1 + -Vector.sub(vec, 1) (*) take the second element of the vector + val it : int = 1 + +signature HAS_LENGTH = sig + val length: +functor(F:HAS_LENGTH): + +polyLength(List) + + +Lists +--------------------------------------------------- + +Lists are the most common data structures in functional languages (they formed +the core of Lisp at its beginning) and there are many facilities to manage them and +to iterate over them. For instance, in the first article of this series, I showed the +``app`` builtin, to apply a function over the elements of a list; there is also +a ``map`` builtin to build a new list from an old one:: + + - val doubles = map (fn x => 2*x) [1,2,3]; + val doubles : int list = [2, 4, 6] + +and a ``filter`` function to extract the sublist of elements satisfying a given predicate:: + + - fun isEven n = n mod 2 = 0; + - val even = List.filter isEven [1,2,3]; + val even : int list = [2] + +Moreover, you can append lists with the ``@`` operator:: + + - [1] @ [2] @ [3,4]; + val it : int list = [1, 2, 3, 4] + +There are other standard facilities and you can look at the documentation +http://www.standardml.org/Basis/list.html to find them all. + +ML lists are linked lists in the same sense of Lisp or Scheme [#]_, however they +are immutable. Just as in Scheme [#]_, where + + ``(list 1 2)`` + +is a shortcut for + + ``(cons 1 (cons 2 '()))`` + +in ML + + ``[1, 2]`` + +is a shortcut for + + ``1::2::[]`` + +and ``::`` is the *cons* operator (short for constructor). + + +.. [#] Python lists are actually arrays, not lists + +.. [#] If you don't know Scheme, + + [1, 2] (ML) => [1,[2,[]]] (Python) + + + +It is also possible to apply a binary operator to a list, via the ``foldl`` and ``foldr`` +functions:: + + - val sum = foldl op+ 0 [1,2,3]; + val sum : int = 6 + + +fun enum n = lazy n :: enum (n+1) + +(for instance, a log file); how can we process it? The simplest possibility is +to convert it into a lazy list of lines, with the following code:: + + fun textFileToList filename = let + val file = TextIO.openIn filename + fun lazy readLines () = + case TextIO.inputLine file + handle ex => (TextIO.closeIn file; raise ex) + of NONE => (TextIO.closeIn file; []) + | SOME line => line :: readLines () + in + readLines () + end + + +A smarter line counter +----------------------------------------------------------- + + - val countLines = length o textFileToList + + + + +Python extended generators in Alice +--------------------------------------------------------- + +def consumer(): + result = [] + while True: + inp = yield + if inp = END: + return result + +functor consumer(val ch: channel): + while true do let + val inp = Channel.gexst ch + if inp = END: + + +In particular, what's the equivalent of the simple +Python one-liner ``for item in container: print item``? The answer is +that there is no equivalent. The Python one-liner is completely +polymorphic, since it works for any kind of container and it is able +to print any kind of object. However, SML is a statically typed +language, and you must give to the compiler some information about the +types: it cannot predict the future, nor the type of the container and +the types of the items. Here I will show how you can loop on +homogenous containers, i.e . containers where the items are all +instances of the same type; + + + fun writeln (tostring, value) = + print(format (tostring o text "\n") value); + + +- app (fn item => writeln(int, item)) [1,2,3]; +1 +2 +3 + +fun sum lst = foldl op+ 0 lst + +The thing to remember is that functions associate to the **right**, so + + ``a -> b -> c`` + +means + + ``a -> (b -> c)`` + +whereas type constructor associates to the **left**, so + + ``int ref list`` + +means + + ``(int ref) list`` + + +Timers +-------------------- + +fun timedFn f a = let + val ct = Timer.startRealTimer () + val result = f a +in + (result, Timer.checkRealTimer (ct)) +end + + + - structure A = struct + type 'a aggr + fun length(a: 'a aggr) = 1 + fun sub(a: 'a aggr, i :int) = 1 + end; + signature AGGREGATE_TYPE = + sig + type 'a aggr + val length : 'a aggr -> int + val sub : 'a aggr * int -> int + end + - + +ok + +"Hello World", part II +------------------------------------------------- + +On the other hand, print2 cannot accept anything different than strings:: + + - print2("the answer is ", 42); + 1.0-1.28: mismatch on application: expression type + string * int + does not match function's argument type + string * string + because type + int + does not unify with + string + +To print a string and a number, we should define another function:: + + - fun print_string_int(s, i)=(print s; print (Int.toString(i))); + val print_string_int : string * int -> unit = _fn + - print_string_int("The answer is ", 42); + The answer is 42val it : unit = () + +This is clearly unpractical, since we can't define an infinite number +of ``print`` functions to be able to print all the potentially +infinite types we can have in our programs. Fortunately there are +utility library for converting objects in strings (even if not +standard :-(). SML/NJ has the library FormatComb, to be used as +follows (from now on, for concision sake, I will omit to write down +the full output of the REPL):: + + - open FormatComb; + - print (format (text "The value is " o int o text ".") 42); + The value is 42 + +The syntax looks strange (especially the "o" operator) and if you +forget the parenthesis you will get funny error messages. To +understand what is going on, you will need to master SML to a much +more advanced level than this first paper is meant to, so you will +have to wait for "Hello World, Part II" ;) Here I will content myself +to notice that the format utility requires you to specify the types of +the arguments. SML is a *typeful* language: the compiler must know the +types of all your variables *before running the program*. In a +dynamic language, instead, the types can be introspected at runtime +and there is no need to specify them. This is most of the times an +advantage, however, it also leads to type errors that cannot occur in +a statically typed language such as SML (also called *type safe* +languages). Moreover, SML compilers may perform optimization which are +impossible in a dynamic language (but this is of lesser relevance, +since any user of dynamic languages will use C libraries if speed is +an issue). + +---- + + diff --git a/ml/article5.txt b/ml/article5.txt new file mode 100644 index 0000000..24719d3 --- /dev/null +++ b/ml/article5.txt @@ -0,0 +1,44 @@ +Records +----------------------- + +The first level of grouping in ML is the record level: a record +roughly correspond to a *struct* in the C programming language and can +contain any kind of first class value. The record itself a first class value, +i.e. it can be passed to and returned from functions. I will give a few +examples of usage. + +A record factory:: + + - fun makeArticle {title:string, author:string} = {title, author}; + val makeArticle : {author : string, title : string} -> {author : string, title : string} = _fn + +An example record:: + + - val article = makeArticle {title="Functional Programming", author="M. Simionato"}; + val article : {author : string, title : string} = + {author = "M. Simionato", title = "Functional Programming"} + +Extracting fields from a record:: + + - #title article; + val it : string = "Functional Programming" + - #author article; + val it : string = "M. Simionato" + +A record containing functions:: + + - val a = {makeArticle, printArticle = fn {title, author} => print (title^" "^author ^"\n") }; + - #printArticle a article; + Functional Programming M. Simionato + +Notice that the order of the fields is not specified and that there is no +concept of subrecord; for instance two records of kind +``{title:string, author:string, publicationDate:string}`` and +``{title:string, author:string}`` +are considered completely different record types, the second one is not a subtype of +the first one and you cannot substitute one with the other, possibly with default +values. Also, records are purely static and resolved at compile time: if you need +something more dynamic, you should use a map, not a record. Finally, records +are functional, i.e. immutable: there is no way to change the value of a field, +you must create an entirely new record with a different value if you want to +simulate a record update. |