Почему бы не аннотировать параметры функции?

28

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

Учитывая , что, почему бы я , чтобы мои товарищи по команде , чтобы уйти с не аннотирования их параметры функции? Возьмем следующий код в качестве примера того, что может быть гораздо более сложным фрагментом кода:

let foo x y = x + y

Теперь быстрая проверка подсказки покажет вам, что F # определил, что вы хотите, чтобы x и y были целыми числами. Если это то, что вы намеревались, то все хорошо. Но я не знаю, хотел ли ты этого. Что если бы вы создали этот код для объединения двух строк вместе? Или что, если я думаю, что вы, вероятно, хотели добавить двойки? Или что, если мне просто не нужно наводить указатель мыши на каждый параметр функции, чтобы определить его тип?

Теперь возьмем это в качестве примера:

let foo x y = "result: " + x + y

F # теперь предполагает, что вы, вероятно, намеревались объединить строки, поэтому x и y определены как строки. Однако, как бедняга, который поддерживает ваш код, я мог бы взглянуть на это и подумать, возможно, вы намеревались добавить x и y (целые числа) вместе, а затем добавить результат в строку для целей пользовательского интерфейса.

Конечно, для таких простых примеров можно было бы отпустить, но почему бы не применить политику явной аннотации типов?

let foo (x:string) (y:string) = "result: " + x + y

Какой вред в том, чтобы быть однозначным? Конечно, программист мог выбрать неправильные типы для того, что он пытается сделать, но, по крайней мере, я знаю, что они этого хотели, что это был не просто недосмотр.

Это серьезный вопрос ... Я все еще очень плохо знаком с F # и прокладываю путь для моей компании. Стандарты, которые я принимаю, вероятно, станут основой для всего будущего кодирования F #, встроенного в бесконечное копирование, которое, я уверен, будет проникать в культуру на долгие годы.

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

JDB
источник
Аннотированная версия является менее общей. По той же причине, по которой вы предпочитаете использовать IEnumerable <> в подписи, а не SortedSet <>.
Патрик
2
@ Патрик - Нет, это не так, потому что F # выводит тип строки. Я не изменил сигнатуру функции, только сделал ее явной.
JDB
1
@ Патрик - И даже если бы это было правдой, вы можете объяснить, почему это плохо? Возможно, подпись должна быть менее общей, если это то, что задумал программист. Возможно, более общая сигнатура на самом деле вызывает проблемы, но я не уверен, насколько конкретно программист намеревался быть без большого количества исследований.
JDB
1
Я думаю, что было бы справедливо сказать, что в функциональных языках вы предпочитаете общность и комментируете в более конкретном случае; например, когда вы хотите повысить производительность и можете получить ее, используя подсказки типа. Повсеместное использование аннотированных функций потребует написания перегрузок для каждого конкретного типа, что может быть неоправданно в общем случае.
Роберт Харви

Ответы:

30

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

  • Чтение
    Когда вы хотите знать, как использовать функцию, невероятно полезно иметь доступную сигнатуру типа. Вы можете просто прочитать его, вместо того, чтобы пытаться сделать это самостоятельно или полагаться на инструменты, которые сделают это за вас.

  • Рефакторинг
    Когда вы хотите изменить функцию, наличие явной подписи дает вам некоторую гарантию того, что ваши преобразования сохраняют намерения исходного кода. В языке с выводом типа вы можете обнаружить, что высокополиморфный код будет проверять тип, но не будет выполнять то, что вы хотели. Сигнатура типа является «барьером», который конкретизирует информацию о типе в интерфейсе.

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

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

