CS52 - Spring 2017 - Class 6

Example code in this lecture

   directions.sml
   student.sml

Lecture notes

  • admin
       - assignment 2 due tomorrow at 5pm
       - assignment 3 out soon

  • midterm
       - logistics
          - in class on Tuesday
          - pencil and paper exam (practice writing a few functions in SML on paper)
          - closed books, notes, computer, etc.
          - *except*, can bring one piece of standard sized paper, single sided, with notes
       - Coverage: SML
          - evaluate the function/what does the function do
          - type signature
          - code up functions
       - Extra mentors on Sunday and Monday for review support
       - Will post *some* samples problems
          - Intro SML reading and books also have others

  • academic honesty

  • quick poll
       - CS62 previously?
       - CS62 next semester?
       - CS55 or math103 previously?
       - CS55 next semester?

  • recursion patterns

  • difference between [] and [[]]

  • decimal numbers
       - most commonly used number systems have a "base"
          - each digit in the number can range from 0...base-1
          - each digit in the number has an index, starting at the right-most digit with 0 and working up to the left
          - the contribution of the digit is:

             base^index * value

          - by summing up the contribution of all of the digits, we get the value of the number
       
       - what is the base for our numbering system?
          - 10

          - for example
             - 245 = 5*10^0 + 4*10^1 + 2*10^2
             - 80498 = 8*10^0 + 9*10^1 + 4*10^2 + 0*10^3 + 8*10^4

  • other bases
       - we can represent values by any base we'd like!
       - A common one is CS, base 2, binary numbers:
          - digits can only be a 0 or a 1

          - for example, the following binary numbers:
             - 101 = 1*2^0 + 0*2^2 + 1*2^2 = 5
             - 11 = 1*2^0 + 1*2^1 = 3
             - 10111 = 1*2^0 + 1*2^1 + 1*2^2 + 0*2^3 + 1*2^4 = 23
             - 100001 = 1*2^0 + 1*2^5 = 33
       - we can use any base we'd like, though
          - base 9
             - digits are numbers from 0 through 8
             - 267 = 7*9^0 + 6*9^1 + 2*9^2 = 7 + 54 + 162 = 223
          - base 200
             - digits are numbers from 0 through 199
             - 267 = 7*200^0 + 6*200^1 + 2*200^2 = 81,207
                - not that even numbers written the same way can be interpreted differently!
             - 2 55 167 = 167*200^0 + 55*200^2 + 2*200^2 = 91,167


  • types seen so far
       - basic types: int, bool, real, char, string
       - compound types: lists and tuples
          - combine existing types to make a new type

  • we can define our own types!
       - simplest version: an enumeration type that can take a fixed set of values
       
       datatype <name_of_type> = Option1 | Option2 | ...;

          - <name_of_type> is the new type's name
             - by convention, this will be lowercased
          - Options are the different values is can take (called "constructors")
             - by convention, these will be capitalized (but not all caps)

       - define a new datatype direction that can have four possible values:

          > datatype direction = North | South | East | West;
          datatype direction = East | North | South | West
          
          - we can now use any of these for "values" and SML will know what type they are

             > North;
             val it = North : direction
             > East;
             val it = East : direction

             - notice that SML figures out that they are of type "direction"
             - these are literal values, like writing 0 or 1

       - we can also use these inside functions, for example the turnRight and turnLeft functions in directions.sml code
          - we must pattern match on *all* of the different values
             - if we don't, we would get non-exhaustive search
          - What will be the type signature?
             - fun rotateRight = fn: direction -> direction
             - direction is just like any other type
          - We can call this function and pass it any direction and it will give us back another direction:
             > turnRight North;
             val it = East : direction
             > turnRight West;
             val it = North : direction
             > turnRight (turnRight North);
             val it = South : direction

       - write a function called isVerticalDirection that takes as input a direction returns true of the direction is a vertical direction (North/South) and false otherwise
          - What will be the type signature?
             val isVerticalDirection = fn: direction -> bool

          - look at isVerticalDirection function in directions.sml code
             - We could have done an exhaustive list, but instead use x to capture East and West
                 - we can do this since the patterns are matched from the top down

  • types that hold values
       - the basic datatype declaration is useful in some cases, but is fairly limited in that we have to explicitly define all the values
       - we can also construct datatypes whose values can take arguments!
          
          datatype <name_of_type> = <constructor_expression1> | <constructor_expression2> | ...

          where <constructor_expression> looks like:
             <name> of <type>

             (or is also just a zero parameter constructor like above, e.g. North)

       - for example, look at the student type declaration in student.sml code
          - four different types of values
             - Firstyear, Sophomore, Junior, Senior
             - each type also can be instantiated with a string value

          > Firstyear "Dave";
          val it = Firstyear "Dave" : student
          > Sophomore "Lucy";
          val it = Sophomore "Lucy" : student

          - by creating the type, we have created 4 "constructors" of the type
             - just like in Java, constructors are used to create objects/values

       - This allows us to write functions that can then access the values
       - Look at the studentToString function in student.sml code
          - Again, we pattern match against the four type values
          - The difference is that if we want to get at the value inside, we pattern match the constructor
             - this is similar to getting at the head of the list or getting at the values in a tuple
          
          - what is the type signature of studentToString?
             - val studentToString = fn: student -> string

             > studentToString (Firstyear "dave");
             val it = "First year dave" : string

       - write a function isGraduating that takes a student and returns true if that student is graduating (i.e is a senior) and false otherwise
          - type signature?
             val isGraduating = fn: student -> bool

          - look at isGraduatingLong function in student.sml code
             - again, pattern match agains the constructors
             - don't really need x at all (could have just written _ here)

          - is there a better (more succinct) way of writing this function?
          
          - look at isGraduating function in student.sml code
             - pattern match against Senior
             - and then just x as a catchall for everything else
                - notice that if you don't need to get at the value you can just match it to a variable

       - look at the funFacts function in student.sml code
          - what does it do? what is its type signature?
             - val funFacts = fn: student -> string
             
             - how does SML know that the input type is of type student?
                - because the input type of both isGraduating and studentToString is student

             - takes in a student and produces a string describing that student

             > funFacts (Firstyear "dave");
             val it = "First year dave has more college fun coming!" : string
             > funFacts (Senior "lucy");
             val it = "Senior lucy is graduating!" : string
          
          - notice that we do NOT always need to pattern match against the type

       - write a function called isOlder that takes as input *two* students and returns true if the first student is strictly older than the second (academically speaking)
          - what is the type signature?
             val isOlder = fn: student -> student -> bool

          - how many different combinations might we need to check?
             - each input parameter can take on the four different years, so 4*4 = 16!

          - look at isOlder function in student.sml code
             - With a bit of smart checking, we're able to reduce it to 8 patterns that we need to check
                - Firstyear isn't older than anyone (pattern 1)
                - Sophomores are older than first years (pattern 2), but no one else (pattern 3)
                - Juniors are older than first years (pattern 4) and sophomores (pattern 5), but no one else (pattern 6)
                - Seniors are not strictly older than other seniors (pattern 7), but are than everyone else (pattern 8)