(Use -XMultiParamTypeClasses
to allow multi-parameter classes)In
the class declaration for ‘Multi’Failed
, modules loaded: none.Компилятор сообщает нам о том, что у нас слишком много параметров в классе Multi
. В рамках стандар-та Haskell можно создавать лишь классы с одним параметром. Но за сообщением мы видим подсказку, если
мы воспользуемся расширением -XMultiParamTypeClasses
, то всё будет хорошо. В этом сообщении имя рас-ширения закодировано в виде флага. Мы можем запустить ghc или ghci с этим флагом и тогда расширение
будет активировано, и модуль загрузится. Попробуем:
Prelude> :
qLeaving GHCi.
$
ghci -XMultiParamTypeClassesPrelude> :
l Test[1 of
1] Compiling Test( Test.
hs, interpreted )Ok
, modules loaded: Test.*Test>
Модуль загрузился! У нас есть и другая возможность подключить модуль с помощью прагмы LANGUAGE
.Имя расширения записано во флаге после символов -X
. Добавим в модуль Test расширение с именемMultiParamTypeClasses
:{-# LANGUAGE MultiParamTypeClasses #-}
module Test where
class Multi
a b whereТеперь загрузим ghci в обычном режиме:
*Test> :
qLeaving GHCi.
$
ghciPrelude> :
l Test[1 of
1] Compiling Test( Test.
hs, interpreted )Ok
, modules loaded: Test.254 | Глава 17: Дополнительные возможности
Обобщённые алгебраические типы данных
Предположим, что мы хотим написать компилятор небольшого языка. Наш язык содержит числа и логиче-
ские значения. Мы можем складывать числа и умножать. Для логических значений определена конструкция
if-then-else
. Определим тип синтаксического дерева для этого языка:data Exp = ValTrue
| ValFalse
| If Exp Exp Exp
| Val Int
| Add Exp Exp
| Mul Exp Exp
deriving
(Show)В этом определении кроется одна проблема. Наш тип позволяет нам строить бессмысленные выражения
вроде Add ValTrue
(Val 2) или If (Val 1) ValTrue (Val 22). Наш тип Val включает в себя все хорошие вы-ражения и много плохих. Эта проблема проявится особенно ярко, если мы попытаемся определить функцию
eval, которая вычисляет значение для нашего языка. Получается, что тип этой функции:
eval :: Exp -> Either Int Bool
Для решения этой проблемы были придуманы
algebraic data types, GADTs). Они подключаются расширением GADTs
. Помните когда-то мы говорили, чтотипы можно представить в виде классов. Например определение для списка
data List
a = Nil | Cons a (List a)можно мысленно переписать так:
data List
a whereNil
:: List
aCons ::
a -> List a -> List aТак вот в GADT определения записываются именно в таком виде. Обобщение заключается в том, что
теперь на месте произвольного параметра a мы можем писать конкретные типы. Определим тип GExp
{-# LANGUAGE GADTs #-}
data Exp
a whereValTrue
:: Exp Bool
ValFalse
:: Exp Bool
If
:: Exp Bool -> Exp
a -> Exp a -> Exp aVal
:: Int -> Exp Int
Add
:: Exp Int -> Exp Int -> Exp Int
Mul
:: Exp Int -> Exp Int -> Exp Int
Теперь у нашего типа Exp
появился параметр, через который мы кодируем дополнительные ограниченияна типы операций. Теперь мы не сможем составить выражение Add ValTrue ValFalse
, потому что оно непройдёт проверку типов.
Определим функцию eval:
eval :: Exp
a -> aeval x = case
x ofValTrue
-> True
ValFalse
-> False
If
p t e-> if
eval p then eval t else eval eVal
n->
nAdd
a b->
eval a + eval bMul
a b->
eval a * eval bЕсли eval получит логическое значение, то будет возвращено значение типа Bool
, а на значение типа ExpInt
будет возвращено целое число. Давайте убедимся в этом:Расширения | 255
*Prelude> :
l Exp[1 of
1] Compiling Exp( Exp.
hs, interpreted )Ok
, modules loaded: Exp.*Exp> let
notE x = If x ValFalse ValTrue*Exp> let
squareE x = Mul x x*Exp>
*Exp>
eval $ squareE $ If (notE ValTrue) (Val 1) (Val 2)4
*Exp>
eval $ notE ValTrueFalse
*Exp>
eval $ notE $ Add (Val 1) (Val 2)<
interactive>:1:14:Couldn’t
match expected type ‘Bool’ against inferred type ‘Int’Expected type: Exp Bool
Actual type: Exp Int
In
the return type of a call of ‘Add’In
the second argument of ‘($)’, namely ‘Add (Val 1) (Val 2)’Сначала мы определили две вспомогательные функции. Затем вычислили несколько значений. Haskell