Pure functional programming
Supports functions as parameters and data Bans explicit memory allocation and deallocation Bans assignment
Lisp was one of the first widely used high level programming languages along with the more widely used Fortran and Cobol.
Lisp is mostly a functional language. (It doesn't ban assignment but recursion typically used instead of any loop like construct.
Free occurrences in Lisp are bound using the most-recently activated rule; i.e. dynamic scope binding.
Scheme is another mostly functional language, which looks almost exactly like Lisp.
The main difference is that Scheme uses the most-closely nested scope binding rule; i.e. lexical (or static) scope binding.
In Lisp/Scheme identifiers are typeless.
All type checking is done at run time on the values currently bound to names.
ML is similarly a mostly functional programming language.
ML supports:
Functions as parameters and data. Nested functions. High-level types (e.g. linked lists) Call-by-value, lexically scoped.
Type safe (Strongly typed) language.
Strong type system with no implicit conversions!
Supports polymorphic functions, but does not provide for user defined overloading.
Supports data abstraction (user defined types)
Supports modularization beyond functions.
Functions are first class types.
That is, functions can have parameters of function type and return values of function type.
It doesn't completely ban assignment, but doesn't have loop or the usual if construct for flow of control.
It does have an if expression which has a value, but doesn't change existing bindings.
ML does have ref types that allow value bindings to be updated (as in imperative programming languages).
Use of ref types is a non functional-programming feature added to ML if you really need it.
Installation of Moscow ML is no-frills.
There is no install shield or fancy graphical interface.
Download the compressed file (zip).
Unzip in some folder.
Define environment variable MOSMLLIB to be the lib subdirectory of the directory where you unzipped the installation file.
If you are going to run mosml from a console window, add the bin subdirectory of the installation directory to your PATH environment variable.
- 5; > val it = 5 : int - "hello"; > val it = "hello" : string - true; > val it = true : bool - #"a"; > val it = #"a" : char - 5.6; > val it = 5.6 : real - [1,2,3]; > val it = [1, 2, 3] : int list - ["hello", "world"]; > val it = ["hello", "world"] : string list - (5, 5.6, "abc"); > val it = (5, 5.6, "abc") : int * real * string - length(["hello", "world"]); > val it = 2 : int - val lst = ["hello", "world"]; > val lst = ["hello", "world"] : string list - length (lst); > val it = 2 : int - length lst; > val it = 2 : int -
bool, char, int, real, string, list, tuple
Basic binders: Ordinary bindings (e.g. of parameters, return types or expressions) x: int y: string (x:int) + 1 Value bindings val x = ... val x = 5; val y = x; val z = f(x); Function bindings fun f(x:int):int = ( );
if expression Grammar: <if_expr> -> 'if' <bool cond> 'then' <expr> 'else' <expr> Example: if x < y then y else x // In C++ (x < y) ? y : x Notes: The else is not optional. Value of this expression is either y or x depending on the condition.
case expression Grammar: <case_expr> -> 'case' <expr> 'of' <pattern> '=>' <expr> { '|' <pattern> '=>' <expr> } [ '|' '_' '=>' <expr> ] <pattern> -> <expr> | <constructor> Example: case x of "Fred" => "001-00-1111" | "Barney" => "010-00-0001" | "Wilma" => "200-11-0001" | "Betty" => "011-22-1010" | _ => "000-00-0000" Notes: x is compared to the pattern => value choices in order. The value of the first pattern that matches is the value of the case expression. The don't care pattern, _ always matches. The don't care _ is optional. However, if x doesn't match any of the patterns and _ is not present, a Match exception is raised. The case expression is not limited to int types. Any type can be used. The <constructor> will be explained later with the discussion of data types.
let expression Grammar: <let_expr> -> 'let' { <binding> } 'in' <expr> 'end' Example: let val s = x * x val f = s * s in f * f end Notes: The value of this let expression is the value of the if expression (the expression between 'in' and 'end'). The f occurring in the 'in' ... 'end' section of the let expression is bound to the val binding in the 'let' ... section. The s in the "binding" part of the let expression is also bound by a val binding. The x occurrences are free in this let expression. let expressions are most often used as the body of functions, where otherwise free occurrences in the let expression are usually bound by the parameter bindings of the function.
fun pow8(x: int):int = ( let val s = x * x val f = s * s in f * f end );
The function body must be a single expression.
However, as just observed, it could be a complicated expression such as a let, if, or case expression
:(*--------------------------------------------- Function: sum(n:int):int Description: returns the sum 1 + 2 + ... + n -----------------------------------------------*) fun sum(n:int):int = ( if n = 0 then 0 else n + sum(n-1) )
1 (*------------------------------------------------------------- 2 Function: amtDue(numWindows: int, winType: string): real 3 Description: Calculates the total amount for a purchase of 4 numWindows, all of type winType. The possible types and 5 cost per window are 6 7 "double hung" 250.00 8 "gliding" 350.00 9 "casement" 450.00 10 11 However, a discount of 5% is given for an order of 5 or more 12 windows. 13 ---------------------------------------------------------------*) 14 fun amtDue(numWindows: int, winType: string): real = 15 ( 16 let 17 fun costForType(wType: string):real = 18 ( 19 case wType of 20 "double hung" => 250.00 21 | "gliding" => 350.00 22 | "casement" => 450.00 23 ) 24 val initAmt = real(numWindows) * costForType(winType) 25 in 26 if numWindows >= 5 then 27 initAmt - 0.05 * initAmt 28 else 29 initAmt 30 end 31 )
ML has built in data list types.
val x = [1,2,3,4] val y = ["Hello", "World"] val z = [ #"a", #"b", #"c"] val w0 = [] The type of x is int list. The type of y is string list The type of z is char list The type of w is not specified completely since the element type is not determined. A type annotation can be given. For example, to specify an empty integer list: val w = [] : int list Note: All elements of a list must be the same type.
In contrast to lists, tuples consist of a sequence of elements whose types don't have to be the same.
However, the number of elements in a tuple is part of its type; so a triple of int's is not the same type as a quadruple of int's.
(5, "hello"); type: (int, string) tuple ML type notation: int * string (5.0, 5, true, #"a") type: (real, int, bool, char) tuple ML type notation: real * int * bool * char
The ML language system has an interactive interpreter.
When this interpreter is started up it loads built in function bindings and optionally user defined function and val bindings defined in a file.
Additional function bindings can be added either interactively or from additional files.
All bindings remain in effect until the interactive session is terminated.
Start ml (E.g., either moscow ml: mosml, or standard ml of new jersey: sml) d:\smldir> mosml Moscow ML version 2.00 (June 2000) Enter `quit();' to quit. - val x = 5; // Enter a val binding interactively > val it = 5 : int - val x = [1,2,3]; > val x = [1, 2, 3] : int list // Enter another val binding - use "tmp.sml"; // Load a file with fun and val bindings [opening file "tmp.sml"] File "tmp.sml", line 30-32, characters 7-87: ! ......."double hung" => 250.00 ! | "gliding" => 350.00 ! | "casement" => 450.00. ! Warning: pattern matching is not exhaustive > val pow8 = fn : int -> int val amtDue = fn : int * string -> real val ''a member = fn : ''a * ''a list -> bool val ''a rmdups = fn : ''a list -> ''a list [closing file "tmp.sml"] > val it = () : unit - x; // ML remembers earlier bindings > val it = [1, 2, 3] : int list - amtDue(3, "double hung"); > val it = 750.0 : real - amtDue; > val it = fn : int * string -> real - (5, "hello"); > val it = (5, "hello") : int * string
In the interactive session typing the name of a function (but not calling it) causes ML to simply report its type. Here is the type notation used for the amtDue function - amtDue; > val it = fn : int * string -> real The definition of amtDue looks like this: fun amtDue(numWindows: int, winType: string): real = ... The parameter type of amtDue is int * string The return type of amtDue is real And the type of amtDue is int * string -> real Note: The type of amtDue is not just return type or just the parameter type; it is both!
ML allows polymorphic functions to be written and some of the built in functions are polymorphic.
These are similar (but not implemented the same) as template functions in C++.
Declaring a variable x: int means that x can have int values such as 5, 10, etc.
ML has a notation for type variables. A type variable can have values which are type names or type expressions.
The ML notation for such type "variables" is single quote followed by a letter. E.g., 'a, 'b, etc.
fun f(x: 'a list) : int = ... Type of f is : 'a list -> int Its parameter type can be a list of any element type. Its return type is int.
Function: hd Description: Returns the "head" of a list Type of hd: 'a list -> 'a Example: hd([1, 2, 3, 4]) is 1 'a for this example is int Function: tl Description: Returns the "tail" of a list; that is, everything except the head Type of tl: 'a list -> 'a list Example: tl([1.5, 2.5, 3.5, 4.5]) is [2.5, 3.5, 4.5] 'a for this example is real
fun len(lst: 'a list) : int returns the number of elements in the list, lst. fun member(x: 'a, lst: 'a list): bool returns true if x is in lst, else false fun rmdups(lst: ''a list) : ''a list returns a list just like lst except with duplicates removed.