3

I want to define a new abstract data type which is either a general Number or a Division construct. How would I do that in Haskell?

My first approach was:

data MyMath = MyNum Num
            | Div MyMath MyMath

The problem is that the compiler complains about "Num" which is not a data type but a type class. So my second thought would be to solve the problem like this:

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

But this would not work either as MyNum is used twice which is not allowed, additionaly this approach would not really be polymorphic. So what is the solution to this problem?

EDIT2: After (again) reading the answers I tried to use GADT data constructors. This is some artificial example code:

 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

But something is wrong with line number 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)

The same goes for `c'. I guess it's a stupid mistake which I just don't see. Do you?

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
Bastian
  • 4,638
  • 6
  • 36
  • 55

4 Answers4

3

This solves the problem you're addressing in the code, but doesn't cover for the boolean. If you want to use a class constraint in a data declaration, you do it the way that you would with any other function:

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

rotskoff
  • 714
  • 3
  • 10
  • 4
    Just note that data contexts shouldn't be used. It is considered a misfeature and it is deprecated in newer versions of GHC. GADTs are far more flexible. – Vitus May 11 '12 at 15:14
3

You can use existential quantification for that:

> let data MyMath = forall n. Num n => MyNum n
> :t MyNum 3
MyNum 3 :: MyMath
> :t MyNum 3.5
MyNum 3.5 :: MyMath
myki
  • 773
  • 5
  • 7
  • The problem with existential quantification is that I can't use pattern bindings like "(Num n) = someFunction" - the compiler just complained. – Bastian May 12 '12 at 10:02
  • Sure it can. You just have to assume that `n` is some _unknown_ `Num` typeclass instance. If you continue having problems, just show us the code based on this approach that isn't working. – Louis Wasserman May 12 '12 at 14:50
  • While you can fix this particular error, you'll soon find that existential types are useless for your problem. Let's disregard the fact that `/` is not part of `Num` for a while and use `+` instead, I'm also going to use `l` and `r` from your code above. Does `l + r` even make sense? We know that `l :: Num a => a` for some type `a` and `r :: Num b => b` for some type `b`. `+` requires its argument to have a same type, but we _don't_ know that; `a` could very well be an `Int` and `b` could be a `Float`! – Vitus May 13 '12 at 00:57
  • You should get rid of the existential quantification and instead expose the type of the number inside. Fix for your GADT code should be simple: `data MyMathExpr a where ...` and same goes for `MyMathVal`. – Vitus May 13 '12 at 01:09
  • Ok, I think that I finally got the data definitions right after reading this: http://www.haskell.org/haskellwiki/GADTs_for_dummies. But now the compiler tells me I'm doing something stupid when I'm trying to pattern match (look at my updated post). – Bastian May 13 '12 at 11:15
  • Since `MyAddExpr num1 num2` has type `MyMathExpr (b,c)`, you have to return a value of type `MyMathVal (b,c)`. But only way to construct such value is `MyMathVal a` for `a :: Num (b,c) => (b,c)`, which makes no sense. Other than that, `+` needs both arguments to have the same type, but in this case, `l` and `r` again have (potentionally) distinct types. Change `MyAddExpr` to `MyAddExpr :: MyMathExpr a -> MyMathExpr a -> MyMathExpr a`. – Vitus May 13 '12 at 13:49
1

There are many ways to do it. One way is with GADTs:

{-# LANGUAGE GADTs #-}

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

Another way with GADTs:

{-# LANGUAGE GADTs #-}

data MyMath a where
    MyNum :: Num a => a -> MyMath a
    MyBool :: Num a => Bool -> MyMath a
nponeccop
  • 13,527
  • 1
  • 44
  • 106
0

As has been mentioned, your attempts don't involve any booleans, but I'll go with the text of your question instead.

You don't have to invent this type, check out Either in the Prelude. What you're looking for is thus Either a Bool, where you want a to be an instance of Num. If you want to actually enforce this, ready the edit below.

Edit: If you don't want to use Either, you can do data MyMath a = MyNum a | MyBool Bool. Now you can enforce a being an instance of Num if you want to, but you may want to consider this SO question and this answer to it first. There's really no need to enforce the instance for the data type; just do it for the functions using it intsead.

Community
  • 1
  • 1
gspr
  • 11,144
  • 3
  • 41
  • 74