ML-intro

advertisement

Introduction to ML

You will be responsible for learning ML on your own.

Today I will cover some basics

Read Robert Harper’s notes on “an introduction to ML”

See course webpage for pointers and info about how to get the software

Intro to ML

Highlights

Functional Language

Functions are pervasive:

First-class values

Storable, arguments, results, nested

Strongly-typed language

Every expression has a type

Certain errors cannot occur

Polymorphic types provide flexibility

Flexible Module System

Abstract Types

Higher-order modules (functors)

Intro to ML

Interactive Language

Type in expressions

Evaluate and print type and result

Compiler as well

High-level programming features

Data types

Pattern matching

Exceptions

Mutable data discouraged

Preliminaries

Read – Eval – Print – Loop

- 3 + 2;

Preliminaries

Read – Eval – Print – Loop

- 3 + 2;

> 5: int

Preliminaries

Read – Eval – Print – Loop

- 3 + 2;

> 5: int

- it + 7;

> 12 : int

Preliminaries

Read – Eval – Print – Loop

- 3 + 2;

> 5: int

- it + 7;

> 12 : int

- it – 3;

> 9 : int

- 4 + true;

Type clash in : 3 + true

Looking for a : int

I have found a : bool

Type

Error

Preliminaries

Read – Eval – Print – Loop

- 3 + 2;

> 5: int

- it + 7;

> 12 : int

- it – 3;

> 9 : int

- 4 + true;

Type clash in : 3 + true

Looking for a : int

I have found a : bool

- 3 div 0;

Failure : Div

Type

Error

- run-time error

Basic Values

- ();

> () : unit => like “void” in C (sort of)

=> the uninteresting value/type

- true;

> true : bool

- false;

> false : bool

- if it then 3+2 else 7;

> 7 : int

- false andalso loop_Forever;

> false : bool

“else” clause is always necessary and also, or else short-circuit eval

Basic Values

Integers

- 3 + 2

> 5 : int

- 3 + (if not true then 5 else 7);

> 10 : int

Strings

- “Dave” ^ “ “ ^ “Walker”;

> “Dave Walker” : string

- print “foo\n”; foo

> 3 : int

Reals

- 3.14;

> 3.14 : real

No division between expressions and statements

Using SML/NJ

Interactive mode is a good way to start learning and to debug programs, but…

Type in a series of declarations into a

“.sml” file

- use “foo.sml”

[opening foo.sml]

… list of declarations with their types

Larger Projects

SML has its own built in interactive

“make”

Pros:

It automatically does the dependency analysis for you

No crazy makefile syntax to learn

Cons:

May be more difficult to interact with other languages or tools

Compilation Manager

sources.cm

Group is a.sig

b.sml

c.sml

a.sig

b.sml

c.sml

% sml

- OS.FileSys.chDir “ ~/courses/510/a2 ” ;

- CM.make();

[compiling … ] looks for “ sources.cm

” , analyzes dependencies compiles files in group

[wrote … ] saves binaries in ./CM/

- CM.make

’ “ myproj/ ” (); specify directory

What is next?

ML has a rich set of structured values

Tuples: (17, true, “stuff”)

Records: {name = “Dave”, ssn = 332177}

Lists: 3::4::5::nil or [3,4]@[5]

Datatypes

Functions

And more!

Rather than list all the details, we will write a couple of programs

An interpreter

Interpreters are usually implemented as a series of transformers: lexing/ parsing evaluate print stream of characters abstract syntax abstract value stream of characters

An interpreter

compilers

COS 320

US!

lexing/ parsing evaluate stream of characters abstract syntax abstract value print stream of characters

A little language (LL)

An arithmetic expression e is

 a boolean value an if statement (if e1 then e2 else e3) the number zero the successor of a number the predecessor of a number a test for zero (isZero e)

LL abstract syntax in ML

datatype term =

Bool of bool

| If of term * term * term

| Zero

| Successor of term

| Predecessor of term

| IsZero of term

-- constructors are capitalized

-- constructors can take a single argument of a particular type vertical bar separates alternatives type of a tuple another eg: string * char

LL abstract syntax in ML

If (Bool true, Zero, Successor (Successor Zero)) represents “if true then 0 else succ(succ 0)”

If

Suc.

Booltrue

Zero

Suc.

Zero

Function declarations

function name function parameter fun isNumberValue t = case t of

Zero => true

| Successor t2 => true

| _ => false default pattern matches anything

What is the type of the parameter t? Of the function?

function name function parameter fun isNumberValue t = case t of

Zero => true

| Successor t2 => true

| _ => false default pattern matches anything

What is the type of the parameter t? Of the function?

fun isNumberValue (t:term) : bool = case t of

Zero => true

| Successor t2 => true

| _ => false val isNumberValue : term -> bool

ML does type inference => you need not annotate functions yourself (but it can be helpful)

