Когда использовать вывод типа в Haskell?

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

Могут ли некоторые опытные Haskellers (Haskellites? Haskellizers?) предоставить некоторый вклад?

Ответы

Ответ 1

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

Глупый пример со странным способом вычисления квадратов чисел:

squares :: [Int]
squares = sums 0 odds
  where
    odds = filter odd [1..]
    sums s (a:as) = s : sums (s+a) as

square :: Int -> Int
square n = squares !! n

odds и sums - это функции, которые нуждаются в сигнатуре типа, если компилятор не выводит их автоматически.

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

squares :: [a]
squares = ...

Компилятор может вывести, что это недопустимо, потому что одна из используемых функций (функция odd из стандартной библиотеки) нуждается в a для класса типа Integral. На других языках вы обычно узнаете это позже.

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

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

Ответ 2

Я всегда пишу подпись типа для функций и значений верхнего уровня, но не для вещей в предложениях "where", "пусть" или "делать".

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

Во-вторых, когда вы ошибаетесь, ошибки компилятора намного проще декодировать, если у компилятора есть информация о типе. На самом деле иногда в сложном предложении "where" я получаю непонятную ошибку типа, поэтому добавляю временные объявления типов, чтобы найти проблему, немного похожу на эквивалент отпечатка printf на уровне типа.

Итак, чтобы ответить на исходный вопрос, я использую вывод типа много, но не в 100% случаев.

Ответ 3

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

Как и другие, я почти всегда ставил сигнатуру типа для функции верхнего уровня и почти никогда не применял никаких других объявлений.

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

Ответ 4

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

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