Prelude> :q
Leaving GHCi.
$ghci -XMultiParamTypeClasses
Prelude> :l Test
[1 of1] Compiling Test
( Test.hs, interpreted )
Ok, modules loaded : Test.
*Test>
Модуль загрузился! У нас есть и другая возможность подключить модуль с помощью прагмы LANGUAGE.
Имя расширения записано во флаге после символов -X. Добавим в модуль Testрасширение с именем
MultiParamTypeClasses:
{-# LANGUAGE MultiParamTypeClasses #-}
module Test where
class Multia b where
Теперь загрузим ghci в обычном режиме:
*Test> :q
Leaving GHCi.
$ghci
Prelude> :l Test
[1 of1] 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( Val2) или If( Val1) ValTrue( Val22). Наш тип Valвключает в себя все хорошие вы-
ражения и много плохих. Эта проблема проявится особенно ярко, если мы попытаемся определить функцию
eval, которая вычисляет значение для нашего языка. Получается, что тип этой функции:
eval :: Exp -> Either Int Bool
Для решения этой проблемы были придуманы обобщённые алгебраические типы данных (generalised
algebraic data types, GADTs). Они подключаются расширением GADTs. Помните когда-то мы говорили, что
типы можно представить в виде классов. Например определение для списка
data Lista = Nil | Consa ( Lista)
можно мысленно переписать так:
data Lista where
Nil
:: Lista
Cons ::a -> Lista -> Lista
Так вот в GADT определения записываются именно в таком виде. Обобщение заключается в том, что
теперь на месте произвольного параметра a мы можем писать конкретные типы. Определим тип GExp
{-# LANGUAGE GADTs #-}
data Expa where
ValTrue
:: Exp Bool
ValFalse
:: Exp Bool
If
:: Exp Bool -> Expa -> Expa -> Expa
Val
:: Int -> Exp Int
Add
:: Exp Int -> Exp Int -> Exp Int
Mul
:: Exp Int -> Exp Int -> Exp Int
Теперь у нашего типа Expпоявился параметр, через который мы кодируем дополнительные ограничения
на типы операций. Теперь мы не сможем составить выражение Add ValTrue ValFalse, потому что оно не
пройдёт проверку типов.
Определим функцию eval:
eval :: Expa ->a
eval x = casex of
ValTrue
-> True
ValFalse
-> False
Ifp t e
-> ifeval p theneval t elseeval e
Valn
->n
Adda b
->eval a +eval b
Mula b
->eval a *eval b
Если eval получит логическое значение, то будет возвращено значение типа Bool, а на значение типа Exp
Intбудет возвращено целое число. Давайте убедимся в этом:
Расширения | 255
*Prelude> :l Exp
[1 of1] Compiling Exp
( Exp.hs, interpreted )
Ok, modules loaded : Exp.
*Exp> letnotE x = Ifx ValFalse ValTrue
*Exp> letsquareE x = Mulx x
*Exp>
*Exp>eval $squareE $ If(notE ValTrue) ( Val1) ( Val2)
4
*Exp>eval $notE ValTrue
False
*Exp>eval $notE $ Add( Val1) ( Val2)
<interactive >:1 :14 :
Couldn’tmatch expected type‘ Bool’against inferred type‘ Int’
Expected type: Exp Bool
Actual type: Exp Int
Inthe return type ofa call of‘ Add’
Inthe second argument of‘( $)’, namely ‘Add (Val 1) (Val 2)’
Сначала мы определили две вспомогательные функции. Затем вычислили несколько значений. Haskell
Читать дальше