diff options
author | michele.simionato <devnull@localhost> | 2007-12-09 15:58:39 +0000 |
---|---|---|
committer | michele.simionato <devnull@localhost> | 2007-12-09 15:58:39 +0000 |
commit | 9b1b7aa5a6b762b1110391c79241a1b8dccbfa83 (patch) | |
tree | 42dd0820d6699d9ffb47519269c80f806c563633 /ml | |
parent | c16096239a55e74292a32fe16c834797f6854edd (diff) | |
download | micheles-9b1b7aa5a6b762b1110391c79241a1b8dccbfa83.tar.gz |
Various enhancements
Diffstat (limited to 'ml')
-rw-r--r-- | ml/format.aml | 40 | ||||
-rw-r--r-- | ml/intro.txt | 50 | ||||
-rw-r--r-- | ml/loops.txt | 2 | ||||
-rw-r--r-- | ml/modules.txt | 8 | ||||
-rw-r--r-- | ml/objects.txt | 119 | ||||
-rw-r--r-- | ml/text.txt | 319 | ||||
-rw-r--r-- | ml/types.txt | 163 |
7 files changed, 423 insertions, 278 deletions
diff --git a/ml/format.aml b/ml/format.aml new file mode 100644 index 0000000..1c6427a --- /dev/null +++ b/ml/format.aml @@ -0,0 +1,40 @@ +(* +A library implementing a very simple string interpolation facility. +It exports a single higher order function 'format' converting a template +string into a 'string list -> string' function replacing the template +with the provided list of arguments. An example of usage is + +do print(format "The square of $ is $\n" ["3", "9"]) +*) + +signature FORMAT = sig + val format : string -> string list -> string +end + +structure Format = struct + exception ArityError of string + + fun checkArity(templN1, argsN) = let + val n1 = Int.toString (length templN1 - 1) + val n = Int.toString (length argsN) + in + if n1=n then () else raise ArityError("Expected "^n1^" arguments, got"^n) + end + + val rec interp' = + fn (t :: [], [], acc) => concat(rev (t :: acc)) + | (t :: templ, a :: args, acc) => interp'(templ, args, t :: a :: acc) + | _ => raise ArityError "This should never happen" + + and interp'' = + fn (templN1, argsN) => ( + checkArity (templN1, argsN); interp' (templN1, argsN, [])) + + and format = + fn templ => let + val templN1 = String.fields (fn c => c = #"$") templ + in + fn args => interp'' (templN1, args) + end + +end: FORMAT diff --git a/ml/intro.txt b/ml/intro.txt index 0bcdc61..803bdf6 100644 --- a/ml/intro.txt +++ b/ml/intro.txt @@ -68,33 +68,33 @@ here is to popularize it, trying to get some of SML ideas into 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; +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 (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 -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, 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. +programming, but are not considered functional (Python being an +example); 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, 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. 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, diff --git a/ml/loops.txt b/ml/loops.txt index a058551..a3c2144 100644 --- a/ml/loops.txt +++ b/ml/loops.txt @@ -113,7 +113,7 @@ 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 +Loops and recursion ---------------------------------------------------- Perhaps the most common construct in imperative programming is the *for* loop; diff --git a/ml/modules.txt b/ml/modules.txt index 43a5664..0775bbe 100644 --- a/ml/modules.txt +++ b/ml/modules.txt @@ -81,7 +81,7 @@ converting it into a function ``iterator -> unit``, whereas 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));`` + ``- fun upper str = String.map Char.toUpper str;`` to each line; we can test it by writing a file like the following:: @@ -276,7 +276,7 @@ defines the type ``list``, whereas the ``Vector`` structure defines the type ``vector``; both types have a common alias ``t``. ``list`` and ``vector`` are container types, in the sense that a list/vector may contain objects of any type, as long as all the elements -are homegenous, so you may have a ``string list``, an ``int list``, +are homogenous, so you may have a ``string list``, an ``int list``, a ``string list list``, etc [#]_:: - val lst = [0,1,2]; @@ -371,7 +371,8 @@ interfaces. Code written to work with a ``SIMPLE_SEQUENCE`` interface will automatically work with lists, vectors, and any data structure (builtin *and* custom) satisfying the interface. -.. [#] Notice that since SML is a functional language, both lists and vectors are immutable. +.. [#] Notice that since SML is a functional language, both lists and + vectors are immutable. .. _information hiding: http://en.wikipedia.org/wiki/Information_hiding .. _facade pattern: http://en.wikipedia.org/wiki/Facade_pattern @@ -381,3 +382,4 @@ will automatically work with lists, vectors, and any data structure *divide et impera* -- old Roman saying + diff --git a/ml/objects.txt b/ml/objects.txt index 9458cea..4cb4a13 100644 --- a/ml/objects.txt +++ b/ml/objects.txt @@ -1,10 +1,10 @@ -Functional Programming For Dynamic Programmers - Part 5 +Functional Programming For Dynamic Programmers - Part 6 ======================================================================= :author: Michele Simionato :date: December 2007 -This is the fifth of a series of articles on functional programming in +This is the sixth 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 @@ -59,67 +59,6 @@ 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. -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 @@ -223,60 +162,6 @@ end 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). Higher order functions can also be used to implement poor man's object diff --git a/ml/text.txt b/ml/text.txt index c53cc55..63a0874 100644 --- a/ml/text.txt +++ b/ml/text.txt @@ -1,5 +1,322 @@ -Text processing in SML +Functional Programming For Dynamic Programmers - Part 4 +======================================================= + +:author: Michele Simionato +:date: December 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. + +Academic languages vs enterprise languages +----------------------------------------------------------- + +There is a widespread misconception that academic languages are clean but +unpractical, whereas enterprise languages are practical but dirty. I never +bought that argument, since I have seen examples of the opposite, and also +because I always believed that a language can be both clean *and* practical. + +For instance in my opinion both Python and Ruby are reasonably clean and +reasonably practical and this is part of the reason why they are having so +much success lately. I believe the reason why they are so clean is that from +the beginning they managed to stay away from optimization, the root of all evil. +On the other hand, I am also convinced that it possible to write a language +which is both practical, clean and fast. Unfortunately, such a language does +not exist yet. SML is fast and somewhat clean but it is definitely not practical. +Here by practical I mean enterprise-ready. + +There is an irriducible aporia_ between enterprise programmers and +academics: people working on enterprise are facing every day problems +that are already solved; to them, the importanting thing is to avoid +reinventing the wheel. Given two languages, one that provides a solution +to a problem they have, and one providing the tools to build the solution +themselves, they prefer the first one. This is natural since in an enterprise +environment one has deadlines and it is essential to be as productive +as possible; of course the best way to be productive is to *not write* code, +and to re-use code written by others. +On the other hand, in academia one has to do with new problems, or +with new techniques to solve old problems: the proof of concept is +more important than the practical implementation. Also, academics +are (rightly) interested in writing papers, not in writing code. For all these +reasons it is clear that you cannot face an +academic language such as SML with an enterprise mindset. +For instance, if you come from an enterprise environment you will be used to +expect ready availabity of a nearly infinite number +of libraries doing everything you may need and more. This is certainly +the situation for all enterprise-level languages such as C, C++, Java, C#, +Perl, Python and now even Ruby, which is younger but rapidly spreading. + +As I said many time, I assume that my readers are dynamic programmers, +well acquainted with scripting languages; I feel the urge warn them that the +situation in SML is very different than in Perl, Python or Ruby [#]_ . Here +are a few differences. + +1. + Scripting languages have a dominant (sometimes unique) + implementation; ML has dozens of implementations, each with + useful features which are not in the standard, so that + different implementations provides incompatible extensions. + +2. + Scripting languages have a BDFL_ (Benevolent Dictator For Life, + i.e. Guido van Rossum for Python, Larry Wall for Perl, Yukihiro + Matsumoto for Ruby) whereas SML is a language designed by + committee. That means that a scripting language can evolve much + *much* faster than a language designed by committee. Also, + languages designed by committee are compromises whereas + single-person languages are much more consistent. + +3. + SML has a very small community: the support you can have in newsgroup + and mailing lists is simply not comparable to the support you can get in + the scripting languages mailing list and newsgroups. + + if Andreas Rossberg won the first price + at the lottery and decided to leave for a tropical paradise, nobody would + provide support for Alice ML on the mailing list + +4. + SML is a deserted language, even compared to non-mainstream languages + as OCaml, Haskell, Scheme, Common Lisp, Smalltalk, Tcl, ...; it is perhaps the + least used language I have ever seen and this is a pity. + +5. + Generally speaking, if you want bindings for external libraries you have to write + it yourself; there are no standard bindings for GUI toolkits, database drivers, + scientific programming libraries. + +6. + Scripting languages have traditionally a strong support for Web programming + which is next to absent in SML. + + +All the issues I have just listed are just accidental, not +structural: in principle one could just solve them by writing code; in principle +a DHH_ could take an SML implementation and make it the next Ruby on Rails. +However that has not happened yet, so if you want to work with SML right now you +will be forced to reinvent the wheel. In this article I will reinvent a few wheels, +just to be able to give some meaninful example to the enterprise programmer +in the next articles. + +.. _aporia: http://en.wikipedia.org/wiki/Aporia +.. _BDFL: http://en.wikipedia.org/wiki/BDFL +.. _DHH: http://en.wikipedia.org/wiki/DHH + +.. [#] Notice that I put Perl, Python and Ruby in the mainstream + languages, since even if the number of dynamic programmers is + inferior the number of say C/C++, Java or .NET programmers, + in practice scripting programmers have available nearly everything the + other languages have available, and much better languages. + +String interpolation the practical way -------------------------------------------------------- +Every language has some builtin mechanism to do string interpolation: C +has ``sprintf``, Python has the ``%`` operator, Lisp has ``format``, but +SML has none. Therefore I will provide here a very simple interpolation library +for SML, so that I will be able to provide more interesting examples later. +The library is based on list processing. +Lists are the most common data structures in +functional languages (they formed the core of Lisp at its beginning) +and you simply cannot live without them. You should look at the `basis library`_ +to see what you can do with lists. Here I will just say that SML lists are +linked lists in the same sense of Lisp or Scheme (with the difference +that they are immutable) and not as in Python (Python lists are actually arrays, +Python is abusing the name). 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). +If you don't know Scheme, a SML lists should be thought of as of a +nested Python list, i.e. + + ``[1, 2] (SML) => [1, [2, []]] (Python)`` + +.. _basis library: http://www.standardml.org/Basis/list.html + +Here is the code:: + + $ cat format.aml + .. include:: format.aml :literal: + +A few comments are in order. + +1. + We used the ability to define exceptions with a value: + ``exception ArityError of string`` means that ``ArityError`` accepts + a string argument (the error message). + +2. + We used the standard library ``String.fields`` utility, which splits a string + according to a delimiter; in our case ``String.fields (fn c => c = #"$") templ`` + splits the template into a list a strings using the character ``$`` as delimiter. + In SML characters are specified with a sharp notation, so the character ``$`` + is written ``#"$"``. + +3. + At the core of the algorithm is the recursive function + ``interp'(templN1, argsN)`` which takes a list of N+1 strings (the + splitted template) and a list of N strings (the arguments) and returns the + interpolated string. For instance, for the template ``The square of $ is $\n`` + which contains N=2 delimiters, + ``templN1`` would be ``["The square of ", " is ", "\n"]``. + +4. + The builtin ``concat`` concatenates a list of strings whereas ``rev`` reverts + a list; you are advised to read the documentation or any book on SML for more. + +String interpolation the mathematical way +------------------------------------------------- + +Functional languages have a reputation for abstractness +but in this series of articles I have focused solely on very concrete +earth-bound aspects. To correct the balance, in this section I will discuss +a few programming techniques which are quite common in +functional languages, but unheard of in "regular" languages, and that +require a mathematically-inclined mind to be understood. I am doing so +because these tricks are actually used a lot in functional languages, and +I don't want to give a false impression by ignoring them +and just discussing the easy things. + +For instance, you should not believe that functional +programming is just about functions; there is an higher order +approach to functional programming, in which the primary objects +are not functions, but operators operating on functions, the so +called *combinators*. For people with a background in Physics, I will +say this is exactly analogous to the switch from the +Schroedinger picture, in which the emphasis is on the wave functions, +to the Heisenberg picture, in which the emphasis is on the quantum +operators. + +Having warned my readers that this section is not for the faint of heart +(but you may safely skip it if you don't feel adventurous) +I will start from the concatenation operator + +:: + + ^ : string * string -> string + +which satifies the properties:: + + a ^ (b ^ c) = (a ^ b) ^ c = a ^ b ^ c + a ^ "" = "" ^ a = a + +Mathematically speaking, the set of strings in SML +is a monoid_ with respect to concatenation and the empty +string "" is the identity element. + +.. _monoid: http://en.wikipedia.org/wiki/Monoid +.. _group: http://en.wikipedia.org/wiki/Group_%28mathematics%29 +.. _group representations: http://en.wikipedia.org/wiki/Group_representation + +If you have a background in Mathematics or in Physics you will be familiar +with the theory of `group representations`_; in particular groups (and monoids +too) can be represented with operators operating on function spaces. +In the case at hand, I can define a lift transformation converting plain +strings into operators by preserving the composition law:: + + - fun L s f s' = f (s' ^ s); + val L : string -> (string -> 'a) -> string -> 'a = _fn + +(this is the same as ``fun L s = fn f => fn s' => f (s' ^ s)``). +In other words, ``L s`` is an operator (combinator) taking a function and +returning a function, with the property + + ``L (s1 ^ s2) = (L s1) o (L s2)`` + +where ``o`` denotes the composition of operators. Just as ``L`` is an upgrade +operation, promoving a plain simple string to the celestial world of operators +in function spaces, I can define a downgrade operation ``l``, demoving +celestial operators back to earthly strings:: + + - fun l O = O Fn. id ""; + val l : (('a -> 'a) -> string -> 'b) -> 'b = _fn + +``l`` takes the operator, applies it to the identity function and returns a function +which is then applied to the empty string, finally bringing back at home a +plain string; ``l`` is the inverse of ``L``, i.e. ``l o L`` is the identity +in the space of strings where ``L o l`` is the identity in the space +of operators. You can try yourself at the prompt that + +:: + + - l(L"a" o L"b"); + val it : string = "ab" + +so we succeded in making simple things hard and abstract. But not happy with +that, we are going to make things harder and more abstract, by defining +another combinator taking a function and returning an higher order function:: + + - fun str f s s' = f (s ^ s') + val str : (string -> 'a) -> string -> string -> 'a = _fn + +(this is the same as ``fun str f = fn s => fn s' => f ( s ^ s')``). +This combinator is so abstract than even when lift back to the mortal world +it is more than a mere string, it is actually the identity function on strings:: + + - l str; + val it : string -> string = _fn + - (l str) "hello"; + val it : string = "hello" + +The ``str`` combinator can be composed in the celestial word: when lift +back to the mortal world, it returns a higher order version of the concatenation +operator:: + + - l (str o str); + val it : string -> string -> string = _fn + + - l (str o str) "hello" " world"; + val it : string = "hello world" + +We can also compose ``str`` with other combinators and we can write things like:: + + - l( L"a" o str o L"b" o str o L"c") "?" ":"; + val it : string = "a?b:c" + +In other words, we have reinvented string interpolation the difficult way. +Still, there is at least an advantage of this approach with respect to the +approach we used in the previous paragraph: combinator-based string interpolation +happens at compile type and *mistakes are discovered by the compiler*, not +at runtime. Moreover, the approach can be easily extended to manage +different types: for instance, if we have to do with numbers, we +can define the combinators:: + + - fun int f s n = f (s ^ (Int.toString n)) + val str : (string -> 'a) -> string -> string -> 'a = _fn + + - fun real f s n = f (s ^ (Real.toString n)) + val real : (string -> 'a) -> string -> real -> 'a = _fn + +and use them as follows:: + + - print (l(int o L" / " o int o L" = " o real o L"\n") 5 2 2.5); + 5 / 2 = 2.5 + val it : unit = () + +If you make a mistake like using an int instead of a real, or if you forget an +argument, you will get a compile time type error. + +---- + +*Give a man a fish and he will eat for a day. Teach a man to fish and he will eat +for the rest of his life.* -- Chinese proverb diff --git a/ml/types.txt b/ml/types.txt index 98c65fc..e07798b 100644 --- a/ml/types.txt +++ b/ml/types.txt @@ -1,10 +1,10 @@ -Functional Programming For Dynamic Programmers - Part 4 +Functional Programming For Dynamic Programmers - Part 5 ======================================================= :author: Michele Simionato :date: December 2007 -This is the fourth of a series of articles on functional programming in +This is the fifth 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 @@ -260,154 +260,55 @@ This approach to polymorphism works for simple things, but it is not practical, nor extensible: this is the reason why SML provides an industrial strenght mechanism to support polymorphism, *functors*. -Functors ------------------------------------------------------- +Input and Output revisited +-------------------------------------------------------------- + +In accordance with the example-oriented spirit of this series, I will +introduce functors with a motivating example, again in the arena of +input and output. +We saw in an earlier installament that the standard library provides two +structures ``TextIO`` and ``BinIO`` for managing text files and binary +files respectively; we also show that the two structures have many things +in common, and it is possibile to define a (sub)signature matching both. -As we saw in the previous article, structures are not first class objects and + +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 Sequence(s:SIMPLE_SEQUENCE) = funct - type t = s.t - length = - end; - - -Input and Output revisited --------------------------------------------------------------- - -Another typical use case the, 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. -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; +declaration ``functor Name(Struct:SIGNATURE) = funct ... 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 - - - structure B=BinIO:>SIMPLE_IO; - structure B : 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 - 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; - - -The rationale behind opaque signatures -is that they make easier to replace an implementation -with another without breaking the interface. - -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. - - - - 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 +.. include:: simple-io.aml + :literal: 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) + - structure T = ManagedIO(TextIO) + - structure B = ManagedIO(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. +code for the structures ``T`` and ``B`` in the *client* program. + + ``- T.withInputFile "three-lines.txt" (print o T.inputAll)`` + +---- -``do print (IO.fileIn IO.inputAll "three-lines.txt")`` + *Such things are called individuals because each of them consists + of characteristics the collection of which will never be the + same for anything else. For the characteristics of Socrates will + never be in any other particular. But the characteristics of man — + I mean of the man that is general — will be the same in + several things, or rather in all particular men insofar as they + are men.* -- Porphyry |