Reputation: 558
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
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
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