Just_Alex
Just_Alex

Reputation: 558

How does SML pattern matching records work

Disclaimer, this is part of an online course, but I have solved the homework. My question is about the pattern matching of records work?

So in the first function I do not have to specify the structure of the record

fun generateName (ls , name)=
case ls of
[] => name::[]
  | x::xs =>
{
  first= x,
  middle= #middle name,
  last= #last name
}
:: generateName(xs, name);

In this function I do. Otherwise I get a flex record error.

fun createName (f, name :{first:string, middle:string, last:string}) =
case f of
x =>
{
  first= x,
  middle= #middle name,
  last= #last name
}

Even more confusingly this one gives no error

fun generateName2 (nms, name) =
let fun aux (ls, name, acc) =
    case ls of
        [] => name::acc
      | x::xs =>
        aux(xs, name, 
        {
          first= x,
          middle= #middle name,
          last= #last name
        } :: acc)
in
aux (nms, name, [])
end

When do I have to specify the record fields?

Upvotes: 1

Views: 388

Answers (2)

Chris
Chris

Reputation: 36581

The answer from @molbdnilo is quite good. Because I firmly believe that fully taking advantage of the niceties of programming languages can make for code that's easier to reason about, let's clean up your code examples a bit. In theory this belongs in a comment, but that's not very well-suited to this level of commentary.

I hope the ideas shown here can help you. Good learning!

fun generateName (ls , name)=
case ls of
[] => name::[]
  | x::xs =>
{
  first= x,
  middle= #middle name,
  last= #last name
}
:: generateName(xs, name);

When we see a case as the first thing in a function body, and it's simply acting on one of the arguments, we can use pattern matching in the function signature itself.

fun generateName([] , name) = name :: []
  | generateName(x::xs, name) = 
      { first = x,
        middle = #middle name,
        last = #last name } :: generateName(xs, name);

We can also use pattern-matching further to eliminate the need to extract the record fields inside the function body.

fun generateName([] , name) = name :: []
  | generateName(x::xs, name as {first:string, middle:string, last:string}) = 
      { first = x,
        middle = middle,
        last = last } :: generateName(xs, name);

In your second example, pattern matching an argument with just one catch-all pattern is pointless. We can get rid of that.

fun createName (f, name :{first:string, middle:string, last:string}) =
  { first = f,
    middle = #middle name,
    last = #last name }

And we don't have to specify the type, if we offer it up as a pattern.

fun createName (f, {first:string, middle:string, last:string}) =
  { first = f,
    middle = middle,
    last = last }

Your third code sample can benefit from these ideas as well.

fun generateName2(nms, name) =
  let 
    fun aux([], name, acc) = name :: acc
      | aux(x::xs, name as { first:string, middle:string, last:string }, acc) =
          aux(xs, name, 
              { first = x,
                middle = middle,
                last = last } :: acc)
  in
    aux(nms, name, [])
  end

Upvotes: 1

molbdnilo
molbdnilo

Reputation: 66371

SML does not have row polymorphism, so the compiler needs to know all field names of a record.

In the functions that return lists, the recursion limits the possible types; if name has the type R, generateName (ls , name) has the type R list, and all the field names can be inferred from the non-empty recursive case.

The middle example does not have such a relationship between input and output, so unless you specify the input field names, SML does not know what they are.

Upvotes: 3

Related Questions