CS52 - Spring 2016 - Class 7

Example code in this lecture


Lecture notes

  • admin
       - assignment 3 out
          - due next Monday at 11:59pm
          - start now... this is a challenging (and long) assignment
          - mentor hours (go to the early ones!)
          - my office hours may move for Wednesday

  • 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)

  • order
       - the isOlder function highlights that there are really three cases when we're comparing values of the same type
          - older, younger and the same age
       - there is a built-in type in SML called "order" that generalizes this representation
       - order can take on three values:
          - LESS
          - EQUAL
          - GREATER
       - typing in any of these you'll get the order type
          > EQUAL;
          val it = EQUAL : order
          > LESS;
          val it = LESS : order

       - how do you think order is define?
          - just another datatype!
          datatype order = LESS | EQUAL | GREATER

       - look at the compareAge function in student.sml code
          - what does it do? what is its type signature?
             val compareAge = fn: student -> student -> order
          - compares two students and returns whether the first is older (GREATER), younger (LESS) or the same age (EQUAL) than the second
             > compareAge (Firstyear "Dave") (Sophomore "Lucy");
             val it = LESS : order
             > compareAge (Senior "Dave") (Senior "Lucy");
             val it = EQUAL : order

  • cs52int
       - For assignment three, we're representing numbers as lists to be able to support very large numbers and numbers in different bases
       - We're going to assume the lists represent positive numbers and then encapsulate functionality that operates on lists inside a dataype
          datatype cs52int = Pos of int list |
           Zero |
           Neg of int list;

          - three different value types
             - Zero (representing the number 0)
             - Pos and Neg representing numbers that are positive and negative
                - not that the list "in" these values will always be positive

       - In base 10, how would we write:
          - 21?
             > Pos [1, 2];
             val it = Pos [1,2] : cs52int
          - -23
             > Neg [3, 2];
             val it = Neg [3,2] : cs52int
          - 0
             > Zero;
             val it = Zero : cs52int

       - write a function called isPositive that takes as input a cs52int
          - type signature?
             val isPositive = fn: cs52int -> bool

          - look at isPositive function in cs52int_examples.sml code
             - could pattern match against all three value types, but don't need to!
             - second pattern catches anything (i.e. Zero and Neg)

       - write a function called isNonzero that takes as input a cs52int
          - type signature?
             val isNonzero = fn: cs52int -> bool
          - look at isNonzero function in cs52int_examples.sml code
             - could pattern matc

       - write a function called numDigits that takes as input a cs52int and returns how many digits are in the number (in it's base)
          - type signature?
             val numDigits = fn: cs52int -> int

          - look at numDigits function in cs52int_examples.sml code
             - In this case we do need to pattern match against all values
                - why can't we write:
                   fun numDigits Zero = 0
                    | numDigits x = length x

                   - length is defined over lists
                   - x here would have type cs52int and you would get an error!