Download PS10

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project

Document related concepts

Mathematics of radio engineering wikipedia , lookup

Principia Mathematica wikipedia , lookup

Elementary mathematics wikipedia , lookup

Vienna Development Method wikipedia , lookup

Transcript
Getting started with ML
• ML is a functional programming language.
• ML is statically typed: The types of literals, values, expressions and functions in a
program are calculated (inferred) at compile time. Scheme, on the other hand, is
a dynamically typed language.
• ML has a general mechanism for parameter passing: Patten Matching.
• ML enables the definition of new types.
Getting started with ML
Working at home? Course web page >> Useful links >> download Standard ML.
Preferring the lab? SML is installed in the CS labs.
INSTRUCTIONS:
Not recommended:
Recommended:
Open the SML Interpreter. Start working (Type any expression or declaration).
1. Create a *.sml file and edit it with any text editor.
2. The file can be loaded to the interpreter in one of the following ways:
A. While at the prompt (-), use “use”: -use(“C:\\ThingIDoForFun\\myMLCode.txt");
B. Or just write your “load” function: -val load = fn (fileName)=>use(“C:\\...\\" ^ fileName);
Load the file you’re working each time you add changes to it: - load(filename);
NOTE: Notepad++ is recommended for editing. To highlight text, select: Language > Caml.
Recursive Functions: Example (1)
• Recursive functions can be defined in the global scope using standard naming
(using the global environment for binding).
• ML introduces a keyword 'rec' for the declarations of recursive functions.
Given b≠0 and a natural number, n, calculate bn according to the following formula:
 
2 n/2

,n / 2 Z
b
b 
n 1

, otherwise
b  b
n
Good old Scheme:
(define exp (lambda (b n)
(cond ((= n 1) b)
((even? n) (exp (* b b) (/ n 2)))
(else (* b (exp b (- n 1)))))))
ML: Would this work?
val exp = fn(b, n)=>
if n = 1
then b
else if n mod 2 = 0
then exp (b*b, n div 2)
else b * exp (b, n-1);
Recursive Functions
val exp = fn(b, n)=>
if n = 1
then b
else if n mod 2 = 0
then exp (b*b, n div 2)
else b * exp (b, n-1);
Error: unbound variable or constructor: exp
ML’s compiler checks the function body at static (compile) time. When it reaches
the recursive call to exp, exp is not yet unbound!
Because it does not read the function’s body at static time, and at run-time, the
function is already defined!
Recursive Functions
That’s how
you comment
Use the keyword rec
for the declerations
of recursive functions
(* Signature: exp(b,n)
Purpose: Calculates the of a natural number
Type: Int*Int-> Int *)
val rec exp = fn(b, n) =>
if n=1
then b
else if n mod 2 = 0
then exp (b*b, n div 2)
else b * exp (b, n-1);
- val exp = fn : int * int -> int
The expression fn=>
is the procedure
value constructor in
ML. It is equivalent
to scheme’s lambda.
Pattern Matching:
• Clausal function expressions exploit ML's general mechanism for parameter passing:
Pattern Matching. Pattern matching takes 2 arguments: a Pattern and an Expression.
• The only way to pass parameters is by pattern matching!
• Examples:
• Variable: a, b
• Atomic value constructors: 1, true, “apple”
• Tuple value constructor: (1, false), (a, b)
• List value constructor: [(1, 2), (3, 4)]
Pattern Matching: Example (2)
The exp procedure was previously defined with a single pattern (b, n).
val rec exp = fn(b, n) =>
if n=1 …
We redefine exp function using two
patterns:
val rec exp = fn (b, 1) => b
| (b, n) => if n mod 2 = 0
then exp (b*b, n div 2)
else b * exp (b, n-1);
- val exp = fn : int * int -> int
• A call to exp with (2,8) may be regarded as a call with one argument.
• The argument (2,8) matches the 2nd pattern: (b,n) and its “body” is executed.
• The first clause whose pattern matches the argument is the one to execute.
• The rest are ignored (like 'cond' evaluation in Scheme).
Pattern Matching:
Pattern
Wildcard Character
Value Constructor
Atomic
Tuple
List
User defined value constructors
=>
=>
=>
=>
=>
=>
=>
Variable | Value Constructor | Wildcard Character
_
Atomic | Tuple | List | User defined value
Numeric | Character | Boolean | String constants
‘(‘ Pattern ‘,’,…’,’ Pattern ‘)’
nil | Pattern::List
Constraints:
• A variable may occur in a pattern only once: val foo=fn (a,a)=>
• The function constructor ‘fn' cannot appear in a pattern (a function
is not an equality type).
Not every ML expression is a pattern: In the exp procedure defined earlier, +, =>, (if 1
= a then true else false), are all ML expressions but are not patterns.
Data Types:
• Problems that require data beyond numbers or Booleans require the extension of the
type system with new datatypes (datatype: a type and its associated operations).
• The introduction of a new data type consists of:
• Type constructors: introduce a name(s) for the new type, possibly with parameters.
• Value constructors: introduce labeled values.
• In scheme we use tagged values to define new types. However, these tagged values have
no special positions in the language, which is why they are not really new types.
• In contrast to scheme, ML enables the extension of the typing system with new types.
Data Types:
Atomic Types
An atomic type is a set of atomic values, which are also its (parameter-less) value
constructors. They are also called Enumeration Types:
(* Type Constructor: direction
Value Constructor: North, South, East, West
Purpose: A datatype declaration that creates a new type to represent
the compass directions. *)
datatype direction = North | South | East | West;
- val move = fn((x,y),North) => (x,y+1) ;
|((x,y),South) => (x,y-1)
|((x,y),East) => (x+1,y)
|((x,y),West) => (x-1,y);
val move = fn : (int * int) * Direction -> int * int
West is a value constructor of type week and it has no parameters – it is a constant.
-move((4,5), North);
val it = (4,6) : int * int
Data Types: Composite Types: Example – complex numbers
A composite type is a (user defined) type whose values are created by value
constructors that take as parameters values of other types. A concrete type is a
non-polymorphic type.
Example: A system that performs arithmetic operations on complex numbers:
Rectangular representation:
Convenient for addition and
subtraction
Polar representation:
Convenient for division and
multiplication.
r
real_part(z1 + z2)
= real_part(z1) + real_part(z2)
imaginary_part(z1 + z2)
= imaginary_part(z1) + imaginary_part(z2)
magnitude(z1 * z2)
= magnitude(z1) * magnitude(z2)
angle(z1 * z2)
= angle(z1) + angle(z2)
Data Types: Composite Types: Example – complex numbers
In ML, using the type constructors and value constructors (that act like type
tags in Scheme), the problem is simple to solve:
(* Type constructor: complex
Value constructors: Rec, Polar.
Data values of this type have the form: Rec(3.0, 4.5), Polar(-3.5, 40.0) *)
datatype complex = Rec of real * real | Polar of real * real;
(* auxiliary function: square *)
- val square = fn x : real => x * x;
val square = fn : real -> real
Data Types: Composite Types: Example – complex numbers
In ML, using the type constructors and value constructors (that act like type
tags in Scheme), the problem is simple to solve:
- val real = fn (Rec(x,y) ) => x
| (Polar(r,a)) => r * Math.cos(a);
val real = fn : complex -> real
- val imaginary = fn (Rec(x,y) ) => y
| (Polar(r,a)) => r * Math.sin(a);
val imaginary = fn : complex -> real
- val radius = fn (Rec(x,y) ) => Math.sqrt( square(x) + square(y) )
| (Polar(r,a)) => r;
val radius = fn : complex -> real
- val angle = fn (Rec(x,y) ) => Math.atan( y / x )
| (Polar(r,a)) => a;
val angle = fn : complex -> real
Data Types: Composite Types: Example – complex numbers
- val add_complex = fn (Rec(x, y), Rec(x', y')) => ( Rec( x + x', y + y') )
| (Rec(x,y), z) => ( Rec( x + real(z), y + imaginary(z) ) )
| (z, Rec(x, y)) => ( Rec( real(z) + x, imaginary(z) + y) )
| (z,z') => (Rec( real(z) + real(z'), imaginary(z) + imaginary(z') ) );
val add_complex = fn : complex * complex -> complex
val sub_complex = fn (Rec(x, y), Rec(x', y')) => ( Rec( x - x', y - y') )
| (Rec(x,y), z) => ( Rec( x - real(z), y + imaginary(z) ) )
| (z, Rec(x, y)) => ( Rec( real(z) - x, imaginary(z) - y) )
| (z,z') => (Rec( real(z) - real(z'), imaginary(z) - imaginary(z') ) );
val sub_complex = fn : complex * complex -> complex
val mul_complex = fn (Polar(r, a), Polar(r', a')) => (Polar(r * r', a + a'))
| (Polar(r,a), z) => (Polar( r * radius(z), a + angle(z) ) )
| (z, Polar(r,a)) => (Polar( radius(z) * r, angle(z) + a ) )
| (z, z') => (Polar( radius(z) * radius(z'), angle(z) + angle(z') ) );
val mul_complex = fn : complex * complex -> complex
Data Types: Composite Types: Example – complex numbers
(* Pre -condition: r' != 0 *)
val div_complex = fn (Polar(r, a), Polar(r', a')) => (Polar(r / r', a - a'))
| (Polar(r, a), z)
=> (Polar(r / radius(z), a - angle(z)))
| (z, Polar(r, a))
=> (Polar(radius(z) / r, angle(z) - a))
| (z, z')
=> (Polar(radius(z) / radius(z'), angle(z) - angle(z')));
val div_complex = fn : complex * complex -> complex
Data Types: Recursive Types: Example - IntTree
Recursive types create infinite sets of values. The value constructors of a recursive
datatypes accept parameters of the defined datatype. A recursive type definition needs a
base case, i.e., a value constructor whose parameters are not the defined type.
- datatype intTree = Null | Leaf of int | Node of int * intTree * intTree;
datatype intTree = Leaf of int | Node of int * intTree * intTree | Null
- Leaf(3);
val it = Leaf 3 : intTree
1
- Node(1, Leaf(2), Null);
val it = Node (1,Leaf 2,Null) : intTree
- Node(1, Node(2, Null, Leaf(3)), Leaf(4));
val it = Node (1,Node (2,Null,Leaf 3),Leaf 4) : intTree
4
2
N
3
Data Types: Polymorphic Types
A polymorphic type is a type defined by a polymorphic type expression that consists of a
type constructor and type variables (‘a, ‘b, …). For example, the ‘a List type is specified
using type variables. Every instantiation of the type variables defines a concrete type.
- 1::2::nil;
val it = [1,2] : int list
Instantiation of the ’a list type:
- type intList = int list;
type intList = int list
- val makeIntList = fn (x,y,z) => x::y::z::nil;
val makeIntList = fn : 'a * 'a * 'a -> 'a list
- val makeIntList = fn (x,y,z) => x::y::z::nil : intList;
val makeIntList = fn : int * int * int -> intList
- makeIntList(1,2,3);
val it = [1,2,3] : int list
- makeIntList(1,2,3);
val it = [1,2,3] : intList
Data Types: Polymorphic Types: Example – sequence ops
Recall the scheme implementation for accumulate:
(define accumulate (lambda (op initial sequence)
(if (null? sequence) initial
(op (car sequence) (accumulate op initial (cdr sequence))))))
(accumulate + 0 (list a b c d)) would accumulate the elements in the following manner:
(a+(b+(c+(d+0))))
This is equivalent to the ML implementation for accumulate_right.
The accumulate_left procedure would accumulate from right to left:
(d+(c+(b+(a+0))))
-val rec accumulate_left = fn (f, e, []) => e
| (f, e, (head::tail)) => accumulate_left (f, f(head, e), tail);
val accumulate_left = fn : ('a * 'b -> 'b) * 'b * 'a list -> 'b
- accumulate_left ((fn(x,y) => x - y), 0, [1, 2 ,3 , 4]);
val it = 2 : int (4-(3-(2-(1-0))))
- accumulate_left ((fn(x,y) => x @ y), [], [[1,2,3], [4,5,6], [7,8,9]]);
val it = [7,8,9,4,5,6,1,2,3] : int list ([7,8,9]@([4,5,6]@([1,2,3]@[])))
Data Types: Recursive Polymorphic Types: Example – tree
• In scheme, trees are represented as heterogeneous lists: ‘(1 (2 3) (4 5)).
• Heterogeneous list cannot be represented in ML using the list value constructor (:: ).
• The reason is that in an ‘a list, once the type variable 'a is instantiated, the type of the list
is determined.
Scheme:
(list 1 (list 1 2))
Ml:
- [1, [2, 3]];
… Error: operator and operand don't agree [literal]
operator domain: int * int list
operand: int * int list list
in expression: 1 :: (2 :: 3 :: nil) :: nil
How can we overcome this problem?
5
2
4
datatype 'a tree = Leaf of 'a | Node of 'a * 'a tree * 'a tree | Null;
datatype 'a tree = Leaf of 'a | Node of 'a * 'a tree * 'a tree | Null
val binnum = Node (5, Leaf(4), Node(2, Leaf(1), Node(8, Null, Null)));
val binnum = Node (5,Leaf 4,Node (2,Leaf 1,Node (8,Null,Null))) : int tree
1
8
N
N
Data Types: Recursive Polymorphic Types: Example – tree
Example: the function treefold (analogous to accumulate).
val rec treefold = fn (f, e, Null) => e
| (f, e, Leaf(n)) => n
| (f, e, Node(node, left, right)) =>
f(node, treefold(f , e, left), treefold(f, e, right));
val treefold = fn : ('a * 'b * 'b -> 'b) * 'b * 'a tree -> ‘b
5
- treefold((fn(a,b,c) => a + b + c), 0, binnum) => 20
val it = 20 : int
- treefold((fn(a,b,c) => a + b + c), 0, t2) ;
val it = 5 : int
- treefold((fn(a,b,c) => a + b + c), 0, t3);
val it = 13 : int
2
4
1
8
N
N