CS54 - Fall 2022 - Class 2

Example code in this lecture

   power.sml
   interval.sml

Lecture notes

  • admin
       - Assignment 0 is due Friday at 5pm
       - Laptop setup
          - Corey has office hours 4-5pm today if you need help
       - I'll have my office hours up soon!
          - email me if you need to meet before they're up
       - Mentor hours up soon
       - Everyone should be added to piazza and gradescope
       - PERM update
       - masks
       - next week
          - no class Monday
          - will have an assignment out on Monday, due on Sunday still
             - can start before Wednesday, but will some material will covered on Wednesday

  • Everything is a value in SML
       - notice that if-then-else represents a value
          - each branch has a value of the same type
          - no matter what branch we go down, we obtain a value
          - we'll see this type of behavior A LOT

  • Look at the power3 function
       - What's new in power3 we haven't seen before?
          - orelse (equivalent to || in Java)
          - the equivalent of && is andalso
       - new language = new syntax
          - There are almost always ideas in new languages that you've seen before
          - Often, they're just slightly different than other languages you've seen
          - Just takes a little while to get used to the syntax of the new language
       - Are there any differences in functionality between these two?
          - no
          - power2 uses pattern matching, while power3 uses if/then

  • A note about formatting conventions
       - Why do we format code?
          - make it easier to read and understand
       - What conventions have you seen so far in the code I've shown you?
          - indenting schemes
          - some variable naming
             - because of the length of the code, more common to use simple variable names
             - you'll still see some conventions as we go forward, though
       - For this class you will need to follow *some* reasonable convention
          - Doing what I do in class is a great idea :)
          - You may also do your own if you'd prefer
          - Whatever you do:
             1) you need to be consistent!
             2) it needs to highlight the structure of the code

  • Look at power4 in power.sml code
       - Any difference?
          - no parentheses around parameters! (and similarly when it's called recursively)
       - If we copy this into sml we get the following type signature:
          val power4 = fn : int -> int -> int
       - What does this say?
          - it's a function
          - that takes as input an int
          - and returns "int -> int"
             - that's another function!
       - To call it, instead of passing a tuple, we just give it two arguments:
          > power4 10 2;
          val it = 100 : int
       - What do you think happens if we just call it with one?
          > power4 10;
          val it = fn : int -> int

          - just like the type signature said, if we give it an int we get back an int->int
          - What do you think this function is?
             - We have specified the first parameter (n = 10)
             - It's the power4 function, but with n already assigned to 10!
             - To use it, though, we need to store it somewhere

  • val
       - the keyword "val" is used to declare a new variable
          > val x = 4;
          val x = 4 : int
          > x;
          val it = 4 : int
       - Functions in SML are first-class variables. They are just like any other values
       - We can store them to a variable, just like any other values:
          > val tenToThe = power4 10;
          val tenToThe = fn : int -> int
          > tenToThe 2;
          val it = 100 : int
          > tenToThe 3;
          val it = 1000 : int
          > tenToThe 4;
          val it = 10000 : int

  • curried vs. uncurried
       - power, power2 and power3 are called "uncurried" functions
          - they take a single parameter that is a tuple
       - power4 is called a "curried" function
       - We'll often prefer the curried version

  • loading files
       - the SML interpreter is nice for doing interactive things and debugging, but we don't want to keep copying and pasting code into the SML interpreter
       - use
          - we can run the contents of an entire file through using "use", e.g.

             > use "power.sml";
             val power = fn : int * int -> int
             val power2 = fn : int * int -> int
             val power3 = fn : int * int -> int
             val power4 = fn : int -> int -> int
             val it = () : unit

          - it behaves as if you started at the top of the file and typed each of the lines one at a time
          - you'll see the output from the SML for each line
          - additionally, you'll see one last line at the end which is the "result" from the actual use command
       - most of the time this is how you will work:
          - edit your function(s) in your text editor
          - *save* the file
          - "use" the file
          - try out some of the functions in the interpreter
          - repeat

  • Write a function called add that takes two integers and adds them
       - Write an uncurried version
       - Write a curried version
       - State what the types of both would be

  • built in datatypes in SML
       - Which ones have we seen so far?
          - int
          - fn

  • look at SMLDataTypes.pdf handout
       - the most common datatypes we'll use
       - int
          - ~ is for negation (not the minus sign!)
          - div and mod (not / and %)
             - distinguishes these from real valued versions
       - real
          - you cannot mix ints and reals:

          > 1.0 + 1;
          stdIn:1.2-1.9 Error: operator and operand don't agree [literal]
           operator domain: real * real
           operand: real * int
           in expression:
           1.0 + 1

             - SML error messages take a little while to get used to
             - operator domain: it expects two reals
             - we gave it real * int
             - the location was stdIn (standard input, i.e. we typed it) line 1 characters 2-9
          - trunc, round and real are used to convert between them
             > 1.0 + real 1;
             val it = 2.0 : real
       - list
          - the simplest list is just an empty list
             > [];
             val it = [] : 'a list
             > nil;
             val it = [] : 'a list

             - What is the type of the empty list?
                - it's a list
                - we don't know the type yet!
                - 'a is a type variable, it stands for *some* type
          - the "fancy", way to make a list, is similar to what you may have seen in other languages:
             > [1, 2, 3, 4];
             val it = [1,2,3,4] : int list
          - the more realistic way, though, uses the :: (append) operator
             > 1::2::3::4::[];
             val it = [1,2,3,4] : int list

             - this more closely mirrors how the list is actually stored
             - a list consists of two things:
                - an element
                - followed by the rest of the list
                - draw the picture
             - lists are very naturally recursive!
                - what is the "base" case?
                   - empty list
          - @ vs. ::
             - there is also list concatenation using @
                > [1] @ [2,3,4];
                val it = [1,2,3,4] : int list

             - be careful that you understand the distinction between these two
                > [1] :: [2,3,4];
                stdIn:21.1-21.15 Error: operator and operand don't agree [literal]
                 operator domain: int list * int list list
                 operand: int list * int list
                 in expression:
                 (1 :: nil) :: 2 :: 3 :: 4 :: nil
                > 1 @ [2, 3, 4];
                stdIn:1.2-1.15 Error: operator and operand don't agree [literal]
                 operator domain: 'Z list * 'Z list
                 operand: int * int list
                 in expression:
                 1 @ 2 :: 3 :: 4 :: nil
       - string
          - should feel very similar to strings in Java
             > "banana";
             val it = "banana" : string
          - main thing to watch out for is ^ is used for concatenation (NOT +)
             > "banana" ^ " cream " ^ "pie";
             val it = "banana cream pie" : string
       - char
          - a single character
          - can create constants using the # sign to indicate that it's a character and not a string
             > #"a";
             val it = #"a" : char
             > #"C";
             val it = #"C" : char
             > #"ab";
             stdIn:26.1-26.5 Error: character constant not length 1
          - underneath the covers, each character has a number associated with it
             - the numbers roughly follow the alphabetical ordering
                > ord(#"a");
                val it = 97 : int
                > ord(#"b");
                val it = 98 : int
             - be careful about capitalized vs. not, etc.
                > ord(#"A");
                val it = 65 : int

  • look at the interval function in interval.sml code
       - What does it do?
          - takes two numbers as parameters
             - how do you know they're numbers?
                - m+1
                - m >= n
          - creates a list (using ::)
          - the list contains the number from m to n
             - including m and n?
                - including m
                - not including n

          > interval 1 10;
          val it = [1,2,3,4,5,6,7,8,9] : int list
       - What is its type signature (curried or uncurried)?
          val interval = fn : int -> int -> int list
          
          - curried function
          - has two parameters (sort of), both ints
          - gives us back a list of ints!

  • look at the interval2 function in interval.sml code
       - does the same thing!
       - uses @ instead of ::
          - notice that because we're using @, we have to do [n-1] because @ expects two lists
       - functionally these behave the same, but there are some differences in performance (more on this later!)
          > interval 1 100000;
          val it = [1,2,3,4,5,6,7,8,9,10,11,12,...] : int list
          > interval2 1 100000;
          Interrupt

  • Comments, an aside
       - SML has one type of comment, similar to Java's /* */ comment
       - Comment start with (* and end with *)
       - They can span multiple lines
       - Commenting code:
          - You should have a comment at the top of any file you create explaining what's in there, your name, etc.
          - All functions should have comments above them explaining what that function does
          - If there are any complicated portions of the functions (or funny parts) you should also put a small comment to the side of that line
          - In general, SML tends to have less comments than other languages because the code is more compact and self-contained. BUT, you still should be putting in *some* comments