问题
I found this code on another SO post:
fun number_in_month ([], _) = 0
| number_in_month ((_,x2,_) :: xs, m) =
if x2 = m then
1 + number_in_month(xs, m)
else
number_in_month(xs, m)
and to my surprise it works.
- number_in_month ([(2018,1,1),(2018,2,2),(2018,2,3),(2018,3,4),(2018,2,30)],2);
val it = 3 : int
My confusion is first unfamiliarity with this form of classic mathematical recursive function (I'm a beginner), then how it actually steps through the list. My intuition would have the recursive calls in the if-then-else
sending the tail of the list, i.e.,
...
1 + number_in_month((tl xs), m)
...
but that doesn't work. How is it iterating through the list with each recursive call? I can only imagine this is baked-in SML magics of some sort.
回答1:
No magic, xs
is the tail of the list.
There are two things to understand: lists and pattern matching.
In SML, the list syntax [a, b, c]
is just a shorthand for a :: b :: c :: nil
, where ::
is the (infix) cons constructor. Other than this shorthand, there is nothing magic about lists in SML, they are pre-defined as this type:
datatype 'a list = nil | :: of 'a * 'a list
infixr 5 ::
The latter definition turns ::
into a right-associative infix operator of precedence 5.
Secondly, the definition is using pattern matching on the argument. A patten like x::xs
matches a (non-empty) list of the same shape, binding x
to the head of the list and xs
to its tail, corresponding to the definition above. In your function, x
furthermore replaced by another pattern itself.
That's all. No magic. This would equally work with a custom list representation:
datatype my_list = empty | cons of (int * int * int) * my_list
infixr 5 cons
fun count (empty, x) = 0
| count ((_,y,_) cons xs, x) =
if x = y then 1 + count (xs, x) else count (xs, x)
val test = count ((1,2,3) cons (3,4,5) cons (6,2,7) cons empty, 2)
回答2:
But how could this be changed to, say, build a new list of matches rather than just counting them?
In that case, you want two modifications to your current solution:
You want to change the pattern of your recursive case to one where you can extract the entire date 3-tuple if it matches. Right now you're only extracting the month part for comparison, throwing away the other bits, since you just want to increment a counter in case the month matches.
The result of the function should not be
1 + ...
, but rather(x1,x2,x3) :: ...
.
So a quick fix:
fun dates_of_month ([], _) = []
| dates_of_month ((year,month,day) :: dates, month1) =
if month = month1
then (year,month,day) :: dates_of_month (dates, month1)
else dates_of_month (dates, month1)
I changed
...((_,x2,_) :: xs, m)
to...((x1,x2,x3) :: xs, m)...
and it worked, but that seems like a kludge.
Here are Andreas Rossberg's two alternatives spelled out:
Using let-in-end:
fun dates_of_month ([], _) = []
| dates_of_month (date :: dates, month1) =
let val (_, month, _) = date
in
if month = month1
then date :: dates_of_month (dates, month1)
else dates_of_month (dates, month1)
end
Using as
:
fun dates_of_month ([], _) = []
| dates_of_month ((date as (_,month,_)) :: dates, month1) =
if month = month1
then date :: dates_of_month (dates, month1)
else dates_of_month (dates, month1)
And here is a third option that abstracts out the recursion by using a higher-order list combinator:
fun dates_of_month (dates, month1) =
List.filter (fn (_, month, _) => month = month1) dates
来源:https://stackoverflow.com/questions/56204390/number-in-month-exercise-sml-recursive-function-on-list-interation