summaryrefslogtreecommitdiff
path: root/ml
diff options
context:
space:
mode:
authormichele.simionato <devnull@localhost>2007-12-04 07:08:22 +0000
committermichele.simionato <devnull@localhost>2007-12-04 07:08:22 +0000
commit3589b2a479ddc727a6a8a8c55927e8da7669a14d (patch)
tree2e66a9b95c06dcb429f4f5287bd46ec4e9c81f1f /ml
parentb672466c102677799fcaf10cc98580c5537735c1 (diff)
downloadmicheles-3589b2a479ddc727a6a8a8c55927e8da7669a14d.tar.gz
Some additions
Diffstat (limited to 'ml')
-rw-r--r--ml/article1.txt145
-rw-r--r--ml/article2.txt165
-rw-r--r--ml/article3.txt172
-rw-r--r--ml/article4.txt291
-rw-r--r--ml/three-lines.txt3
5 files changed, 436 insertions, 340 deletions
diff --git a/ml/article1.txt b/ml/article1.txt
index 5b28c23..0bcdc61 100644
--- a/ml/article1.txt
+++ b/ml/article1.txt
@@ -16,15 +16,16 @@ 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
+One may rightly wonder why to study functional languages, and in
+particular statically-typed ones such as SML and Haskell: after all,
+their relevance in enterprise programming is negligible, they are practically
+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,
+A 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.
@@ -42,13 +43,13 @@ 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
-[#].
+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
+advantages of a language count very little compared to things 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
@@ -59,10 +60,10 @@ 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)
+have a simpler 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.
+here is to popularize it, trying to get some of SML ideas into Python frameworks.
Micro-introduction to functional languages
------------------------------------------------------------
@@ -73,7 +74,7 @@ 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,
+programming, but are not considered functional (for instance, 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
@@ -86,14 +87,23 @@ 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
+typed (Lisp, Scheme, Erlang ...) and statically typed (SML, OCaml, 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
+start with it.
+
+In the following, I will often contrast the functional way with the imperative
+way, and I will mostly use Python as an example of imperative language,
+because it is a language that I know decently well, and because it is
+so readable that the examples will be easy to understand even for
+programmers not familiar with Python.
+
+Getting an SML compiler
+--------------------------------------------------------
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
@@ -126,17 +136,12 @@ Now you can write your first mandatory "Hello world" program::
Hello world
val it : unit = ()
-and you are ready to start ;) If you are an old timer Emacs user, you
+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
@@ -152,7 +157,12 @@ has a custom version of it, that you can download from here_ ).
user-experience is the same as if you had an
interpreter. This approach is commons in ML and Lisp implementations.
-"Hello world", Part I
+It may be useful sometime to contrast Alice with more traditional ML
+implementations (especially to see if the error messages are more
+understandable with another compiler) so you want to run
+``apt-get install smlnj mlton`` as well.
+
+Hello world
---------------------------------------
In the previous section I have shown how the traditional "Hello world"
@@ -260,101 +270,6 @@ advanced for this introduction. But stay tuned for the next issues ;)
.. [#] 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*
diff --git a/ml/article2.txt b/ml/article2.txt
index 400b364..a058551 100644
--- a/ml/article2.txt
+++ b/ml/article2.txt
@@ -2,7 +2,7 @@ Functional Programming For Dynamic Programmers - Part 2
=======================================================
:author: Michele Simionato
-:date: 10 November 2007
+:date: December 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.
@@ -13,7 +13,107 @@ 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
+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,
+I will address those situations in the next issues. In order to do so,
+however, I need to perform a digression on two of the most common
+programming techniques in functional languages, recursion and higher order
+functions. You, as a dynamic programmer, will probably already know
+both concepts, but in languages such are Python, Perl, or even Common Lisp
+they are not as pervasive as in truly functional languages.
+
+Recursion
----------------------------------------------------
Perhaps the most common construct in imperative programming is the *for* loop;
@@ -256,7 +356,7 @@ 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
+to define equivalent higher order functions 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
@@ -264,61 +364,8 @@ 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
+*Debugging is twice as hard as writing the code in the first
+place. Therefore, if you write the code as cleverly as possible, you
+are, by definition, not smart enough to debug it.* -- Brian W. Kernighan
diff --git a/ml/article3.txt b/ml/article3.txt
index 3991f89..d6887d1 100644
--- a/ml/article3.txt
+++ b/ml/article3.txt
@@ -2,7 +2,7 @@ Functional Programming For Dynamic Programmers - Part 3
=======================================================
:author: Michele Simionato
-:date: November 2007
+:date: December 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.
@@ -13,7 +13,7 @@ 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
+How to write your first module
------------------------------------------------------
Usually programs start small, but they have an unfortunate tendency to
@@ -114,7 +114,7 @@ With this definitions, we get the same behavior as in Python [#]_ ::
http://www.standardml.org/Basis/string.html and here
http://www.standardml.org/Basis/char.html
-.. [#] In Python we would write
+.. [#] In Python we would have
::
@@ -129,7 +129,10 @@ With this definitions, we get the same behavior as in Python [#]_ ::
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
StopIteration
-
+
+Importing modules and components
+----------------------------------------------------
+
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
@@ -156,11 +159,13 @@ 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::
+this is however actively discouraged, to prevent namespace pollution,
+i.e. name conflicts, since an exported name can shadow a pre-existent
+name. There is actually 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.
+For instance, we could supplement the ``Iter`` structure with
+a routine to read binary files in chunks::
- structure Iter = struct
open Iter
@@ -168,7 +173,7 @@ them with additional objects as in this example::
val inp = BinIO.openIn fname
in
fn () => let
- val vec = BinIO.inputN (inp, chunksize)
+ val vec = BinIO.inputN (inp, chunksize) (* read N bytes *)
in
if Word8Vector.length(vec) = 0 then raise StopIteration else vec
handle err => (BinIO.closeIn inp; raise err)
@@ -176,8 +181,7 @@ them with additional objects as in this example::
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
+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::
@@ -196,7 +200,7 @@ and should be done with care.
Structures are not first class values
-----------------------------------------------------------
-The problem with structures is that they are not first class values, so they cannot
+The problem with structures is that they are not first class values, i.e. 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::
@@ -204,32 +208,37 @@ the prompt the name of a structure does not work::
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`` [#]_
-
-::
+in a completely separate namespace [#]_, so that you can associate any value
+to the name ``TextIO`` and still you can access the contents of the structure
+without any problem::
- val TextIO = "astring";
+ 1.4-1.10: warning: value identifier `TextIO' violates standard naming conventions,
+ which suggest value names of the form foo, foo', fooBar
val TextIO : string = "astring"
-and still you can access the contents of the structure without any problem::
-
- TextIO.print;
val it : string -> unit = _lazy
+Notice the 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.
+
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
+for instance, if you want to give a shorter alias to ``TextIO`` you can define
- ``- structure T=TextIO;``
+ ``- 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;``
+ ``- 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
+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``
@@ -241,37 +250,74 @@ 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
+.. [#] Readers familiar with Common Lisp will be familiar with the concept
+ of having separate namespecies for different kind of objects.
+
+Defining signatures
--------------------------------------------
-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``;
+Strictly speaking, it is not necessary to define signatures for your own
+structures, since even if you do not specify a signature, the
+compiler is able to figure out the complete signature,
+thanks to type inference. The problem is exactly that: the compiler
+extracts the *complete* signature, a.k.a. the *principal* signature
+of you library, which is too much. Typically, you don't
+want to expose all the objects in your structure: for instance, you
+may have private utility functions that clients of your library should
+not see. This is a typical use case for writing custom signatures:
+if you want to implement `information hiding`_ you can just provide a
+signature not exposing your private objects.
+Another typical use case the `facade pattern`_, which is used when you
+want to provide a simple interface to complex library. For instance,
+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;
+but still they share many features and it is possibile to define subsignature(s)
+matching both.
+In particular, it is possible to define a simplified interface (a *facade*) which is
+common to both 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;
+
+To an application compatible with the SIMPLE_IO signature, both ``TextIO``
+and ``BinIO`` will look the same: giving an explicit signature
+enhances genericity, modularity and code reuse. This is expecially true
+if you use *opaque importing*, i.e. the ``:>`` syntax exemplified below::
+
+ - structure T=TextIO:>SIMPLE_IO;
+ structure T : SIMPLE_IO
-and you could import only a subset of the features provided by ``TextIO``
-and ``BinIO`` as follows::
+ - structure B=BinIO:>SIMPLE_IO;
+ structure B : SIMPLE_IO
- structure T = TextIO:SIMPLE_IO;
+ - val f = B.openIn "three-lines.txt";
+ val f : B.instream = _val
+ - val x = Byte.bytesToString (B.inputAll f);
+ 1.8-1.41: mismatch on application: expression type
+ B.vector
+ does not match function's argument type
+ t
+ because type
+ B.vector
+ does not unify with
+ Word8Vector.vector
+ -
+
+Transparent signatures
+---------------------------------
+
+and you could import only a subset of the features provided by as follows::
+
+ - structure T = TextIO:SIMPLE_IO;
structure T :
sig
type vector = vector
@@ -285,6 +331,7 @@ and ``BinIO`` as follows::
val closeOut : TextIO.outstream -> unit
end = TextIO
+::
structure B = BinIO:SIMPLE_IO;
structure B :
@@ -306,39 +353,20 @@ and ``BinIO`` as follows::
- 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
+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
+.. _information hiding: http://en.wikipedia.org/wiki/Information_hiding
+.. _facade pattern: http://en.wikipedia.org/wiki/Facade_pattern
----
diff --git a/ml/article4.txt b/ml/article4.txt
index 6c42a18..71bb24b 100644
--- a/ml/article4.txt
+++ b/ml/article4.txt
@@ -13,90 +13,62 @@ 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::
+SML has a sophisticated type which is, however, completely different from
+the type system of dynamic languages. Here I am assuming that you, the
+dynamic programmer, are familiar with the system of languages such as
+Python and Ruby, or Common Lisp and Smalltalk.
+In such languages you here often the mantra *everything is an
+object* meaning that everything is a first class value which can created and
+inspected at runtime. Moreover, any object has a class, and classes themselves
+are objects, i.e. they are instances of metaclasses. In SML things are completely
+different. There a lots of things which are not first class values (structures,
+exceptions, signatures, ...) and even types are such: in SML any value has a type,
+but types itself are not values, i.e. they are not 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
+SML types are not classes in any OO sense of the term,
+you cannot introspect them, there are no methods, no inheritance, and
+they live in a separated namespace.
+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, but you don't use types for OOP, types are used
+internally by the compiler, but from the user's point of view they are just
+labels attached to values.
+
+The type system of SML is extensible, and the user can define new types (i.e.
+new labels) in terms of primitive types. The labels are just that; but together
+with a new label, the definition of an user defined type includes the definition
+of one or more functions, the *constructors* of the type, which are first class
+values and extremely useful for pattern matching.
+Let me give a concrete example: an
+``int_or_string`` datatype with two constructors ``INT`` and ``STR``, defined
+as follows::
+
+ - datatype int_or_string = INT of int | STR of string;
+ datatype int_or_string = INT of int | STR of string
+
+This definitions tells the compiler that the label ``int_or_string`` has to
+be used when showing the signature of objects of the newly defined types;
+but the important things are the constructors, which are able to convert (or *cast*)
+primitive values to values of the new type::
+
+ - val v1 = INT 1;
+ val v1 : int_or_string = INT 1
+ - val v2 = STR "1";
+ val v2 : int_or_string = STR "1"
+
+The constructors are essential since you can use them in *pattern matching*.
+For instance, suppose you want to define an utility function casting integer to strings.
+You can do so as follows::
- fun valueToString (INT x) = Int.toString x
- | valueToString (STR x) = x;
+ | valueToString (STR x) = x;
val valueToString : int_or_string -> string = _fn
Let me check that it works:
@@ -106,9 +78,54 @@ Let me check that it works:
- valueToString (STR "1");
val it : string = "1"
-With this trick it is possible to emulate C++-style function overloading.
+In essence, we have just implemented runtime type dispatching: ``valueToString``
+is now emulating a C++ overloaded function (or a Lisp generic function) which
+accepts both integers and strings and behaves differently according to the type.
+This in spirit, but technically ``valueToString`` is just an ordinary SML function
+which takes in input the ``int_or_string`` type, so we are somewhat cheating,
+but it works ;)
+Consider for instance the issue of defining heterogeneous collections, i.e.
+collections containing different types; in SML we cannot define a list
+
+- [1, "two"];
+ 1.0-1.2: ill-typed constructor argument:
+ int * string list
+ does not match argument type
+ int * int list
+ because type
+ string
+ does not unify with
+ int
+
+but we can define
+
+- [INT 1, STR "two"];
+val it : int_or_string list = [INT 1, STR "two"]
-In particular he may define composite types, like the following::
+As Harper puts it, *heterogenity is a special case of homogenity*.
+
+It is also possible to define polymorphic types, where the constructor(s) can
+accept any type as argument. For instance, we can define a polymorphic
+(parametric) box type::
+
+ - datatype 'a box = Box of 'a;
+ datatype 'a box = Box of 'a
+
+ - Box
+ val it : 'a -> 'a box = _fn
+
+``box`` is a parametric type with constructor ``Box`` and parameter 'a
+(to be read *alpha*), which corresponds to a generic type, so that you
+can define
+
+ - Box(1);
+ val it : int box = Box 1
+ - Box("hello");
+ val it : string box = Box "hello"
+ - Box(fn x => 2*x);
+ val it : (int -> int) box = Box (_fn)
+
+In particular we may define composite types, like the following::
- datatype string_int = STRING_INT of string * int;
datatype string_int = STRING_INT of string * int
@@ -121,28 +138,11 @@ be used to make concrete instances of the type::
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
@@ -229,6 +229,51 @@ functor(F:HAS_LENGTH):
polyLength(List)
+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")``
+
Lists
---------------------------------------------------
@@ -448,6 +493,64 @@ 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).
+
+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/three-lines.txt b/ml/three-lines.txt
new file mode 100644
index 0000000..d26ed6c
--- /dev/null
+++ b/ml/three-lines.txt
@@ -0,0 +1,3 @@
+ line 1
+ line 2
+ line 3