What does this function signature mean in sml?

自作多情 提交于 2019-12-19 04:35:28

问题


I'm looking through some notes that my professor gave regarding the language SML and one of the functions looks like this:

fun max gt = 
    let fun lp curr [] = curr
           | lp curr (a::l) = if gt(a,curr)
                             then lp a l
                             else lp curr l
in
    lp
end

Could someone help explain what this is doing? The thing that I am most confused about is the line:

    let fun lp curr [] = curr

What exactly does this mean? As far as I can tell there is a function called lp but what does the curr [] mean? Are these arguments? If so, aren't you only allowed one parameter in sml?


回答1:


It means that lp is a function that takes 2 parameters, the first being curr and the second being, well, a list, which logically, may be either empty ([]) or contain at least one element ((a::l) is a pattern for a list where a is at the head, and the rest of the list is l).

If one were to translate that bit of FP code into a certain well-known imperative language, it would look like:

function lp(curr, lst) {
  if (lst.length == 0) {  
    return curr;
  } else {
    var a = lst[0];                   // first element
    var l = lst.slice(1, lst.length); // the rest
    if (gt(a, curr)) {
      return lp(a, l);
    } else {
      return lp(curr, l)
    }
  }
}

Quite a mouthful, but it's a faithful translation.

Functional languages are based on the Lambda Calculus, where functions take exactly one value and return one result. While SML and other FP languages are based on this theory, it's rather inconvenient in practice, so many of these languages allow you to express passing multiple parameters to a function via what is known as Currying.

So yes, in ML functions actually take only one value, but currying lets you emulate multiple arguments.

Let's create a function called add, which adds 2 numbers:

fun add a b = a + b

should do it, but we defined 2 parameters. What's the type of add? If you take a look in the REPL, it is val add = fn : int -> int -> int. Which reads, "add is a function that takes an int and returns another function (which takes an int and returns an int)"

So we could also have defined add this way:

fun add a = 
  fn b => a + b

And you will see that they are alike. In fact it is safe to say that in a way, the former is syntactic sugar for the later. So all functions you define in ML, even those with several arguments, are actually functions with one argument, that return functions that accept the second argument and so on. It's a little hard to get used to at first but it becomes second nature very soon.

fun add a b = a + b  (* add is of type  int -> int -> int *)

add 1 2 (* returns 3 as you expect *)

(* calling add with only one parameter *)

val add1 = add 1

What's add1? It is a function that will add 1 to the single argument you pass it!

add1 2 (* returns 3 *)

This is an example of partial application, where you are calling a function piecemeal, one argument at a time, getting back each time, another function that accepts the rest of the arguments.

Also, there's another way to give the appearance of multiple arguments: tuples:

(1, 2);     (* evaluates to a tuple of (int,int) *)

fun add (a,b) = a + b;

add (1, 2)  (* passing a SINGLE argument to a function that
               expects only a single argument, a tuple of 2 numbers *)

In your question, lp could have also been implemented as lp (curr, someList):

fun max gt curr lst = 
    let fun lp (curr, []) = curr
          | lp (curr, (a::l)) = if gt(a,curr) then lp (a, l)
                                else lp (curr, l)
in
    lp (curr, lst)
end

Note that in this case, we have to declare max as max gt curr lst!

In the code you posted, lp was clearly implemented with currying. And the type of max itself was fn: ('a * 'a -> bool) -> 'a -> 'a list -> 'a. Taking that apart:

('a * 'a -> bool) ->  (* passed to 'max' as 'gt' *)   
    'a ->             (* passed to 'lp' as 'curr' *)
       'a list ->     (* passed to 'lp' as 'someList' *)
          'a          (* what 'lp' returns (same as what 'max' itself returns) *)

Note the type of gt, the first argument to max: fn : (('a * 'a) -> bool) - it is a function of one argument ('a * 'a), a tuple of two 'a's and it returns an 'a. So no currying here.

Which to use is a matter of both taste, convention and practical considerations.

Hope this helps.




回答2:


Just to clarify a bit on currying, from Faiz's excellent answer.

As previously stated SML only allows functions to take 1 argument. The reason for this is because a function fun foo x = x is actually a derived form of (syntactic sugar) val rec foo = fn x => x. Well actually this is not entirely true, but lets keep it simple for a second

Now take for example this power function. Here we declare the function to "take two arguments"

fun pow n 0 = 1 
  | pow n k = n * pow n (k-1)

As stated above, fun ... was a derived form and thus the equivalent form of the power function is

val rec pow = fn n => fn k => ...

As you might see here, we have a problem expressing the two different pattern matches of the original function declaration, and thus we can't keep it "simple" anymore and the real equivalent form of a fun declaration is

val rec pow = fn n => fn k =>
    case (n, k) of 
      (n, 0) => 1
    | (n, k) => n * pow n (k-1)

For the sake of completeness, cases is actually also a derived form, with anonymous functions as the equivalent form

val rec pow = fn n => fn k => 
    (fn (n,0) => 1
      | (n,k) => n * pow n (k-1)) (n,k)

Note that (n,k) is applied directly to the inner most anonymous function.



来源:https://stackoverflow.com/questions/14432696/what-does-this-function-signature-mean-in-sml

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!