Reputation: 211
I am trying to define type class instances to work with vectors of 1-4 components of Num
, represented as tuples. This has been the best solution I have found (from my last question)
{-# LANGUAGE FlexibleInstances #-}
data OneTuple a = OneTuple a deriving Show
class MyClass a where
prints :: a -> String
instance (Show n, Num n) => MyClass (n, n) where
prints = show
instance (Show n, Num n) => MyClass (n, n, n) where
prints (a, b, c) = foldl (++) "" $ map (++" ") $ map show [a,b,c]
instance (Show n, Num n) => MyClass (OneTuple n) where
prints (OneTuple a) = show a
but this comes with the requirement that prints 1
be written as prints $ OneTuple 1
which is undesirable. I tried implementing a tuplify
function for this, but
wrap :: (MyClass a) => (String -> String) -> a -> String
wrap f = f . prints
tuplify :: (MyClass b) => a -> b
tuplify (a,b) = (a,b)
tuplify (a,b,c) = (a,b,c)
tuplify a = OneTuple a
stringFunction :: String -> String
stringFunction = id
vectorToString x = wrap stringFunction $ tuplify x
doesn't compile. I am trying to arrive at something that looks like this
vectorToString (1,2,3)
vectorToString (1,2)
vectorToString 1
Is this even possible? I think I remember reading something about how a function's behavior should not change depending on the input type, which is exactly what tuplify
is doing. Is there a different way I could solve this problem?
This is the error message from ghci:
question.hs:18:9: error:
• Couldn't match expected type ‘a’ with actual type ‘(a0, b0)’
‘a’ is a rigid type variable bound by
the type signature for:
tuplify :: forall b a. MyClass b => a -> b
at question.hs:17:1-32
• In the pattern: (a, b)
In an equation for ‘tuplify’: tuplify (a, b) = (a, b)
• Relevant bindings include
tuplify :: a -> b (bound at question.hs:18:1)
|
18 | tuplify (a,b) = (a,b)
| ^^^^^
question.hs:18:17: error:
• Couldn't match expected type ‘b’ with actual type ‘(a0, b0)’
‘b’ is a rigid type variable bound by
the type signature for:
tuplify :: forall b a. MyClass b => a -> b
at question.hs:17:1-32
• In the expression: (a, b)
In an equation for ‘tuplify’: tuplify (a, b) = (a, b)
• Relevant bindings include
b :: b0 (bound at question.hs:18:12)
a :: a0 (bound at question.hs:18:10)
tuplify :: a -> b (bound at question.hs:18:1)
|
18 | tuplify (a,b) = (a,b)
| ^^^^^
Upvotes: 0
Views: 74
Reputation: 120711
I think I remember reading something about how a function's behavior should not change depending on the input type
Sounds like you're thinking of the Liskov substitution principle. But that's something from the OO world, which doesn't apply to Haskell. You can't even really formulate it, because Haskell has no subtypes.
Haskell does have a related principle though: parametrically polymorphic functions can't have different behaviour depending on which types are used. But this is not so much a principle the programmer should obey, as something that's enforced by the language. That's one reason why your different cases for tuplify
can't possibly compile: there you try to, like, pattern-match on types. This is not something you can do in Haskell function declarations.
However, it is almost exactly what typeclasses do, and indeed your own typeclass does it already.
There's another problem with your tuplify
: the signature MyClass b => a -> b
means the caller gets to pick what both a
and b
are, and then the function would somehow have to be able to convert between them. For example it would have to be able to convert a 2-tuple into a 3-tuple. What you probably intend to express there is that every input type is associated a result type – i.e. a type-level function. We call those type families, and indeed you can write
{-# LANGUAGE TypeFamilies #-}
type family Tuplified t where
Tuplified (a,b) = (a,b)
Tuplified (a,b,c) = (a,b,c)
Tuplified t = t
However, that still doesn't really bring you any further: now you have a type-level function, but no corresponding value-level functions for the individual cases. That function would have to have type
tuplify :: t -> Tuplified t
but you can't define it without resorting to a type class. You could write such a type class, but then you would just run into the same problem you tried to solve in your other question in the first place!
I don't see how any of this makes sense. Probably, your class instances should actually be more like the standard Show
class:
instance MyClass Integer where prints = show
instance MyClass Int where prints = show
instance (MyClass a, b~a) => MyClass (a,b) where
prints (x,y) = '(' : prints x ++ "," ++ prints y ++ ")"
Upvotes: 1