Reputation: 95
I've started playing with Haskell, read some tutorials and one of the official books (lyah). I felt myself able to start my very first personal project with. And as for any new language I pick, I wanted to implement a package for linear algebra processing (operations around matrices, vectors, etc.). Functions are OK, but I didn't went too far with data types.
Originally, I had a function that looked like:
add_vect :: (Num a) => [a] -> [a] -> [a]
add_vect x y = zipWith (+) x y
Now I want to give a name (Vector
) to what [a]
means, so that vect_add
would look like:
vect_add :: Vector -> Vector -> Vector
vect_add x y = zipWith (+) x y
After many ambitious tries, I ended up (inspired by the definition of String
) with a very simple definition:
type Vector = [Int]
The problem with this is that I loose the type genericity for my function which now works only of [Int]
instead of any numeral type.
My question is: Is there any way to express genericity (using type calsses for instance) into the definition of new types. Something similar to:
type Vector = (Num a) => [a]
or maybe any other way to keep my Vector
's genericity?
Upvotes: 2
Views: 925
Reputation: 74404
One "right" way is to just note that you're only using Vector
for documentation purposes and so type Vector a = [a]
isn't a bad thing. Then you have vect_add :: Vector a -> Vector a -> Vector a
which must match and even vector-space
's (*^) :: a -> Vector a -> Vector a
.
Upvotes: 0
Reputation: 120751
Well...
{-# LANGUAGE RankNTypes #-}
type Vector = forall a . Num a => [a]
But that's not really what you want: you'd end up with
addVect :: (forall a.Num a => [a]) -> (forall b.Num b => [b]) -> (forall c.Num c => [c])
which can't be defined (each of the vectors could have different numerical types).
As said by Paul Johnson, you could use type Vector a = [a]
(or, equivalently, type Vector = []
). But I don't think that's really what you want: you basically end up with the same signatures as you have now, and it's not really natural to have vector-functions parametrically polymorphic on the field that spans the vector space.
The right solution, IMO, is the one taken by the vector-space
package: (simplified)
class VectorSpace v where
type Scalar v :: * -- This is going to be the type of the
-- field ("of the components of each vector")
(^+^) :: v -> v -> v -- You call this `addVect`
(*^) :: (Scalar v) -> v -> v
...
You can then have e.g.
data Vectorℝ2 = Vectorℝ2 !Double !Double
instance VectorSpace Vectorℝ2 where
type Scalar Vectorℝ2 = Double
Vectorℝ2 x y ^+^ Vectorℝ2 x' y' = Vectorℝ2 (x+x') (y+y')
...
or
newtype Vectorℝn = Vectorℝn [Double]
instance VectorSpace Vectorℝn where
type Scalar Vectorℝn = Double
Vectorℝn xs ^+^ Vectorℝn x's = Vectorℝn $ zipWith (+) xs x's
BTW, zipWith(+)
isn't really a good definition for variable-dimensional vector addition: you'd get e.g.
[1,2,3] ^+^ [4,5] ≡ [5,7]
Though actually I'd expect [4,5] ≅ [4,5,0]
in the vector sense, and thus [1,2,3] ^+^ [4,5] ≡ [5,7,3] ≆ [5,7]
.
Upvotes: 3
Reputation: 17806
You can't do what you want to do, because the type checker would have no way of knowing that all three vectors are the same type. Otherwise you could write code like this:
mkVect :: [a] -> Vector -- You are going to need something with this type.
x1 :: [Int]
x1 = [1,2,3]
x2 :: [Double]
x2 = [1.0,2.0,3.0]
v3 = add_vect (mkVect x1) (mkVect x2)
The only way the type checker can stop this is by having the type parameter for Vector as part of add_vect.
So instead you have to write
type Vector a = [a]
That way the type checker can see what you are doing.
Upvotes: 3