Параметры функции SML: кортеж, карри, синонимы типа - PullRequest
0 голосов
/ 21 декабря 2018

Я начинаю с этого

type int_pair = int * int

, а затем получаю эти

fun sip1 ((i,j) : int_pair) = (j,i)
fun sip1a (ip : int_pair) = (#2 ip, #1 ip)
fun sip2 (ip : int*int) = (#2 ip, #1 ip)
fun sip3 (i : int, j : int) = (j,i)

с этими результатами

: val sip1 = fn : int_pair -> int * int
: val sip1a = fn : int_pair -> int * int
: val sip2 = fn : int * int -> int * int
: val sip3 = fn : int * int -> int * int

Все они работают.Что меня озадачивает, так это то, как sip1 и sip1a могут быть настроены так, чтобы принимать одну int_pair переменную ip или как (i,j) как кортеж.Как он может обрабатывать оба из-за синонима типа?Я просто предполагаю, что любой тип (a,b) в теле будет по умолчанию набирать int*int при возврате, но также в выражении (j,i) из sip1, похоже, нет необходимости ссылаться (#1 ...) sip1a.Довольно странно на первый взгляд.sip2 кажется прямо.Но тогда в sip3 казалось бы, что просто два int входят и получается результат типа int*int;но тип функции говорит, что это int * int -> int * int.Почему это не тип int -> int -> int*int, как я мог видеть на других языках?Итак, почему ML обрабатывает несколько входящих переменных как кортеж?

1 Ответ

0 голосов
/ 24 декабря 2018

В ML все функции принимают ровно один аргумент.Но когда нам нужно отправить более одного аргумента, ML интерпретирует аргументы в традиционных скобках как один кортеж , в соответствии с правилом только один аргумент:

fun swap1 (i,j) = (j,i)
: val swap1 = fn : 'a * 'b -> 'b * 'a

ДваВышеуказанные аргументы, разделенные *, означают, что они являются двухартериальным кортежем.

- swap1 (1,2);
val it = (2,1) : int * int

Даже если переменные кажутся отдельными, они все равно считаются одним аргументом, опять же, кортежем из 2 арностей:

fun swap1a (i : int, j : int) = (j,i)
: val swap1 = fn : int * int -> int * int

Если мы создаем синоним типа с type, например,

type int_pair = int * int

функции, использующие int_pair, все еще печатаются как кортежи:

fun swap2 ((i,j) : int_pair) = (j,i)
: val swap2 = fn : int_pair -> int * int
fun swap2a (ip : int_pair) = (#2 ip, #1 ip)
: val swap2a = fn : int_pair -> int * int
fun swap2b (ip : int*int) = (#2 ip, #1 ip)
: val swap2b = fn : int * int -> int * int

MLтакже имеет функции curry , где множественные аргументы curried , что в основном означает, что аргументы принимаются по одному за раз, что, опять же, относится к правилу «только один аргумент».Карринг позволяет нам частично применять функцию к некоторым ее аргументам, оставляя остаточную функцию, которая может быть дополнительно оценена позже.Специальный синтаксис ML для функции, которая использует каррирование, состоит в том, чтобы просто иметь аргументы без заключенных в скобки и разделенных пробелом:

fun swap3 i j = (j,i)
: val swap3 = fn : 'a -> 'b -> 'b * 'a

- swap3 1 2;
val it = (2,1) : int * int

Другая визуализация заключается в создании swap3 с анонимными функциями:

- val swap3a = fn i => fn j => (j,i)
- swap3a 1 2;
val it = (2,1) : int * int

Теперь мы можем представить входящее 1 2, разрешаемое справа налево (по иронии судьбы левостороннее ) ля лямбда-исчисление:

(fn i => fn j => (j,i))(1,2)
(fn i => (2,i))(1)
(2,1)

Обратите внимание, что fn i =>... на самом деле принимает функцию в качестве входа , в этом примере выражение анонимной функции fn j =>..., которое имеет значение (2,i).Фактически, дух этого можно сделать в REPL:

- ((fn i => fn j => (j,i)) 1) 2;
val it = (2,1) : int * int

Как говорит комментатор Андреас Россберг (см. Комментарии выше), в большинстве функциональных языков функции с несколькими аргументами используют каррирование.Он отмечает, что использование кортежей является декартовым в смысле декартового произведения.Он также отмечает, что это ортогонально , что в этом контексте может означать, что эти два подхода к обработке нескольких аргументов, кортежа и карри, ортогональны друг к другу, поскольку они полностьюпротивоположные, не перекрывающиеся подходы, которые семантически не мешают друг другу.

...