A type error

fun isNumberValue t = case t of

Zero => 0

| Successor t2 => true

| _ => false line 25 line 22 ex.sml:22.3-25.15 Error: types of rules don't agree [literal] earlier rule(s): term -> int this rule: term -> bool in rule:

Successor t2 => true

A type error

Actually, ML will give you several errors in a row: ex.sml:22.3-25.15 Error: types of rules don't agree [literal] earlier rule(s): term -> int this rule: term -> bool in rule:

Successor t2 => true ex.sml:22.3-25.15 Error: types of rules don't agree [literal] earlier rule(s): term -> int this rule: term -> bool in rule:

_ => false

A very subtle error

fun isNumberValue t = case t of zero => true

| Successor t2 => true

| _ => false

The code above type checks. But when we test it refined the function always returns “true.”

What has gone wrong?

A very subtle error

fun isNumberValue t = case t of zero => true

| Successor t2 => true

| _ => false

The code above type checks. But when we test it refined the function always returns “true.”

What has gone wrong?

-- zero is not capitalized

-- ML treats it like a variable pattern (matches anything!)

Another function

fun isNumberValue t = ...

fun isValue t = case t of

Bool _ => true

| t => isNumberValue t

Exceptions

exception Error of string fun debug s : unit = raise (Error s)

Exceptions

exception Error of string fun debug s : unit = raise (Error s) in SML interpreter:

- debug "hello"; uncaught exception Error raised at: ex.sml:15.28-15.35

Evaluator

fun isNumberValue t = ...

fun isValue t = ...

exception NoRule fun eval1 t =

...

case t of

Bool _ | Zero => raise NoRule

Evaluator

...

fun eval1 t = case t of

Bool _ | Zero => raise NoRule

| If(Bool b,t2,t3) => (if b then t2 else t3)

| If(t1,t2,t3) => If (eval1 t1,t2,t3)

...

Evaluator

exception NoRule fun eval1 t = case t of

Bool _ | Zero => ...

| ...

| Successor t => if isValue t then raise NoRule else let val t’ = eval1 t in

Successor t’ end

Finishing the Evaluator

fun eval1 t = case t of

...

| ...

| Successor t => ...

| Predecessor t => ...

| IsZero t => ...

be sure your case is exhaustive

Finishing the Evaluator

fun eval1 t = case t of

...

| ...

| Successor t => ...

What if we forgot a case?

Finishing the Evaluator

fun eval1 t = case t of

...

| ...

| Successor t => ...

What if we forgot a case?

ex.sml:25.2-35.12 Warning: match nonexhaustive

(Bool _ | Zero) => ...

If (Bool b,t2,t3) => ...

If (t1,t2,t3) => ...

Successor t => ...

Multi-step evaluation

fun eval1 t = ...

fun eval t = let in fun loop t = loop (eval1 t) val message = “Done\n”

((loop t) handle

NoRule => print message

| Error s => print s) end

Be very careful with the syntax of handle

(use extra parens)

ML is all about functions

There are many different ways to define functions!

I almost always use “fun f x = ...”

When I am only going to use a function once and it is not recursive, I write an anonymous function:

(fn x => ...)

Anonymous functions

val n = 3 binds a variable (n) to a value (3) binds a variable

(isNumberValue) fn keyword introduces anonymous fun val isNumberValue =

(fn t => case t of zero => true

| Successor t2 => true

| _ => false) to the anonymous function value

Anonymous functions

a type definition (very convenient) type ifun = int -> int val intCompose : ifun * ifun -> ifun = ...

a pair of anonymous functions fun add3 x = intCompose ((fn x => x + 2), (fn y => y + 1)) x

Anonymous functions

type ifun = int -> int pattern match against arg val intCompose : ifun * ifun -> ifun = fn (f,g) =>

(fn x => f (g x)) argument is pair of functions result is a function!

fun add3 x = intCompose ((fn x => x + 2), (fn y => y + 1)) x

Another way to write a function

fun f x = ........ can be written as: val f = (fn x => ......) provided the function is not recursive; f does not appear in ........

Another way to write a function

fun f x = ....

can always be written as: val rec f = (fn x => ...f can be used here...) keyword rec declares a recursive function value

Yet another way to write a function

fun isNumberValue Zero = true

| isNumberValue (Successor t2) = true

| isNumberValue (_) = true

This is just an abbreviation for fun isNumberValue t = case t of

Zero => true

| Successor t2 => true

| _ => true

Yet another way to create a type error

fun isNumberValue 0 = true

| isNumberValue (Successor t2) = true

| isNumberValue (_) = true ex.sml:9.1-11.29 Error: parameter or result constraints of clauses don't agree [literal] this clause: term -> 'Z previous clauses: int -> 'Z in declaration: isNumberValue =

