200 likes | 287 Views
Learn about formal vs. actual parameters in ML functions and the use of patterns as parameters. Explore how different languages handle parameter transmission and pattern matching. Get insights into examples of patterns in ML, Prolog, Java, and more.
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