зачем писать объявления типов в Haskell?

Я новичок в Haskell и я пытаюсь понять, почему нужно писать объявления типа. Поскольку у Haskell есть вывод типа, когда мне вообще нужна первая строка? GHCI, похоже, генерирует правильный вывод с использованием': t'

единственный пример, который я нашел до сих пор, который, похоже, нуждается в объявлении, - это следующее.

maximum' :: (Ord a) => [a] -> a  
maximum' = foldr1 max

однако, если я добавлю "- xnomonomorphismrestriction " объявление флага не требуется снова. Существуют ли конкретные ситуации, когда вывод типа делает не работает и нужно указывать типы?

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

EDIT: оказывается, что тип вывода является обоюдоострым мечом на Реальный Мир Haskell книга имеет хорошее обсуждение этой темы.

5 ответов


считают read "5". Как Хаскелл может знать тип read "5"? Он не может, потому что нет способа разрешить результат операции, так как read определяется как (Read a) => String -> a. a не зависит от строки, поэтому он должен использовать контекст.

обычно контекст-это что-то вроде Ord или Num поэтому его невозможно определить. Это не ограничение мономорфизма, а скорее другой случай, который никогда не может быть обработан правильно.

примеры:

не работает:

read "0.5"
putStrLn . show . read $ "0.5"

Работает:

read "0.5" :: Float
putStrLn . show . (read :: String -> Float) $ "0.5"

эти необходимы потому, что по умолчанию Show экземпляр, если я правильно помню, составляет Int.


  • когда у вас есть большие программы Haskell, наличие сигнатур типов часто дает вам лучшие сообщения об ошибках от компилятора
  • когда-нибудь вы можете получить то, что функция делает из своего имени и его подписи
  • часто функция понятна намного лучше с сигнатурами типов, например, если вы используете currying
  • даже написание программ становится проще, я часто начинаю с сигнатур типов и большинства функций, объявленных как undefined. Это все компилирует I знаю, что моя идея кажется не слишком плохо. Затем я продолжаю и заменяю undefined реальный код

спокойствие. Иногда приятно убедиться, что компилятор согласен с вашим восприятием того, каким должен быть тип функции. Если выводимый тип не объединяется с данным типом, компилятор будет кричать на вас. Как только вы познакомитесь с системой типов, вы обнаружите, что необязательные подписи типов могут быть большим благом для вашей уверенности в кодировании.


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

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

тогда бывают случаи, когда вывод типа не работает, например, пример "чтения", который дал другой ответ. Но это встроенные определения типов, а не определение типа для функции.


одна важная вещь, которую я действительно не видел в каких-либо ответах, заключается в том, что вы часто будете писать свои определения типов и подписи типов, прежде чем записывать какой-либо фактический код. Как только вы завершите эту" спецификацию", ваша реализация будет проверена на соответствие ей, когда вы ее напишете, что облегчит обнаружение ошибок ранее, когда компилятор проверяет соответствие ваших типов. Если вы знаете, например, что-то должно иметь подпись Int -> Int -> [a] -> [a] но, когда пишу это, а не экземпляр двух параметров x и y, вы создаете экземпляр только одного параметра x случайно и использовать его дважды, компилятор поймает ошибку в точке, где вы определили функцию, в отличие от точки, в которой вы пытались использовать ее, как вы должны были использовать ее.