CS52 - Spring 2017 - Class 19
Example code in this lecture
local.sml
anonymous_functions.sml
Lecture notes
What does the randListBad function do in
local.sml code
?
- tries to generate a list of random numbers of size n
- if you run it, though:
> randListBad 10;
val it = [79,79,79,79,79,79,79,79,79,79] : int list
generates the same number over and over!
- Why?
- The random number generator is a "pseudo-random" number generator
- It generates numbers that look random
- But they're algorithmically generated
- The seed indicates the starting point. Given the same seed, it will generate exactly the same numbers.
- Each time the function is called, it uses the same seed
- How do we fix this?
- Key: declare the generator outside of the function
Look at the randList function in
local.sml code
- We could just declare the generator outside as a variable and then pass it to the function.
- Another option is to declare a variable, use it in the function, but make its scope only visible to the function.
local
- A way for declaring a local scope
local
<declaration>
<declaration>
...
in
<declaration>
<declaration>
...
end
- The declarations between "local" and "in" are only visible within that block and within the "in-end" block
local vs. let
- Use let:
- When the declarations are per function call
- When the declarations are small (e.g. variables, simple functions)
- Use local:
- When the declarations need to only happen once.
- When you have larger declarations (e.g. longer functions).
- When you need to share values/functions across functions (e.g. helper functions that are used in multiple places).
- Regardless, going forward, you should use one or the other appropriately!
Write a function add47list that takes in a list and adds 47 to each value in the list
- look at first two variants in
anonymous_functions.sml code
anonymous functions
- sometimes we only need a function for one simple use case, e.g. when we were trying to add 47 to all the elements in a list
- we could write the function as: fun add47 x = x+47;
- and then use it in map like we did
- we can also just create a function on the fly, what is called an anonymous function (because it doesn't have a name!)
- the syntax is:
fn <parameters> => <body>
- for example, we could write the add47 function as:
fn x => x+47;
- We can embed this in a statement, for example:
fun add47list lst = map (fn x => x+47) lst
- or even more interesting
fun addToAll k lst = map (fn x => x+k) lst
- or if you want to get fancy
val addToAll k = map (fn x => x+k)
What is the type signature of this function?
fun square x = x*x;
- int -> int
- Could it be anything else? Put another way, might we want to call it on anything else?
- might also like to square reals, e.g.
> square 10.0
val it = 100.0 : real
- In situations where there is ambiguity (i.e. it could be more than one type) we can add a constraint to a variable declaration by adding ":<type>" after the declaration, e.g.
fun square x:real = x*x;
would have the type signature: real -> real
- Note that ':' is not a function like "real" that converts between types, it just constrains the type signature
"type"
- We've seen how we can declare our own datatypes
- Another useful feature that SML has is to create another name for an existing type: the "type" command
- the syntax is:
type <new_type_name> = <old_type>;
- For example:
- type davesawesometype = int;
type davesawesometype = int
- val x = 2;
val x = 2 : int
- val x = 2 : davesawesometype;
val x = 2 : davesawesometype
- x;
val it = 2 : davesawesometype
- fun double x : davesawesometype = 2*x;
val double = fn : davesawesometype -> davesawesometype
- double 10;
val it = 20 : davesawesometype
- Note that underneath the covers, they're just two names for the same type of value, so they can still be used interchangeably.
- Another example:
- type pair = int * int;
type pair = int * int
- fun addPair ((x,y):pair) = x+y;
val addPair = fn : pair -> int
- val u = (1,2);
val u = (1,2) : int * int
- val v = (1,2):pair;
val v = (1,2) : pair
- u = v;
val it = true : bool
Look at the starter code for assignment 8
- We use three "type" declarations to give other names for existing types
- This doesn't add any functionality to our code
- But makes our functions more readable, e.g.
- matches:
- Old version:
Peg list -> Peg list -> (int * int)
- New version:
code -> code -> response
- If we then used matches to specify a codemaker (by giving it a code):
- Old version:
Peg list -> (int * int)
- New version:
code -> response