Let's say Goo
is my type class, which is often claimed to be the interface equivalent in languages like C++, Java or C#:
class Goo goo where ham :: goo -> String
data Hoo = Hoo
instance Goo Hoo where ham _ = "Hoo!"
mustard _ = "Oh oh."
data Yoo = Yoo
instance Goo Yoo where ham _ = "Yoo!"
mustard _ = "Whew"
But I cannot return a Goo
:
paak :: (Goo goo) => String -> goo
paak g = (Yoo)
-- Could not deduce (goo ~ Yoo)
-- from the context (Goo goo)
-- bound by the type signature for paak :: Goo goo => String -> goo
-- at frob.hs:13:1-14
-- `goo' is a rigid type variable bound by
-- the type signature for paak :: Goo goo => String -> goo
-- at frob.hs:13:1
-- In the expression: (Yoo)
-- In an equation for `paak': paak g = (Yoo)
I found this enlightening statement, which explains why:
The type
paak :: (Goo goo) => String -> goo
does not mean that the function might return anyGoo
it wants. It means that the function will return whicheverGoo
the user wants.
(transliterated from sepp2k's answer here)
But then, how could I return or store something that satisfies the Goo
constraints, but can be Hoo
, Yoo
, Moo
, Boo
or any other Goo
?
Am I just entangled too much in own programming background, and need to think completely different, like resorting to C-like interfaces:
data WhewIamAGoo = WhewIamAGoo {
ham' :: String
mustard' :: String
}
paak :: String -> WhewIamAGoo
paak g = let yoo = Yoo
in WhewIamAGoo { ham' = ham yoo
mustard' = mustard ham
}
But that seems awkward.
In my specific case, I would like to use Goo
like this:
let x = someGoo ....
in ham x ++ mustard x
I.e. the caller should not need to know about all the Yoo
s and whatnot.
edit: To clarify: I am looking for the way a Haskell programmer would go in such situation. How would you handle it in an idiomatic way?
There are two ways of solving this problem that I consider idiomatic Haskell:
Algebraic data type
data Goo = Hoo | Yoo
ham Hoo = "Hoo!"
ham Yoo = "Yoo!"
mustard Hoo = "Oh oh."
mustard Yoo = "Whew"
Pro: easy to add new operations
Con: adding a new "type" potentially requires modifying many existing functions
Record of supported operations
data Goo = Goo { ham :: String, mustard :: String }
hoo = Goo { ham = "Hoo!", mustard = "Oh oh." }
yoo = Goo { ham = "Yoo!", mustard = "Whew" }
Pro: easy to add new "types"
Con: adding a new operation potentially requires modifying many existing functions
You can of course mix and match these. Once you get used to thinking about functions, data and composition rather than interfaces, implementations and inheritance, these are good enough in a majority of cases.
Type classes are designed for overloading. Using them to mimic object-oriented programming in Haskell is usually a mistake.
Typeclasses are a bit like Java-style interfaces, but you don't really use them precisely the same way you use interfaces, so it's not a great way to learn them.
An interface is a type (because OO languages have subtypes, so other types can be subtypes of the interface, which is how you get anything done). All types are disjoint in Haskell, so a type class is not a type. It's a set of types (the instance declarations are where you declare what the members of the set are). Try to think of them this way. It makes the correct reading of type signatures much more natural (String -> a
means "takes a String
and returns a value of any type you want", and SomeTypeClass a => String -> a
means "takes a String
and returns a value of any type you want that is a member of SomeTypeClass
").
Now you can't do what you want the way you want it, but I'm not sure why you need to do it the way you want it. Why can't paak
just have the type String -> Yoo
?
You say you're trying to do something like:
let x = someGoo ....
in ham x ++ mustard x
If someGoo ...
is paak "dummy string"
, then x
will be of type Yoo
. But Yoo
is a member of Goo
, so you can call Goo
methods like ham
and mustard
on it. If you later change paak
to return a value in a different Goo
type, then the compiler will tell you all the places that used any Yoo
-specific functionality, and happily accept unchanged any places that called paak
but then only used the Goo
functionality.
Why do you need it to be typed "some unknown type which is a member of Goo
"? Fundamentally, callers of paak
don't operate on any type in Goo
, they only operate on what paak
actually returns, which is a Yoo
.
You have some functions that operate on concrete types, which can call functions on those concrete types as well as functions that come from type classes of which the concrete type is a member. Or you have functions which operate on any type which is a member of some type class, in which case all you can call are functions that work on any type in the type class.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With