Search code examples
pattern-matchingsmlsmlnj

Pattern Matching a List in SML


Lets say I create a student record object which is a tuple that consists of (studentID,name,midtermScore,finalScore). I can then use pattern matching to create functions that use this object. For example, a function that returns a composite score based on the student record:

fun score (studentID,name,midtermScore,finalScore) = 
     ( 0.4 * (midtermScore) ) + ( 0.6 * (finalScore) )

Now lets say I wanted to create another function which operates on a whole list of these student record objects, this function would take such a list, and return a new list containing the studentID of each record object, and its composite score. For example:

fun scores ( [studentID,name,midtermScore,finalScore] ) = 
     map(fn x => (studentID,score(x)))

I can also implement this function in other ways syntactically which also use pattern matching, but the problem I'm having is while the code compiles, it never generates the bindings I'm looking for. For example, the above scores function generates these bindings:

val scores = fn : 'a list -> ('b * 'c * real * real) list -> ('a * real) list

Whereas what I'm trying to achieve is this:

val scores = fn : ('a * 'b * real * real) list -> ('a * real) list

I know whats causing this discrepancy is in the way that I'm pattern matching the list of student record objects as a parameter to the scores function.

Could someone explain semantics wise why I'm getting the bindings that I'm getting, and how I would need to modify the scores function in order to generate the desired bindings?


Solution

  • Now lets say I wanted to create another function which operates on a whole list of these student record objects, this function would take such a list, and return a new list containing the studentID of each record object, and its composite score. For example:

    fun scores ( [studentID,name,midtermScore,finalScore] ) = 
        map(fn x => (studentID,score(x)))
    

    That's not how pattern matching on lists works.

    First, here is how I might represent students as records with named fields (records) rather than numbered fields (tuples):

    datatype student = Student of {
      studentID : int,
      name : string,
      midtermScore : real,
      finalScore : real
    }
    

    It isn't strictly necessary to use a datatype, you could also write type student = { ... }.

    Then writing a few helper functions that were avoidable if I weren't using datatype, because then I could simply use #name as I would be able to write #1, #2 for accessing numbered tuple fields:

    fun studentScore (Student { midtermScore = midtermScore, finalScore = finalScore, ... }) =
        (0.4 * midtermScore) + (0.6 * finalScore)
    
    fun studentName (Student { name = name, ... }) = name
    

    I am able to generate a list of (name, score) tuples using map:

    fun studentScores students =
      map (fn student => (studentName student, studentScore student)) students
    

    If you simply wanted to stick to tuples and do recursion and pattern matching on lists, you could do:

    fun studentScore (_, _, midtermScore, finalScore) =
        (0.4 * midtermScore) + (0.6 * finalScore)
    
    fun studentName (_, name, _, _) =
        name
    
    fun studentScores students =
      map (fn student => (studentName student, studentScore student)) students
    

    You will notice that with the right amount of abstraction, the implementation may not matter much.