Что означает эта сигнатура функции в sml?

Я просматриваю некоторые заметки, которые мой профессор дал относительно языка SML, и одна из функций выглядит так:

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

Может ли кто-нибудь помочь объяснить, что это делает? Больше всего меня смущает строка:

    let fun lp curr [] = curr

Что именно это означает? Насколько я могу судить, есть функция с именем lp, но что означает curr []? Это аргументы? Если да, то разве вам не разрешен только один параметр в sml?


person Nosrettap    schedule 21.01.2013    source источник
comment
cs.princeton.edu/courses/archive/ fall08 / cos441 / notes / Это форма определения функции, которую вы будете видеть снова и снова в языках, основанных на ML, haskell, lisp: если вы нажали пустой список, функция будет выполнена, в противном случае обработайте заголовок список и рекурсивный   -  person Gene T    schedule 21.01.2013
comment
Не могли бы вы подробнее объяснить, какие параметры принимает функция lp и что такое curr? Является ли параметр кортежем (curr,someList)?   -  person Nosrettap    schedule 21.01.2013


Ответы (2)


Это означает, что lp - это функция, которая принимает 2 параметра, первый из которых curr, а второй, ну, список, который по логике может быть либо пустым ([]), либо содержать хотя бы один элемент ((a::l) - шаблон для списка, где a находится во главе, а остальная часть списка - l).

Если перевести этот фрагмент кода FP на известный императивный язык, это будет выглядеть так:

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)
    }
  }
}

Довольно громоздко, но это точный перевод.

Функциональные языки основаны на лямбда-исчислении, где функции принимают ровно одно значение и возвращают один результат. Хотя SML и другие языки FP основаны на этой теории, на практике это довольно неудобно, поэтому многие из этих языков позволяют выражать передачу нескольких параметров функции через так называемый Currying.

Итак, да, в ML функции фактически принимают только одно значение, но каррирование позволяет имитировать несколько аргументов.

Давайте создадим функцию с именем add, которая складывает 2 числа:

fun add a b = a + b

должен это сделать, но мы определили 2 параметра. Какой тип add? Если вы посмотрите в REPL, это val add = fn : int -> int -> int. Что гласит: "add - это функция, которая принимает int и возвращает другую функцию (которая принимает int и возвращает int)"

Таким образом, мы могли бы определить add так:

fun add a = 
  fn b => a + b

И вы увидите, что они похожи. Фактически, можно с уверенностью сказать, что в некотором смысле первое является синтаксическим сахаром для более позднего. Таким образом, все функции, которые вы определяете в ML, даже с несколькими аргументами, на самом деле являются функциями с одним аргументом, которые возвращают функции, которые принимают второй аргумент, и так далее. Сначала к этому немного сложно привыкнуть, но очень скоро это становится второй натурой.

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

Что add1? Это функция, которая добавит 1 к единственному аргументу, который вы ей передаете!

add1 2 (* returns 3 *)

Это пример частичного приложения, где вы вызываете функцию по частям, по одному аргументу за раз, каждый раз возвращаясь, другая функция принимает остальные аргументы.

Кроме того, есть еще один способ создать несколько аргументов: кортежи:

(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 *)

В вашем вопросе lp мог также быть реализован как 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

Обратите внимание, что в этом случае мы должны объявить max как max gt curr lst!

В опубликованном вами коде lp явно был реализован с каррированием. А сам тип max был fn: ('a * 'a -> bool) -> 'a -> 'a list -> 'a. Разобрав это на части:

('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) *)

Обратите внимание на тип gt, первый аргумент max: fn : (('a * 'a) -> bool) - это функция одного аргумента ('a * 'a), кортежа из двух 'a, и он возвращает 'a. Так что карри здесь нет.

Что использовать - дело вкуса, условностей и практических соображений.

Надеюсь это поможет.

person Faiz    schedule 21.01.2013
comment
Если это не слишком много работы, не могли бы вы показать мне, как lp выглядел бы, если бы он был реализован как lp (curr, someList)? Спасибо - person Nosrettap; 21.01.2013
comment
В ML? Только что добавил. Надеюсь, это поможет. - person Faiz; 21.01.2013
comment
@Faiz Фактически, можно с уверенностью сказать, что в некотором смысле первое является синтаксическим сахаром для более позднего. Это действительно производная форма, хотя та, которую вы представили, не раскрывается полностью до эквивалентной формы. - person Jesper.Reenberg; 21.01.2013
comment
Ах, вы имеете в виду val add = fn a => fn b => a + b? Да, мне следовало бы добавить это для полноты картины! Спасибо! - person Faiz; 21.01.2013

Просто чтобы немного прояснить каррирование из отличного ответа Фаиза.

Как указывалось ранее, SML позволяет функциям принимать только 1 аргумент. Причина этого в том, что функция fun foo x = x на самом деле является производной формой (синтаксического сахара) val rec foo = fn x => x. На самом деле это не совсем так, но давайте на секунду упростим

Теперь возьмем, к примеру, эту степенную функцию. Здесь мы объявляем функцию "принимать два аргумента"

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

Как указано выше, fun ... была производной формой и, следовательно, эквивалентной формой степенной функции является

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

Как вы можете видеть здесь, у нас есть проблема с выражением двух разных совпадений шаблонов в исходном объявлении функции, и поэтому мы не можем больше сохранять его "простым", и реальная эквивалентная форма объявления fun

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

Для полноты картины case на самом деле также является производной формой с анонимными функциями в качестве эквивалентной формы.

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

Обратите внимание, что (n,k) применяется непосредственно к самой внутренней анонимной функции.

person Jesper.Reenberg    schedule 21.01.2013