(fn 0 => true

| Successor t2 => true

| _ => true)

Parametric Polymorphism

Functions like compose work on objects of many different types val compose = fn f => fn g => fn x => f (g x) compose (fn x => x + 1) (fn x => x + 2) compose not (fn x => x < 17)

Parametric Polymorphism

Functions like compose work on objects of many different types val compose = fn f => fn g => fn x => f (g x) compose not (fn x => x < 17) compose (fn x => x < 17) not

BAD!!

Parametric Polymorphism

Functions like compose work on objects of many different types val compose = fn f => fn g => fn x => f (g x) compose: (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b)

Note: type variables are written with ‘

Parametric Polymorphism

compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b) not : bool -> bool

 compose not : (‘c -> bool) -> (c’ -> bool) compose not not : bool -> bool

Parametric Polymorphism

compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b) not : bool -> bool

 compose (fn x => x) : ?

Parametric Polymorphism

compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b) not : bool -> bool

 compose (fn x => x) : ?

‘d -> ‘d

Parametric Polymorphism

 compose : (‘a -> ‘b) -> (‘c -> ‘a) -> (‘c -> ‘b) not : bool -> bool compose (fn x => x) : ?

‘d -> ‘d must be the same ie:

‘a = ‘d

‘b = ‘d

Parametric Polymorphism

 compose : (‘d -> ‘d) -> (‘c -> ‘d) -> (‘c -> ‘d) not : bool -> bool compose (fn x => x) : ?

‘d -> ‘d must be the same ie:

‘a = ‘d

‘b = ‘d

Parametric Polymorphism

compose : (‘d -> ‘d) -> (‘c -> ‘d) -> (‘c -> ‘d) not : bool -> bool

 compose (fn x => x) : ?

‘d -> ‘d

(‘c -> ‘d) -> (‘c -> ‘d)

Lists

Lists: nil : ‘a list

:: : ‘a * ‘a list -> ‘a list

3 :: 4 :: 5 :: nil : int list

(fn x => x) :: nil : (‘a -> ‘a) list

List Processing

Functions over lists are usually defined by case analysis (induction) over the structure of a list fun length l = case l of nil => 0 l x :: l => 1 + (length l)

Hint: often, the structure of a function is guided by the type of the argument (recall eval)

List Processing

fun map f l = case l of nil => [] l x :: l => (f x) :: (map f l) an incredibly useful function:

- map (fn x => x+1) [1,2,3];

> val it = [2,3,4] ; int list

List Processing

fun fold f a l = case l of nil => a l x :: l => f (fold f a l) x another incredibly useful function what does it do?

use it to write map.

ML Modules

Signatures

Interfaces

Structures

Implementations

Functors

Parameterized structures

Functions from structures to structures

Structures

 structure Queue = struct type ‘a queue = ‘a list * ‘a list exception Empty val empty = (nil, nil) fun insert (x, q) = … fun remove q = … end

Structures

structure Queue = struct type ‘a queue = ‘a list * ‘a list end exception Empty

...

fun insert2 q x y =

Queue.insert (y, Queue.insert (q, x))

Structures

structure Queue = struct

...

end structure Q = Queue fun insert2 q x y =

Q.insert (y, Q.insert (q, x)) convenient abbreviation

Structures

structure Queue = struct

...

end open Queue fun insert2 q x y = insert (y, insert (q, x)) for lazy programmers

-- not encouraged!

Structures

structure Queue = struct end type ‘a queue = ‘a list * ‘a list

...

fun insert2 (q1,q2) x y : ‘a queue =

(x::y::q1,q2) by default, all components of the structure may be used

-- we know the type ‘ a queue

Signatures

signature QUEUE = sig type ‘a queue exception Empty abstract type

-- we don ’ t know the type ‘ a queue val empty : ‘a queue val insert : ‘a * ‘a queue -> ‘a queue val remove : ‘a queue -> ‘a * ‘a queue end

Information hiding

signature QUEUE = sig type ‘a queue

... end structure Queue :> QUEUE = struct type ‘ a queue = ‘ a list * ‘ a list val empty = (nil, nil)

… end does not type check fun insert2 (q1,q2) x y : ‘ a queue =

(x::y::q1,q2)

Signature Ascription

Opaque ascription

Provides abstract types structure Queue :> QUEUE = …

Transparent ascription

A special case of opaque ascription

Hides fields but does not make types abstract structure Queue_E : QUEUE = …

SEE Harper, chapters 18-22 for more on modules

Other things

 functors (functions from structures to structures) references (mutable data structures)

 ref e; !e; e1 := e2 while loops, for loops arrays

(* comments (* can be *) nested *) a bunch of other stuff...

Last Things

Learning to program in SML can be tricky at first

But once you get used to it, you will never want to go back to imperative languages

Check out the reference materials listed on the course homepage

Download