Как читать подписи типа F #?
Я борюсь с нотной подписью типа F #. Например, скажем, у вас есть функция Fold:
let rec Fold combine acc l =
...
который может иметь подпись этого типа:
('a -> 'b -> 'a) -> 'a -> list<'b> -> 'a
который я читал бы как
функция, имеющая три аргумента:
- функция, которая берет 'a, a' b и возвращает a '
- a 'a
- список 'b
и возвращает 'a.
Но тогда мозгу моего пещерного человека было бы разумнее выразить это как
('a, 'b -> 'a), 'a, list<'b> -> 'a
Я уверен, что существует семантическая причина, по которой параметры разделены стрелкой точно так же, как и тип возвращаемой функции, но каким-то образом я ее пропускаю и не нашел четкого объяснения в книгах/статьях до сих пор, Каждый раз, когда я вижу подпись типа, я должен немного задержаться, чтобы понять это. Я чувствую, что мне просто не хватает этой маленькой части головоломки, которая делает "дешифрование" очевидным.
Может кто-нибудь, пожалуйста, просветит меня?
Ответы
Ответ 1
Я уверен, что есть семантическая причина почему параметры разделены стрелки точно так же, как и возвращаемый тип функции, но как-то я пропустил его и не нашел ясного объяснение в книгах/статьях до сих пор.
Вы читаете первую функцию правильно. Для мгновенного дешифрования сигнатуры типов выражаются следующим образом:
val functionName = inputType1 -> inputType2 -> ... -> inputTypeN -> returnType
Как правило, обозначение стрелки указывает, что функция выполнена карри.
// val add4 : int -> int -> int -> int -> int
let add4 a b c d = a + b + c + d;;
// val f : (int -> int)
let f = add4 1 2 3 // returns (int -> int) waiting for last argument
Поскольку функция курсируется, вы можете технически написать ее следующим образом:
// val add4 : int -> int -> int -> int -> int
let add4 = (fun a -> (fun b -> (fun c -> (fun d -> a + b + c + d))));;
// val f : (int -> int)
let f = fun x -> add4 1 2 3 x
Если вы думаете об этом, подпись add4
эквивалентна этому:
val add4 : int -> (int -> (int -> (int -> int) ) )
Я считаю, что мы используем обозначение стрелки, потому что оно похоже на структуру функции, когда мы явно приводим аргументы, как показано выше.
Ответ 2
Подписи записываются таким образом из-за того, что называется Currying. Несколько более точный способ описания вашей функции состоит в том, что она принимает (функция, которая принимает 'a
и возвращает функцию от 'b
до 'a
) и возвращает функцию, которая принимает 'a
, и возвращает функции от list<'b>
до a 'a
. Из-за этого сигнатура типа может быть переписана как
('a -> 'b -> 'a) -> ('a -> (list<'b> -> 'a))
Ответ 3
Вы можете написать аналогичную функцию в F #, которая имеет тип, который вы предлагаете (но в F # он будет записан как ('a * 'b -> 'a) * 'a * list<'b> -> 'a
. Однако преимущество существующей функции в том, что ее легко частично применить только поставляя префикс аргументов. Например:
let sum = List.fold (+) 0
Используя ваше определение, вам нужно написать
let sum l = List.fold((fun (x,y) -> x + y), 0, l)
Ответ 4
Причиной этого является функциональное программирование. Каждая функция фактически имеет только один параметр.
Итак, скажем, у вас есть функция Sum as:
int -> int -> int
Он принимает 2 int и возвращает один int. Теперь, если вы вызываете эту функцию, просто передавая один int, вы не получите никакой ошибки компилятора, а возвращаемое значение будет иметь тип int → int. Таким образом, вы видите, что это обозначение стрелки соответствует этому поведению. Это поведение известно как Currying.
Отъезд: http://en.wikipedia.org/wiki/Currying