Почему вывод типа F#настолько непостоянен?


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

насколько сложно сделать это более гибким, и планируется ли это для будущей версии F#? Очевидно, что это можно сделать, так как Хаскелл (например)не имеет таких ограничений с таким же мощным выводом. Есть ли что-то принципиально иное в дизайне или идеологии F#, что вызывает это?

5 66

5 ответов:

Что касается "столь же мощного вывода Хаскелла", я не думаю, что Хаскелл должен иметь дело с

  • динамический подтип OO-style (классы типов могут делать некоторые подобные вещи, но классы типов легче вводить / выводить)
  • перегрузка метода (классы типов могут делать некоторые подобные вещи, но классы типов легче вводить / выводить)

то есть, я думаю, что F# должен иметь дело с некоторыми жесткими вещами, которые Haskell не делает. (Почти наверняка, Хаскелл должен иметь дело с некоторыми жесткими вещами, которые F# не делает.)

Как уже упоминалось в других ответах, большинство основных языков .NET имеют инструменты Visual Studio в качестве основного влияния на языковой дизайн (см., например, как LINQ имеет "from ... выберите "вместо SQL-y" выбрать... from", мотивированный получением intellisense из префикса программы). Intellisense, закорючки ошибок и понятность сообщений об ошибках-все это факторы инструментария, которые определяют дизайн F#.

Это вполне возможно сделать лучше и выводить больше (не жертвуя другим опытом), но я не думаю, что это среди наших высоких приоритетов для будущих версий языка. (В Haskellers могут видеть F# тип вывода Как слабовата, но они, вероятно, меньше, чем на C#онеров, которые видят в 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# не может выразить тип " некоторый класс с a Length - члены".

напротив, OCaml может выражать прямой эквивалент:

let lengths xss = Array.map (fun xs -> xs#length) xss

потому что OCaml может выразить этот тип (он написан <length: 'a ..>). Обратите внимание, что для этого требуется более мощный вывод типа, чем у F# или Haskell в настоящее время, например, OCaml может выводить типы сумм.

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

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

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

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

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

недавно я спросил Дона Сайма о создании несколько проходов источника для улучшения тип процесса вывода. Его ответ был "Да, это можно сделать многоходовым вывод типа. Есть также однопроходные вариации что создавать конечный набор ограничений.

однако эти подходы, как правило, дают плохие сообщения об ошибках и плохие результаты IntelliSense в Visual редактор."

http://www.markhneedham.com/blog/2009/05/02/f-stuff-i-get-confused-about/#comment-16153

короткий ответ заключается в том, что F# основан на традиции SML и OCaml, тогда как Haskell происходит из немного другого мира Миранды, Гофера и тому подобного. Различия в исторической традиции неуловимы, но всепроникающи. Это различие параллелизируется и в других современных языках, таких как ML-подобный Coq, который имеет те же ограничения упорядочения, что и Haskell-подобный Agda, который этого не делает.

эта разница связана с ленивым против строгой оценки. В Хаскелл стороне Вселенная верит в лень, и как только вы уже верите в лень, идея добавления лени к таким вещам, как вывод типа, не вызывает сомнений. В то время как в ML-стороне Вселенной всякий раз, когда необходима лень или взаимная рекурсия, она должна быть явно отмечена с помощью таких ключевых слов, как С,и, rec и т. д. Я предпочитаю подход Haskell, потому что он приводит к меньшему шаблонному коду, но есть много людей, которые думают, что лучше сделайте эти вещи явными.