CSC347 Oct13

slide version

single file version

Contents

  1. Functional Programming: Characteristics
  2. Functional Programming: Languages
  3. ML: Characteristics
  4. ML: Versions and Installation
  5. Sample Interaction
  6. ML: Predifined data types
  7. ML: Bindings
  8. Expressions
  9. Expressions
  10. Expressions
  11. Expressions (cont.)
  12. ML: Functions
  13. ML: Another function
  14. ML: Lists
  15. Tuples
  16. Evaluating ML functions
  17. Example
  18. ML: Function Type Notations
  19. Type variables
  20. Functions on Lists
  21. Writing some ML functions on lists

Functional Programming: Characteristics[2]

Pure functional programming

 Supports functions as parameters and data
 Bans explicit memory allocation and deallocation
 Bans assignment

Functional Programming: Languages[3]

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.

ML: Characteristics[4]

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.

ML: Versions and Installation[5]

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.

Sample Interaction[6]

- 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
- 

ML: Predifined data types[7]

     bool, char, int, real, string, list, tuple

ML: Bindings[8]

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 = (  );


ML: if Expressions[9]

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. 

ML: case Expressions[10]

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.

ML: let Expressions[11]

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.

ML: let Expressions (cont.)[12]

    fun pow8(x: int):int =
    (
       let
         val s = x * x
         val f = s * s
       in
         f * f
       end
    );

ML: Functions[13]

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)
)

ML: Another function[14]

    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: Lists[15]

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.

Tuples[16]

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

Evaluating ML functions[17]

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.

Example[18]

     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


ML: Function Type Notations[19]

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!

Type variables[20]

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.

Functions on Lists[21]

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

Writing some ML functions on lists[22]

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.