Это определение bind
является функциональным парадоксом.Это противоречит самому себе.
let rec bind a f = f a |> bind
// val bind: (a:'a) -> (f:'a->'a) -> 'b
Лучший способ увидеть это - добавить аннотации некоторых типов, давайте заменим 'a
на int
, чтобы сделать его более конкретным:
let rec bind (a:int) (f:int->int) = f a |> bind
// val bind:(a:int) -> (f:int->int)-> 'a
bind
получает число и функцию, которая принимает число и возвращает другое, но что возвращает bind
?Система не знает, потому что она никогда ничего не возвращает, она только углубляется и углубляется в другой уровень каррирования.Это само по себе не проблема, F # может обрабатывать подпрограммы, которые никогда не завершаются, например:
let rec loop() = printfn "Hello" ; loop()
// val loop : unit -> 'a
Фактически вы можете аннотировать loop
любым типом, и F # будет в порядке с этим:
let rec loop() : float = printfn "Hello" ; loop()
// val loop : unit -> float
Но если мы сделаем то же самое с bind
, противоречие станет очевидным:
let rec bind (a:int) (f:int->int) : string = f a |> bind
// val bind:(a:int) -> (f:int->int)-> string
Сообщение об ошибке гласит:
Expecting a 'int -> string' but given a 'int -> (int -> int) -> string'
Почему оно ожидает int -> string
?Потому что это то, что говорит тип.Мы знаем, что f a
возвращает int
, и мы передаем его bind
, и мы должны получить string
, потому что это конечный результат функции.Но bind
не возвращает string
, когда передается только один параметр, вместо этого он возвращает функцию типа (f:int->int)-> string
, в этом заключается противоречие.Левая сторона (=
) говорит, что bind
возвращает string
при получении 2 параметров, но правая сторона говорит, что она возвращает 'строку' при получении 1 параметра.Это парадокс, например, утверждение «Я всегда лгу».
Если мы вернемся к исходному определению без аннотаций типов, мы увидим, что предполагаемый тип результата привязки равен 'b
, что означает любой тип,но это должен быть один конкретный тип, а не много типов или тип, который изменяется при каждом вызове.В частности, 'b
не может быть равным ('a->'a) -> 'b
, поскольку включает 'b
и, следовательно, является круговым определением (или, возможно, эллиптическим), следовательно, бесконечный цикл.
Для Javascript это не проблема, потому что этоне заботится о том, какие типы передаются или возвращаются из функций.Именно эта ключевая особенность делает работу с F # намного лучше, чем с Javascript.