This is much inspired by the well-written A Gentle Introduction to ML by Andrew Cumming, Computer Studies, Napier University, Edinburgh. I have written this in a rush, so please forgive my mistakes by correcting me if you spot any.
This is not a gentle introduction to programming. I assume you have existing knowledge of a programming language and understand recursion. I am a student pursuing Computing & Information Systems degree from Goldsmiths, University of London external studies programme, via Singapore Institute of Management. My language preferences are PHP and Python. I do not have prior experience in SML or any pure functional programming language (but I do use lambda) so this tutorial is written partly to further my knowledge on the functional programming language – SML. That said, this tutorial introduces the SML language from a slightly imperative point of view.
From what I know about SML
I didn’t research much on SML, but this is from Wikipedia:
Standard ML (SML) is a general-purpose, modular, functional programming language with compile-time type checking and type inference. It is popular among compiler writers and programming language researchers, as well as in the development of theorem provers.
SML is a modern descendant of the ML programming language used in the Logic for Computable Functions (LCF) theorem-proving project. It is distinctive among widely used languages in that it has a formal specification, given as typing rules and operational semantics in The Definition of Standard ML (1990, revised and simplified as The Definition of Standard ML (Revised) in 1997). (Source: Wikipedia)
Python has multiple implementations like CPython, PyPy and Jython, SML too has multiple implementations which includes SML/NJ, MLton and Moscow ML.
My SML environment
I install SML/NJ in my Windows machine. I use Windows 7 predominantly but SML/NJ works in all major operating systems. For any editing of SML files, I prefer to use Notepad++ which can do some syntax highlights for .sml files in CAML. It’s quite similar in coloring to SML. To aid my learning, I also svn-co-ed out the SML repository but that’s not necessary. This tutorial uses SML/NJ implementation so be sure to have it at least if you are interested in following it through.
Downloading and installing SML/NJ
You can get a copy of SML/NJ here. Just choose the latest available version to download. I then choose to download the one for Microsoft Windows – smlnj.msi. It’s right at the bottom.
Installing SML/NJ is rather easy, just follow through the steps and you’re done.
Different filetypes that SML uses
You can check out the things SML/NJ installs. It’s a bunch of .cm files littered around.
- .sml files are the source files for SML.
- .sig files are the signatures of the methods in the corresponding SML files.
- .cm files are SML/NJ’s Compilation Manager files. Think of them as compiled files for the .sml.
Honestly I don’t really like the way the folders are organized but that’s not really for me to decide.
Go next
Using the SML/NJ interactive shell
Go to Command Prompt. Then type “sml” and hit your Enter key. You should see something like this:
As you can see, I am using “Standard ML of New Jersey v110.69 [built: Wed Dec 31 23:09:16 2008]“. If you want to load an SML source file, type:
sml "myfile.sml"
This is helpful for small assignments like the one I am doing right now.
The SML/NJ interactive shell resembles that of Python and Ruby’s. You can type in expressions from the shell and receive immediate feedback. Great for learning the language. Much of this tutorial revolves around using the interactive shell.
To quit the interactive shell, press CTRL+Z and then Enter. This would exit the SML/NJ interactive shell and bring you back to the Windows command prompt.
Let’s try some expressions
We begin by asking SML/NJ how does it do (note that each expression terminates with a semi-colon):
Standard ML of New Jersey v110.69 [built: Wed Dec 31 23:09:16 2008] - "How do you do?"; val it = "How do you do?" : string -
SML/NJ returns back that it’s fine:
val it = "How do you do?" : string
This is to represent that the last evaluation is “How do you do?” and it is a string. All expressions evaluated are assigned to “it” if not otherwise assigned to any symbol.
Try some of the following:
- 1 + 1;
- 1.0 + 1.0;
- 1.0 + 1;
Here’s what you should be seeing:
- 1 + 1; val it = 2 : int - 1.0 + 1.0; val it = 2.0 : real - 1.0 + 1; stdIn:5.1-5.8 Error: operator and operand don't agree [literal] operator domain: real * real operand: real * int in expression: 1.0 + 1 -
Let’s analyze what the above means for us. “1″ is an integer. And the “+” is an infix function that allows for an integer on the left and an integer on the right. “+” is for integer or real addition. The second evaluation take “1.0 + 1.0″ and returns “2.0″ (a real). In the third evaluation “1.0 + 1″ results in an error. Let’s try to analyze the rather confusing error message:
- 1.0 + 1; stdIn:5.1-5.8 Error: operator and operand don't agree [literal] operator domain: real * real operand: real * int in expression: 1.0 + 1 -
“stdIn:5.1-5.8″ is informing the user where the fault occurred, it says that it starts from line 5 character 1 to line 5 character 8. “1.0 + 1;” happens to be my 5th evaluation using the interactive shell and if you do the counting, character 1 to 8 is basically all of “1.0 + 1;”. Now why did it fail?
It turns out the “+” requires the left and right side to be of the same type, either int or real but not a mixture. SML/NJ returns the operator (“+”) domain to tell the user “+” has some rules to follow and it is not complied with.
Basic mathematical operators
These are the various mathematical operators of SML/NJ. I urge you to try out some expressions before you proceed to get the feel of how it works. It’s a little different from the usual programming languages due to the type restrictions but you should get the hang of it in no time. Some of the operators are only for either int or real.
Operator | Purpose | int | real | Example |
+ | Addition | Y | Y | 1 + 1 is 2 |
- | Substraction | Y | Y | 3.0 – 1.0 is 2.0 |
* | Multiplication | Y | Y | 3.2 * 2.0 is 6.4 |
/ | Division | N | Y | 3.2 / 2.0 is 1.6 |
div | Division | Y | N | 3 div 2 is 1 |
mod | modulo | Y | N | 13 div 5 is 3 |
These typical mathematical operators are referred to as infix operators because the operator is in between 2 operands like this: 3 + 5. The infix operator is + and the operands are 3 and 5. Now try this evaluating: (op +)(3, 5);
- 3 + 5; val it = 8 : int - op+ (3, 5); val it = 8 : int
Recognize that both evaluations are similar. We’ve just converted the infix to a prefix operator using the “op” keyword.
Basic types
There are basically 4 main types – int, real, char and string. To denote a negative value, use “~”. Therefore “~1.3″ is a negative real value. String is denoted with double quotes as shown in the table.
Type | Example |
int | ~3 |
real | 712.13 |
char | #”c” |
string | “how do you do?” |
Note that for character, we can only put in a character of length 1.
String concatenation
String concatenation is done using a caret (^) like the following:
- "I am " ^ "fine."; val it = "I am fine." : string
Now, once again if we mix the types, we will get an error:
- "Ans " ^ (1 + 1); stdIn:20.1-20.17 Error: operator and operand don't agree [literal] operator domain: string * string operand: string * int in expression: "Ans " ^ (1 + 1)
Looks like SML/NJ is angry again. Try interpreting the error yourself. Can you figure what’s wrong?
The val, the fn and the fun
We previously asked SML how did it do:
Standard ML of New Jersey v110.69 [built: Wed Dec 31 23:09:16 2008] - "How do you do?"; val it = "How do you do?" : string -
SML assign “How do you do?” to the symbol ‘it’. ‘it’ always stores the last evaluation binded.
The val keyword
- val greeting = "How do you do?"; val greeting = "How do you do?" : string -
Using the ‘val’ keyword you can bind “How do you do?” to the symbol ‘greeting’. Now we try to bind an integer to ‘greeting’.
- val greeting = 1; val greeting = 1 : int - greeting = 1; val it = true : bool -
Now you can we attempt (and successfully) binded 1 to the symbol ‘greeting’ which seconds ago used to be a string. ‘greeting’ cannot be mutated, you can however bind a new item to ‘greeting’. Note that using “greeting = 1″ checks for equality and is not an assignment.
Writing a function
To write a function, we can use write as follows:
- fn x => x + 1; val it = fn : int -> int - it 24; val it = 25 : int -
The above function adds 1 to argument we input. We can call the function using ‘it 25′ since ‘it’ holds the value of last item used. The function is somewhat anonymous right now, and the next time we call ‘it’, it is no longer holds a function. What good is a function without a name. We can bind a function to a symbol using the ‘val’ keyword as shown below:
- val add_one = fn x => x + 1; val add_one = fn : int -> int - add_one 3; val it = 4 : int -
Now let’s take a look at the simple function we wrote:
- val add_one = fn x => x + 1; val add_one = fn : int -> int -
This function takes in 1 object ‘x’ as a parameter. The function evaluates ‘x + 1′ and returns the value. This function is then bind to the symbol ‘add_one’. On evaluation of the sentence, SML returns:
val add_one = fn : int -> int
This can be read as add_one is a symbol that holds a function. The function takes in a type ‘int’ and returns a type ‘int’. How does SML know what type to return?
Previously, we see that “1.0 + 1;” cannot be evaluated as ‘+’ requires both sides to be the same type. Since the function performs ‘x + 1′ and the right side is an int type, x must be an int type too and so has got to be its return type.
SML does this form of implicit typing all the time, it saves you from typing your symbols. We’ll see type inference again later.
The fun keyword
The ‘fun’ keyword is used in this way:
- fun add_one x = x + 1; val add_one = fn : int -> int -
Notice the difference with using the val keyword:
- val add_one = fn x => x + 1; val add_one = fn : int -> int -
The type of the function is exactly the same: int -> int
You can think of the ‘fun’ keyword as an alternate syntax to the ‘val’ keyword. Take note of the difference between the keyword ‘fun’ and ‘fn’. Virtually no one bind functions using the ‘val’ keyword since ‘fun’ looks a lot neater. The reason why ‘val’ is being introduced first is to stress that functions are similar to types like int and real. They can be bind to any symbol as would an integer.
From this point, we are going to use the ‘fun’ keyword.
That’s all for this lesson
This lesson is a little shorter but there’s lots of practice and try out yourself. There’s no learning without trying.