Почему вывод типа F# настолько изменчив?
Компилятор F#, кажется, выполняет вывод типа (довольно) строго сверху вниз, слева направо. Это означает, что вы должны делать такие вещи, как помещать все определения перед их использованием, порядок компиляции файлов является значительным, и вам, как правило, нужно переставлять вещи (через |>
или что у вас), чтобы избежать явных аннотаций типов.
Насколько сложно сделать это более гибким, и планируется ли это для будущей версии F#? Очевидно, что это можно сделать, так как, например, у Haskell нет таких ограничений с таким же мощным выводом. Есть ли что-то принципиально иное в дизайне или идеологии F#, что вызывает это?
5 ответов
Что касается "столь же мощного вывода Хаскелла", я не думаю, что Хаскеллу приходится иметь дело с
- Динамическое подтипирование в стиле OO (классы типов могут делать что-то похожее, но классы типов легче набирать / выводить)
- перегрузка методов (классы типов могут делать что-то похожее, но классы типов проще набирать / выводить)
То есть, я думаю, что F# имеет дело с некоторыми сложными вещами, которых нет у Haskell. (Почти наверняка Haskell имеет дело с некоторыми сложными вещами, которых нет у F#.)
Как отмечается в других ответах, большинство основных языков.NET имеют инструментальные средства Visual Studio в качестве основного влияния на проектирование языка (см., Например, как LINQ имеет "from ... select" вместо SQL-y "select... from"). msgstr ", мотивировано получением intellisense из префикса программы). Интеллектуальность, загадочность ошибок и понятность сообщений об ошибках - все это инструменты, которые определяют дизайн F#.
Вполне возможно, что можно делать лучше и делать больше выводов (не жертвуя другим опытом), но я не думаю, что это является одним из наших главных приоритетов для будущих версий языка. (Хаскелеры могут считать вывод типа F# несколько слабым, но, вероятно, их численно превосходят те, кто считает вывод F# очень сильным.:))
Также может быть трудно расширить вывод типа неразрывным способом; в будущей версии можно поменять нелегальные программы на легальные, но вы должны быть очень осторожны, чтобы убедиться, что ранее легальные программы не меняют семантику в соответствии с новыми правилами вывода, а разрешение имен (ужасный кошмар на всех языках), скорее всего, вполне вероятно. взаимодействовать с изменениями типа-вывода удивительными способами.
Есть ли что-то принципиально иное в дизайне или идеологии F#, что вызывает это?
Да. F# использует номинальную, а не структурную типизацию, потому что она проще и, следовательно, проще для простых смертных.
Рассмотрим пример F#:
let lengths (xss: _ [] []) = Array.map (fun xs -> xs.Length) xss
let lengths (xss: _ [] []) = xss |> Array.map (fun xs -> xs.Length)
Первый не компилируется, потому что тип xs
внутри анонимной функции не может быть выведено, потому что F# не может выразить тип "некоторый класс с Length
член".
В отличие от OCaml может выразить прямой эквивалент:
let lengths xss = Array.map (fun xs -> xs#length) xss
потому что OCaml может выразить этот тип (написано <length: 'a ..>
). Обратите внимание, что для этого требуется более мощный вывод типов, чем у F# или Haskell в настоящее время, например, OCaml может выводить типы сумм.
Однако эта функция, как известно, является проблемой удобства использования. Например, если вы напортачили в другом месте кода, компилятор еще не определил, что тип xs
Предполагалось, что это будет массив, поэтому любое сообщение об ошибке, которое он может выдать, может предоставить только информацию типа "некоторый тип с элементом длины", а не "массив". Только с немного более сложным кодом это быстро выходит из-под контроля, поскольку у вас есть массивные типы со многими структурно выведенными членами, которые не совсем объединяются, что приводит к непонятным (C++/STL-подобным) сообщениям об ошибках.
Я думаю, что алгоритм, используемый в F#, имеет то преимущество, что его легко (по крайней мере, приблизительно) объяснить, как он работает, поэтому, как только вы его поймете, у вас могут возникнуть некоторые ожидания относительно результата.
Алгоритм всегда будет иметь некоторые ограничения. В настоящее время их довольно легко понять. Для более сложных алгоритмов это может быть сложно. Например, я думаю, что вы можете столкнуться с ситуациями, когда вы думаете, что алгоритм должен иметь возможность что-то выводить - но если бы он был достаточно общим, чтобы охватить случай, он был бы неразрешимым (например, мог бы продолжать цикл навсегда).
Другая мысль заключается в том, что проверка кода сверху вниз соответствует тому, как мы читаем код (по крайней мере, иногда). Так что, может быть, тот факт, что мы склонны писать код таким образом, который позволяет выводить типы, также делает код более читабельным для людей...
F# использует однопроходную компиляцию, так что вы можете ссылаться только на типы или функции, которые были определены ранее в файле, в котором вы находитесь, или появиться в файле, который указан ранее в порядке компиляции.
Недавно я спросил Дона Сайма о том, как сделать несколько исходных пропусков, чтобы улучшить процесс вывода типов. Он ответил: "Да, можно сделать многопроходный вывод типа. Существуют также однопроходные варианты, которые генерируют конечный набор ограничений.
Однако эти подходы, как правило, дают плохие сообщения об ошибках и плохие результаты intellisense в визуальном редакторе ".
http://www.markhneedham.com/blog/2009/05/02/f-stuff-i-get-confused-about/
Короткий ответ заключается в том, что F# основан на традициях SML и OCaml, тогда как Haskell происходит из немного другого мира - Миранды, Гофера и тому подобного. Различия в исторической традиции неуловимы, но широко распространены. Это различие параллельно и в других современных языках, таких как ML-подобный Coq, который имеет те же ограничения порядка, что и Haskell-подобный Agda, который не имеет.
Эта разница связана с ленивым против строгой оценки. Сторона вселенной Haskell верит в лень, и как только вы уже поверите в лень, идея добавить лень к таким вещам, как вывод типа, не представляет никакой сложности. В то время как на стороне ML вселенной всякий раз, когда необходима лень или взаимная рекурсия, это должно быть явно отмечено использованием ключевых слов, таких как with, и, rec, и т. Д. Я предпочитаю подход Haskell, потому что он приводит к меньшему количеству шаблонного кода, но есть много людей, которые думают, что лучше сделать эти вещи явными.