Когда нужны подписи типа в Haskell?

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

Насколько я могу судить, единственная необходимость в явной подписи - это устранение неоднозначности классов типов. (Канонический пример read . show.) Есть ли другие случаи, о которых я не думал, или это все?

(Я знаю, что если вы выйдете за пределы Haskell 2010, существует множество исключений. Например, GHC никогда не будет выводить типы ранга N. Но типы ранга N являются расширением языка, а не частью официального стандарта [пока].)

3 ответа

Решение

Ограничение мономорфизма

Если у вас есть MonomorphismRestriction включен, то иногда вам нужно будет добавить сигнатуру типа, чтобы получить наиболее общий тип:

{-# LANGUAGE MonomorphismRestriction #-}
-- myPrint :: Show a => a -> IO ()
myPrint = print
main = do
  myPrint ()
  myPrint "hello"

Это не удастся, потому что myPrint мономорфен. Вам нужно раскомментировать сигнатуру типа, чтобы она заработала, или отключить MonomorphismRestriction,

Фантомные ограничения

Когда вы помещаете полиморфное значение с ограничением в кортеж, сам кортеж становится полиморфным и имеет такое же ограничение:

myValue :: Read a => a
myValue = read "0"

myTuple :: Read a => (a, String)
myTuple = (myValue, "hello")

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

myString = snd myTuple

Хотя интуитивно можно было бы ожидать myString быть просто String, средство проверки типов должно специализировать переменную типа a и выяснить, действительно ли ограничение выполнено. Чтобы это выражение работало, нужно аннотировать тип snd или же myTuple:

myString = snd (myTuple :: ((), String))

Полиморфная рекурсия нуждается в типовых аннотациях, в общем.

f :: (a -> a) -> (a -> b) -> Int -> a -> b
f f1 g n x = 
    if n == (0 :: Int)
    then g x
    else f f1 (\z h -> g (h z)) (n-1) x f1

(Фото: Патрик Кузо)

Обратите внимание, что рекурсивный вызов выглядит плохо набранным (!): Он вызывает себя с пятью аргументами, несмотря на f имея только четыре! Тогда запомни это b может быть создан с помощью c -> d, что приводит к появлению дополнительного аргумента.

Придуманный выше пример вычисляет

f f1 g n x = g (f1 (f1 (f1 ... (f1 x))))

где f1 применены n раз. Конечно, существует гораздо более простой способ написания эквивалентной программы.

В Хаскеле, как я уверен, вы знаете, типы выводятся. Другими словами, компилятор решает, какой тип вы хотите.

Однако в Haskell также существуют полиморфные классы типов, функции которых действуют по-разному в зависимости от типа возвращаемого значения. Вот пример класса Monad, хотя я не все определил:

class Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b
    fail :: String -> m a

Нам дано много функций только с сигнатурами типов. Наша работа состоит в том, чтобы делать объявления экземпляров для различных типов, которые можно рассматривать как монады, например Maybe t или же [t],

Посмотрите на этот код - он не будет работать так, как мы могли бы ожидать:

return 7

Это функция из класса Monad, но поскольку существует более одной Monad, мы должны указать, какое возвращаемое значение / тип мы хотим, или оно автоматически становится IO-монадой. Так:

return 7 :: Maybe Int
-- Will return...
Just 7

return 6 :: [Int]
-- Will return...
[6]

Это потому что [t] а также Maybe оба были определены в классе типа Monad.

Вот еще один пример, на этот раз из класса случайных типов. Этот код выдает ошибку:

random (mkStdGen 100)

Так как random возвращает что-то в Random класс, мы должны определить, какой тип мы хотим вернуть, с StdGen объект tupelo с любым значением, которое мы хотим:

random (mkStdGen 100) :: (Int, StdGen)
-- Returns...
(-3650871090684229393,693699796 2103410263)

random (mkStdGen 100) :: (Bool, StdGen)
-- Returns...
(True,4041414 40692)

Все это можно найти на сайте Haskell, но вам придётся долго читать. Это, я почти на 100% уверен, это единственный раз, когда нужны типы.

Другие вопросы по тегам