Подтвердить что ты не робот

Тип вывода с взаимной рекурсией

Я думал о том, как работает вывод типа в следующей программе OCaml:

let rec f x = (g x) + 5
and g x = f (x + 5);;

Конечно, программа совершенно бесполезна (зацикливается навсегда), но как насчет типов? OCaml говорит:

val f : int -> int = <fun>
val g : int -> int = <fun>

Это будет именно моя интуиция, но как это знает алгоритм определения типа?

Предположим, что алгоритм считает "f" первым: единственным ограничением, которое он может получить, является то, что возвращаемый тип "g" должен быть "int", и поэтому его собственный тип возврата также "int". Но он не может вывести тип своего аргумента по определению "f" .

С другой стороны, если он сначала рассматривает "g" , он может видеть, что тип его собственного аргумента должен быть "int". Но, не считая "f" раньше, он не может знать, что возвращаемый тип "g" также является "int".

Какова его магия?

4b9b3361

Ответ 1

Предположим, что алгоритм считает "f" первым: единственным ограничением, которое он может получить, является то, что возвращаемый тип "g" должен быть "int", и поэтому его собственный тип возврата также "int". Но он не может вывести тип своего аргумента по определению "f".

Он не может вывести его конкретному типу, но он может что-то сделать. А именно: тип аргумента f должен быть таким же, как тип аргумента g. Итак, в основном после просмотра f ocaml знает о типах:

for some (to be determined) 'a:
f: 'a -> int
g: 'a -> int

После просмотра g он знает, что 'a должен быть int.

Для более подробного изучения того, как работает алгоритм вывода типа, вы можете прочитать статью Википедии о вывод типа Hindley-Milner или этот пост в блоге, который кажется намного более дружелюбным, чем статья в Википедии.

Ответ 2

Вот моя ментальная модель того, что происходит, что может или не может соответствовать действительности.

let rec f x =

Хорошо, в этот момент мы знаем, что f - это функция, которая принимает аргумент x. Таким образом, мы имеем:

f: 'a -> 'b
x: 'a

для некоторых 'a и 'b. Далее:

(g x)

Хорошо, теперь мы знаем, что g - это функция, которая может быть применена к x, поэтому мы добавляем

g: 'a -> 'c

к нашему списку информации. Продолжение...

(g x) + 5 

Aha, возвращаемый тип g должен быть int, так что теперь мы решили 'c=int. На этом этапе мы имеем:

f: 'a -> 'b
x: 'a
g: 'a -> int

Перемещение...

and g x =

Хорошо, здесь есть другой x, допустим, что исходный код имел y, чтобы сделать вещи более очевидными. То есть, перепишите код как

and g y = f (y + 5);; 

Итак, мы находимся в

and g y = 

Итак, теперь наша информация:

f: 'a -> 'b
x: 'a
g: 'a -> int
y: 'a

поскольку y является аргументом для g... и мы продолжаем:

f (y + 5);; 

и это говорит нам из y+5, что y имеет тип int, который решает 'a=int. А так как это возвращаемое значение g, которое мы уже знаем, должно быть int, это решает 'b=int. Это было много за один шаг, если код был

and g y = 
    let t = y + 5 in
    let r = f t in
    f r;; 

то в первой строке будет показано, что y является int, поэтому решение для 'a, а затем следующая строка будет говорить, что r имеет тип 'b, а затем конечная строка - это возврат of g, который решает 'b=int.