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