200 likes | 285 Views
Patterns in ML functions. Formal vs. actual parameters. Here's a function definition (in C): int add (int x, int y) { return x + y; } x and y are the formal parameters Formal parameters must be variables (in C) Here's a function call: total = add (total, 5);
E N D
Formal vs. actual parameters • Here's a function definition (in C): • int add (int x, int y) { return x + y; } • x and y are the formal parameters • Formal parameters must be variables (in C) • Here's a function call: • total = add (total, 5); • total and 5 are the actual parameters • Actual parameters typically can be expressions
Parameters are patterns • In most conventional languages, formal parameters must be variables • Example: x and y in f(x, y) are both variables • In ML, all functions take a single parameter • For example, in fun add (x, y) = x + y;we say that (x, y) is one parameter, a tuple • But (x, y) is not a variable... • ...it is a pattern
The unit as parameter • Consider: fun five ( ) = 5; • The single parameter is the unit, ( ) • But the unit is a value, that is, a constant • In ML, a formal parameter (not just an actual parameter) may be a constant • Parameter transmission uses pattern matching
Patterns separate cases in Prolog • Prolog passes parameters by unification • Unification is a very general and powerful kind of pattern matching • Parameters are used to separate a task into cases, for example, • member(X, [X | _]).member(X, [_ | Y]) :- member(X, Y).
Patterns separate cases in Java • Java doesn't do pattern matching on parameters, but... • Java does allow methods to be overloaded • Overloaded methods have different signatures • The signature describes the number and type of parameters • This is a primitive kind of pattern matching • In a sense, it separates method calls into cases
ML allows multiple patterns • Pattern matching is not guaranteed to succeed • It's OK if a Prolog predicate fails, but... • ...an ML function must return a value • Therefore, ML must support multiple patterns in a function definition • Also, ML must ensure that the patterns are exhaustive, i.e. some pattern must match
Factorial • fun factorial 0 = 1| factorial n = n * factorial (n - 1); • The vertical bar separates cases • The function name is repeated • There's only one semicolon, at the end • Patterns are tried in order, therefore... • ...specific cases must precede general cases
Lists as patterns • Use the cons operator :: to form patterns • The pattern (x :: xs) matches a nonempty list • fun listSize [ ] = 0| listSize (x::xs) = 1 + listSize xs; • val listSize : 'a list -> int = fn • listSize ["a", "b", "c"]; • val it : int = 3
Exhaustive patterns • fun car (x::xs) = x; • warning: Match not exhaustivemissing constructors of type 'a list : nilval car : 'a list -> 'a = fn • car ["a", "b", "c"]; • val it : string = "a" • ML warns you but lets you continue
Last element of a list • fun last [x] = x | last (x::xs) = last xs; • warning: Match not exhaustivemissing constructors of type 'a list : nilval last : 'a list -> 'a = fn • last ["a", "b", "c"]; • val it : string = "c" • Notice the use of [x] and (x::xs) as patterns • [x] and (x::nil) are really the same thing
Doubling each element of a list • fun doubleAll [ ] = [ ]| doubleAll (h::t) = 2 * h :: (doubleAll t); • val doubleAll : int list -> int list = fn • doubleAll [1,2,3,4,5]; • val it : int list = [2, 4, 6, 8, 10]
as patterns as parameters • A pattern as a formal parameter breaks apart the components of the actual parameter • Using (x, y) gives us names for the two parts • (x :: xs) gives us names for the head and tail • We may also want a name for the whole thing • identifieraspattern lets us do both • Example: theList as (head :: tail)
Example: merge • fun merge(nil, M) = M| merge(L, nil) = L| merge(L as x::xs, M as y::ys) = if x < y then x::merge(xs, M) else y::merge(L, ys); • val merge : (int list * int list) -> int list = fn • Source: Elements of ML Programming, J. Ullman
Match expressions • A match consists of rules separated by | • A rule has the form pattern => result • pattern1 => result1 |pattern2 => result2 |. . .patternN => resultN • The results must all be of the same type • The patterns should be exhaustive
case expressions • A match all by itself isn't a complete expression • A case expression has the form:case expression of match • Patterns are tried in the order they are given • When a match is found, the corresponding result is evaluated and returned as the result • If nothing matches, a Match exception is raised • We haven't covered exceptions yet
if...then...else... expressions • if expr1 then expr2 else expr3 is actually syntactic sugar forcase expr1 of true => expr2 | false => expr3 • If you make an error in an if expression, the compiler reports it as an error in a case expression
Functions using fn • fun f(pattern1) = expr1| f(pattern2) = expr2; is actually syntactic sugar forval rec f = fn pattern1 => expr1| pattern2 => exprn2; • Note the use of val, fn, and rec • rec is only required for recursive functions
ML is purely functional • The if and case expressions are special cases of a match; so is fun • Arithmetic operators such as + are syntactic sugar for functions • Operators can be used as functions: op + (a, b) • In ML, "expressions" are really functions, and basically everything is a function