Как читать подписи типа 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