Работает ли вывод типа F # только сверху вниз (и слева направо)? - PullRequest
0 голосов
/ 15 декабря 2018

Я до сих пор не могу понять, почему компилятор F # не может определить тип в следующем примере (взятом из книги Programming F# 3.0):

open System.IO

// type inference ok: fileInfos : string [] -> FileInfo []
let fileInfos (files : string[]) =
    Array.map (fun file -> new FileInfo(file)) files

// type inference does not work !?
let fileSizes (fileInfos : FileInfo []) =
    Array.map (fun info -> info.Length) fileInfos

Объяснение в книге (стр. 62):

Это связано с тем, что вывод типа обрабатывает код слева направо и сверху вниз, поэтому он видит лямбду, переданную в Array.map, прежде чем он увидит тип переданных элементов массива.(Следовательно, тип параметра лямбды неизвестен.)

, что в данном случае разумно (для fileInfos тип file выводится как string, поскольку конструктор FileInfo имеет параметр string; для fileSizes такой информации нет).

Но у меня все еще есть сомнения, потому что, если объяснение верное, то вывод типа (вариант алгоритма Хиндли – Милнера)Ж) так ограничен.Действительно, есть еще один источник , который говорит:

... [F #] ... вывод типа работает сверху вниз, снизу вверх, спереди назад, сзадивперед, в середине, везде, где есть информация о типе, она будет использоваться.

Редактировать: спасибо всем за ответы, я просто добавляю ниже некоторые детали, чтобы объяснить, почему я все ещезапутаться.

В случае fileSizes компилятор знает:

  • filesInfo : FileInfo [],
  • Array.map : ('a -> 'b) -> 'a [] -> 'b [],

он может заменить 'a на FileInfo, поэтому в лямбде должно быть info : FileInfo fun info -> info.Length

Я могу привести пример, когда вывод типа F # показывает, что этоболее способный, чем «слева направо, сверху вниз»:

// type inference ok: justF : int [] -> (int -> int -> 'a) -> 'a []
let justF (nums : int []) f =
    Array.map (fun x -> f x x) nums

, где компилятор правильно выводит тип f : int -> int -> 'a (очевидно, он не может иметь такой вывод, если он смотрит только налямбда).

Ответы [ 2 ]

0 голосов
/ 15 декабря 2018

Чтобы использовать вывод типа слева направо, вы можете изменить fileSizes, используя оператор трубы |>.Поскольку он уже знает, что fileInfos является массивом FileInfo, он может сделать вывод, что info должно быть FileInfo:

// type inference now is ok
let fileSizes (fileInfos : FileInfo []) =
    fileInfos |> Array.map (fun info -> info.Length) 

Другой способ не работает, потому что появляется лямбда-функцияпрежде чем он узнает, каким будет второй параметр Array.map.Оператор канала, с другой стороны, уже устанавливает, что все, что следует после канала, должно получить массив FileInfo.

В случае fileInfos, как вы правильно сказали, он знает, что file должна быть строкой, потому что это единственный тип, который new FileInfo(...) принимает.Теперь представьте, что вы хотите использовать одного из членов ОО info, например ToUpper().Это может привести к той же ошибке, потому что может быть любое количество типов, которые могут иметь член с именем ToUpper, который возвращает string:

// same type inference error
let fileInfos (files : string[]) =
    Array.map (fun file -> new FileInfo(file.ToUpper())) files

, и вы можете исправить это, передав файлы в первую очередьиспользование канала:

let fileInfos (files : string[]) =
    files |> Array.map (fun file -> new FileInfo(file.ToUpper())) 

При использовании типов записей F # ищет тип, который имеет член с таким же именем, и предполагает, что это тип.Это может работать неправильно, если существует более одной возможности.В этом случае он выберет последний объявленный или открытый.

0 голосов
/ 15 декабря 2018

Алгоритм вывода типов в F # несколько ограничен в доступе к элементу экземпляра объекта - он не пытается определить тип «назад» из них, поэтому, если не предоставлено достаточно информации о типе, он просто остановится на месте.

Это не относится к полям записей, например (обратите внимание, что нет аннотации даже к аргументу функции):

type FileInfoRecord = { length: int }

let fileSizes fileInfos =
    Array.map (fun info -> info.length) fileInfos

Это сознательный компромисс дизайна, однако - я помнюслышать, что это может быть улучшено, но за счет менее надежного интеллектуального смысла и более запутанных сообщений об ошибках (т. е. оно будет работать нормально для простых случаев, но когда оно не будет работать, оно будет отказывать дальше от фактической строки, вызвавшей проблему).С текущей настройкой обходной путь прост - просто поместите аннотацию к аргументу лямбды.

Я полагаю, что это цена, которую стоит заплатить за выполнение вывода типа стиля Хиндли-Милнера вместе с объектно-ориентированными иерархиями типов с полиморфизмом и перегрузкой.

...