Работает правило: если произошла неопределённость и один из участвующих классов является Num
, а всеостальные классы – это стандартные классы, определённые в Prelude
, то компилятор начинает последова-тельно пробовать все типы, перечисленые за ключевым словом default
, пока один из них не подойдёт. Еслитакого типа не окажется, компилятор скажет об ошибке.
Ограничение мономорфизма
С выводом типов в классах связана одна тонкость. Мы говорили, что не обязательно выписывать типы
выражений, компилятор может вывести их самостоятельно. Например, мы постоянно пользуемся этим в ин-
терпретаторе. Также когда мы говорили о частичном применении, мы сказали об очень полезном умолчании
в типах функций. О том, что за счёт частичного применения, все функции являются функциями одного аргу-
мента. Эта особенность позволяет записывать выражения очень кратко. Но иногда они получаются чересчур
краткими, и вводят компилятор в заблуждение. Зайдём в интерпретатор:
Prelude> let
add = (+)Prelude> :
t addadd :: Integer -> Integer -> Integer
Мы хотели определить синоним для метода плюс из класса Num
, но вместо ожидаемого общего типаполучили более частный. Сработало умолчание для численного типа. Но зачем оно сработало? Если мы
попробуем дать синоним методу из класса Eq
, ситуация станет ещё более странной:Prelude> let
eq = (==)Prelude> :
t eqeq ::
() -> () -> BoolМы получили какую-то ерунду. Если мы попытаемся загрузить модуль с этими определениями:
52 | Глава 3: Типы
module MR where
add =
(+)eq
=
(==)то получим:
*MR> :
l MR[1 of
1] Compiling MR( MR.
hs, interpreted )MR.
hs:4:7:Ambiguous type
variable ‘a0’ in the constraint:(Eq
a0) arising from a use of ‘==’Possible cause: the monomorphism restriction applied to the following:
eq :: a0 -> a0 -> Bool (bound at MR.hs:4:1)
Probable fix: give these definition(s) an explicit type signature
or use -XNoMonomorphismRestriction
In the expression: (==)
In an equation for ‘eq’:
eq = (==)Failed
, modules loaded: none.Компилятор жалуется о том, что в определении для eq ему встретилась неопределённость и он не смог
вывести тип. Если же мы допишем недостающие типы:
module MR where
add :: Num
a => a -> a -> aadd =
(+)eq :: Eq
a => a -> a -> Booleq
=
(==)то всё пройдёт гладко:
Prelude> :
l MR[1 of
1] Compiling MR( MR.
hs, interpreted )Ok
, modules loaded: MR.*MR>
eq 2 3False
Но оказывается, что если мы допишем аргументы у функций и сотрём объявления, компилятор сможет
вывести тип, и тип окажется общим. Это можно проверить в интерпретаторе. Для этого начнём новую сессию:
Prelude> let
eq a b = (==) a bPrelude> :
t eqeq :: Eq
a => a -> a -> BoolPrelude> let
add a = (+) aPrelude> :
t addadd :: Num
a => a -> a -> aЗапишите эти выражения в модуле без типов и попробуйте загрузить. Почему так происходит? По смыслу
определения
add a b =
(+) a badd
=
(+)ничем не отличаются друг от друга, но второе сбивает компилятор столку. Компилятор путается из-
за того, что второй вариант похож на определение константы. Мы с вами знаем, что выражение справа от
знака равно является функцией, но компилятор, посчитав аргументы слева от знака равно, думает, что это
возможно константа, потому что она выглядит как константа. У таких возможно-констант есть специальное
имя, они называются константными аппликативными формами (constant applicative form или сокращённо
CAF). Константы можно вычислять один раз, на то они и константы. Но если тип константы перегружен,
и мы не знаем что это за тип (если пользователь не подсказал нам об этом в объявлении типа), то нам
приходится вычислять его каждый раз заново. Посмотрим на пример:
Проверка типов | 53
res =
s + ss =
someLongLongComputation 10someLongLongComputation :: Num
a => a -> aЗдесь значение s содержит результат вычисления какой-то большой-пребольшой функции. Перед компи-
лятором стоит задача вывода типов. По тексту можно определить, что у s и res некоторый числовой тип.
Проблема в том, что поскольку компилятор не знает какой тип у s конкретно в выражении s +
s, он вы-нужден вычислить s дважды. Это привело разработчиков Haskell к мысли о том, что все выражения, которые
выглядят как константы должны вычисляться как константы, то есть лишь один раз. Это ограничение называ-
ют ограничением
пользователь не укажет обратное в типе или не подскажет компилятору косвенно, подставив неопределённое
значение в другое значение, тип которого определён. Например, такой модуль загрузится без ошибок:
eqToOne =
eq oneeq =
(==)one :: Int
one =
1Только в этом случае мы не получим общего типа для eq: компилятор постарается вывести значение,
которое не содержит контекста. Поэтому получится, что функция eq определена на Int
. Эта очень спорная