2012-05-11 3 views
3

Я хочу определить новый абстрактный тип данных, который является либо общей конструкцией числа, либо разделом. Как мне это сделать в Haskell?Как определить абстрактный тип данных, например «данные MyMath = MyNum Num»?

Мой первый подход:

data MyMath = MyNum Num 
      | Div MyMath MyMath 

Проблема заключается в том, что компилятор жалуется на «Num», который не является типом данных, но класс типа. Так что моя вторая мысль была бы решить эту проблему следующим образом:

data MyMath = MyNum Int 
      | MyNum Float 
      | Div MyMath MyMath 

Но это не будет работать либо как используются тупит дважды, не допускаются, дополнительно этот подход не будет действительно полиморфным. Итак, каково решение этой проблемы?

EDIT2: После (снова) чтения ответов я попытался использовать конструкторы данных GADT. Это некоторый искусственный пример кода:

5 data MyMathExpr a where 
6    MyNumExpr :: Num a => a -> MyMathExpr a 
7    MyAddExpr :: MyMathExpr b -> MyMathExpr c -> MyMathExpr (b, c) 
8 deriving instance Show(MyMathExpr a) 
9 deriving instance Eq(MyMathExpr a) 
10 
11 data MyMathVal a where 
12     MyMathVal :: Num a => a -> MyMathVal a 
13 deriving instance Show(MyMathVal a) 
14 deriving instance Eq(MyMathVal a) 
15 
16 foo :: MyMathExpr a -> MyMathVal a 
17 foo (MyNumExpr num) = MyMathVal num 
18 foo (MyAddExpr num1 num2) = MyMathVal (l + r) 
19 where (MyMathVal l) = foo num1 
20   (MyMathVal r) = foo num2 

Но что-то не так с линии номер 18:

test.hs:18:40: 
Couldn't match type `b' with `(b, c)' 
    `b' is a rigid type variable bound by 
     a pattern with constructor 
     MyAddExpr :: forall b c. 
        MyMathExpr b -> MyMathExpr c -> MyMathExpr (b, c), 
     in an equation for `foo' 
     at test.hs:18:6 
In the first argument of `(+)', namely `l' 
In the first argument of `MyMathVal', namely `(l + r)' 
In the expression: MyMathVal (l + r) 

То же самое относится и к `с». Думаю, это глупая ошибка, которую я просто не вижу. Вы?

+3

Я не вижу никаких логических значений здесь. –

+0

Спасибо, Дон, отправляя этот вопрос, я забыл приспособить изменения, которые я делал тем временем, ко всем необходимым местам - теперь это должно быть яснее. – Bastian

ответ

3

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

data (Num a) => MyMath a = MyMath {x :: a}

+4

Обратите внимание, что контексты данных не должны использоваться. Он считается недостоверным, и он устарел в новых версиях GHC. GADT намного более гибкие. – Vitus

0

Как уже упоминалось, ваши попытки не связаны с булерами, но вместо этого я перейду к тексту вашего вопроса.

Вам не нужно изобретать этот тип, ознакомьтесь с Either в Прелюдии. Таким образом, вы ищете Either a Bool, где вы хотите, чтобы a являлся экземпляром Num. Если вы хотите на самом деле обеспечить соблюдение этого правила, подготовьте его ниже.

Edit: Если вы не хотите использовать Either, вы можете сделать data MyMath a = MyNum a | MyBool Bool. Теперь вы можете привести в исполнение a, являющийся экземпляром Num, если хотите, но вы можете сначала рассмотреть this SO question и this answer to it. Нет необходимости принудительно применять экземпляр для типа данных; просто сделайте это для функций, использующих это intsead.

3

Вы можете использовать экзистенциальную квантификацию для этого:

> let data MyMath = forall n. Num n => MyNum n 
> :t MyNum 3 
MyNum 3 :: MyMath 
> :t MyNum 3.5 
MyNum 3.5 :: MyMath 
+0

Проблема с экзистенциальной квантификацией заключается в том, что я не могу использовать привязки шаблонов, такие как «(Num n) = someFunction» - компилятор просто жаловался. – Bastian

+0

Уверен, что он может. Вам просто нужно предположить, что 'n' - это некоторый экземпляр типа« num ». Если у вас возникнут проблемы, просто покажите нам код, основанный на этом подходе, который не работает. –

+0

Хотя вы можете исправить эту конкретную ошибку, вы скоро обнаружите, что экзистенциальные типы бесполезны для вашей проблемы. Давайте проигнорируем тот факт, что '/' не является частью 'Num' в течение некоторого времени и вместо этого использует' + ', я также буду использовать' l' и 'r' из вашего кода выше. Имеет ли 'l + r' даже смысл? Мы знаем, что 'l :: Num a => a' для некоторого типа' a' и 'r :: Num b => b' для некоторого типа' b'. '+' требует, чтобы его аргумент имел один и тот же тип, но мы этого не знаем; 'a' вполне может быть' Int' и 'b' может быть' Float'! – Vitus

1

Есть много способов сделать это. Один из способов с GADTs:

{-# LANGUAGE GADTs #-} 

data MyMath where 
    MyNum :: Num a => a -> MyMath 
    MyBool :: Bool -> MyMath 

Другой способ с GADTs:

{-# LANGUAGE GADTs #-} 

data MyMath a where 
    MyNum :: Num a => a -> MyMath a 
    MyBool :: Num a => Bool -> MyMath a