Джон Перди
источник
3
Это звучит как очень разумный и хорошо сбалансированный ответ.
JDB
1
Я согласен с тем, что определение с явно определенными типами может помочь при рефакторинге, но я считаю, что модульное тестирование также может решить эту проблему, и потому что я считаю, что модульный тест следует использовать с функциональным программированием, чтобы гарантировать, что функция работает так, как задумано, перед использованием функция с другой функцией, это позволяет мне исключать типы из определения и иметь уверенность в том, что если я сделаю критическое изменение, оно будет обнаружено. Пример
Guy Coder
4
В Haskell считается правильным аннотировать ваши функции, но я полагаю, что идиоматические F # и OCaml, как правило, пропускают аннотации, если в этом нет необходимости устранять неоднозначность. Нельзя сказать, что это вредно (хотя синтаксис для аннотирования в F # более уродлив, чем в Haskell).
KChaloux
4
@KChaloux В OCaml уже есть аннотации типов в .mliфайле interface ( ) (если вы пишете таковые, что настоятельно рекомендуется. Аннотации типов, как правило, исключаются из определений, поскольку они будут избыточными с интерфейсом.
Жиль ТАК - перестать быть злым '27
1
@Gilles @KChaloux, действительно, то же самое в .fsiфайлах F # signature ( ), с одной оговоркой: F # не использует файлы сигнатур для вывода типов, поэтому, если что-то в реализации является неоднозначным, вам все равно придется аннотировать его снова. books.google.ca/…
Явар Амин
1

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

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

Например:

/// Parser that requires a specific item.

// a (tok : 'a) : ('a list -> 'a * 'a list)                     // generic
// a (tok : string) : (string list -> string * string list)     // string
// a (tok : token)  : (token list  -> token  * token list)      // token

или

/// Parses iterated left-associated binary operator.


// leftbin (prs : 'a -> 'b * 'c) (sep : 'c -> 'd * 'a) (cons : 'd -> 'b -> 'b -> 'b) (err : string) : ('a -> 'b * 'c)                                                                                    // generic
// leftbin (prs : string list -> string * string list) (sep : string list -> string * string list) (cons : string -> string -> string -> string) (err : string) : (string list -> string * string list)  // string
// leftbin (prs : token list  -> token  * token list)  (sep : token list  -> token  * token list)  (cons : token  -> token  -> token  -> token)  (err : string) : (token list  -> token  * token list)   // token

Так как код защищен авторским правом, я не буду его здесь включать, но он доступен на Github . Не копируйте это здесь.

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

Гай Кодер
источник
1
Почему бы вам не написать общую версию в функцию? Разве это не сработает?
svick
@ svick Я не понимаю твой вопрос. Вы имеете в виду, что я исключил типы из определения функции, но мог бы добавить универсальные типы к определениям функции, так как универсальные типы не изменили бы значение функции? Если так, то причина скорее в личных предпочтениях. Когда я впервые начал с F #, я добавил их. Когда я начал работать с более продвинутыми пользователями F #, они предпочли, чтобы их оставляли в качестве комментариев, потому что было проще модифицировать код без подписей. ...
Гай Кодер
В конце концов, я покажу им, как комментарии работали для всех, когда работал код. Когда я начинаю писать функцию, я добавляю типы. Как только у меня заработает функция, я перемещаю сигнатуру типа в комментарий и удаляю как можно больше типов. Иногда с F # вам нужно оставить в типе, чтобы помочь вывод. Некоторое время я экспериментировал с созданием комментария с помощью let и =, чтобы вы могли раскомментировать строку для тестирования, а затем прокомментировать ее снова, когда все будет готово, но они выглядели глупо, и добавить let и = не так уж сложно.
Гай Кодер
Если эти комментарии отвечают на ваши вопросы, дайте мне знать, чтобы я мог переместить их в ответ.
Гай Кодер
2
Поэтому, когда вы изменяете код, вы не изменяете комментарии, что приводит к устаревшей документации? Это не звучит как преимущество для меня. И я действительно не вижу, как грязный комментарий лучше, чем грязный код. Мне кажется, что этот подход сочетает в себе недостатки двух вариантов.
svick
-8

Количество ошибок прямо пропорционально количеству символов в программе!

Обычно это указывается как количество ошибок, пропорциональных строкам кода. Но меньшее количество строк с одинаковым количеством кода дает одинаковое количество ошибок.

Поэтому, хотя приятно быть явным, любые дополнительные параметры, которые вы вводите, могут привести к ошибкам.

Джеймс Андерсон
источник
5
«Количество ошибок прямо пропорционально количеству символов в программе!». Итак, мы все должны перейти на APL ? Быть слишком кратким - это проблема, как быть слишком многословным.
svick
5
« Для того, чтобы этот вопрос к ответственности, давайте предположим , что стоимость двусмысленность в сознании программиста гораздо дороже , чем несколько дополнительных нажатий клавиш. »
JDB