Обработка «Результирующий тип будет бесконечным при объединении» - PullRequest
0 голосов
/ 11 октября 2018

let rec bind x f = f x |> bind bind "Hello World" (fun x -> x.toUpper()) printf "%s"

Приведенный выше фрагмент кода приводит к этой ошибке «Получающийся тип будет бесконечным при объединении».

Предложение компилятора: «Поиск объекта неопределенного типа на основеинформация до этой программной точки. Для ограничения типа объекта может потребоваться аннотация типа до этой программной точки. Это может позволить разрешить поиск. "

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

Это может быть полезно.Поскольку Javascript более простителен и не жалуется, вы можете посмотреть на рабочий пример того же фрагмента кода, переведенного на JS.

const bind = x => f => bind(f(x))

bind("Hello World")
(x => x.toUpperCase())
(console.log)

Ответы [ 3 ]

0 голосов
/ 11 октября 2018

Резюме: вы не можете сделать это, но эта функция бесполезна в F #;вместо этого просто используйте оператор |>.

Более длинная версия: нет способа аннотировать описанную вами функцию bind способом, который удовлетворит компилятор F #.Когда я вставляю let rec bind x f = f x |> bind в F # Interactive, он выдает мне следующую ошибку:

error FS0001: Type mismatch. Expecting a
    ''a -> 'b'    
but given a
    ''a -> ('a -> 'a) -> 'b'    
The types ''b' and '('a -> 'a) -> 'b' cannot be unified.

Если мы немного изменим определение, чтобы оно выглядело как let rec bind x f = bind (f x), мы получимнемного упрощенная ошибка типа:

error FS0001: Type mismatch. Expecting a
    ''a'    
but given a
    ''b -> 'a'    
The types ''a' and ''b -> 'a' cannot be unified.

С небольшим намеком на тип (let bind (x : 'x) (f : 'f) = ...) мы получаем ошибку, что типы 'a и 'f -> 'a не могут быть объединены,так становится понятно, что происходит.'a - это тип возвращаемого значения bind (при отсутствии каких-либо имен для универсальных типов F # назначает их, начиная с 'a).Теперь давайте посмотрим, почему происходит эта ошибка типа.

Похоже, что вы уже знаете о частичном применении: любая функция с двумя аргументами, если дан один аргумент, возвращает функцию, которая ждет своего второго аргумента перед вычислениемтело функции.Другими словами, let f a b = ... в F # эквивалентен JavaScript const f = a => b => ....Здесь функция bind, когда ей передан один аргумент x, возвращает функцию, которая ждет f, прежде чем вычислить тело bind.Это означает, что когда bind передается один параметр, его тип возвращаемого значения равен 'f -> 'a (где 'a - это, как мы уже говорили, имя, которое компилятор F # произвольно присвоил результату bind).

Однако здесь возникает конфликт типов: значение bind (f x), которое, как мы уже говорили, имеет значение 'f -> 'a, также является результатом вашей bind функции .Это означает, что он должен иметь тип 'a.Таким образом, компилятору F # потребуется скомпилировать эту функцию таким образом, чтобы тип 'a был того же типа, что и 'f -> 'a.Если это было возможно, то в алгебре типа 'a = 'f -> 'a, и вы могли бы затем расширить 'a в правой части этого уравнения до 'f -> 'a, чтобы уравнение стало 'a = 'f -> ('f -> 'a).Теперь вы можете снова расширить 'a, получив 'a = 'f -> ('f -> ('f -> 'a)).И так до бесконечности.Компилятор F # не допускает бесконечно расширяющихся типов, поэтому это недопустимо.

Но, как я уже указывал (и объяснил Томас Петричек), вам на самом деле не нужна эта bind функция в F #.Все это - способ подключить функции к конвейеру, где выходные данные одной функции будут переданы на вход следующей (как демонстрирует ваш пример Javascript).И в F #, идиоматический способ сделать это с оператором «конвейера».Вместо bind "input value" f1 f2 f3 (где f1, f2 и f3 - три функции соответствующего типа), в F # вы просто напишите:

"input value"
|> f1
|> f2
|> f3

Это нормальный идиоматический F #, и он будет понят в значительной степенилюбой, даже те, кто не особенно знаком с функциональным программированием.Так что нет необходимости в этой bind функции в F #.

0 голосов
/ 11 октября 2018

Это определение 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.

0 голосов
/ 11 октября 2018

Проблема в том, что функция bind, которую вы пытаетесь определить, не имеет типа, который вы можете выразить в стандартном F #.Вам нужна функция, тип которой выглядит примерно так:

'A -> ('A -> 'B) -> ('B -> 'C) -> ('C -> 'D) -> ('D -> 'E) -> ....

Другими словами, функция bind должна принимать входные данные и последовательность функций, которые к ним применяются, но вы неограничивая, как долго последовательность.Стандартная система типа F # не может выразить это, поэтому люди обычно используют другие шаблоны.В вашем случае это будет оператор |>.В отличие от вашего bind, вам приходится многократно писать |>, но это, как правило, хорошо:

"Hello World" 
|> (fun x -> x.ToUpper()) 
|> printf "%s"

Тем не менее, я думаю, что ваш пример выбран неправильно, и я бы просто написал:

printf "%s" ("Hello world".ToUpper())

Наконец, на самом деле может быть возможно определить что-то вроде вашей bind функции, используя перегрузку, которая возможна благодаря статическим ограничениям члена F #.Что-то вроде этих строк , но |> - идиоматическое и чистое решение для того, что вы делаете, как показано в вашем примере.

...