Reputation: 2229
In the project I have several different types, defined in different modules and each of them has related functions (the functions have the same name and very similar meaning, so the following make sense). Now I want to create a list, in which it will be possible to have instances of all these types (simultaneously). The only possibility I can think of is something like this:
data Common = A{...} | B{...} | ...
but it implies keeping the definition in a single place, and not in different modules (for A, B, ...). Is there a better way to do this?
UPD
I'm rather new to haskell and write some programs related to my studying. In this case I have different FormalLanguage
definition methods: FiniteAutomata
, Grammars
and so on. Each of them has common functions (isAccepted
, representation
, ...), so it seemed logical to have a list where elements can be of any of these types.
Upvotes: 0
Views: 275
Reputation: 35089
You are bringing an OOP mindset to Haskell by assuming the correct solution is to store distinct types in a list. I'll begin by examining that asssumption.
Usually we store distinct types in a homogeneous list because they support a common interface. Why not just factor out the common interface and store THAT in the list?
Unfortunately, your question does not describe what that common interface is, so I will just introduce a few common examples as demonstrations.
The first example would be a bunch of values, x
, y
, and z
, that all support the Show
function, which has the signature:
(Show a) => a -> String
Instead of storing the type we want to show later on, we could instead just call show
directly on the values and store the resulting strings in the list:
list = [show x, show y, show z] :: String
There's no penalty for calling show
prematurely because Haskell is a lazy language and won't actually evaluate the show
s until we actually need the string.
Or perhaps the type supports multiple methods, such as:
class Contrived m where
f1 :: m -> String -> Int
f2 :: m -> Double
We can transform classes of the above form into equivalent dictionaries that contain the result of partially applying the methods to our values:
data ContrivedDict = ContrivedDict {
f1' :: String -> Int,
f2' :: Double }
... and we can use this dictionary to package any value into the common interface we expect it to support:
buildDict :: (Contrived m) => m -> ContrivedDict
buildDict m = ContrivedDict { f1' = f1 m, f2' = f2 m }
We can then store this common interface itself in the list:
list :: [buildDict x, buildDict y, buildDict z]
Again, instead of storing the distinctly-typed values, we've factored out their common elements for storage in the list.
However, this trick won't always work. The pathological example is any binary operator that expect two operands of equal type, such as the (+)
operator from the Num
class, which has the following type:
(Num a) => a -> a -> a
As far as I know, there is no good dictionary-based solution for partially applying a binary operation and storing it in such a way that it guarantees it is applied to a second operand of the same type. In this scenario the existential type class is probably the only valid approach. However, I recommend you stick to the dictionary-based approach when possible as it permits more powerful tricks and transformations than the type-class-based approach.
For more on this technique, I recommend you read Luke Palmer's article: Haskell Antipattern: Existential Typeclass.
Upvotes: 5
Reputation: 13677
There are few possibilities:
Possibility 1:
data Common = A AT | B BT | C CT
with AT, BT and CT described in their respective modules
Possibility 2:
{-# LANGUAGE ExistentialQuantification #-}
class CommonClass a where
f1 :: a -> Int
data Common = forall a . CommonClass a => Common a
which is almost the same as OOP superclass, but you cannot do "downcasts". You can then declare implementations for members of common classes in all the modules.
Possibility 3 suggested by @Gabriel Gonzalez:
data Common = Common {
f1 :: Int
}
So your modules implement common interface by using closures to abstract over the 'private' part.
However, Haskell design is usually radically different from OOP design. While it's possible to implement every OOP trick in Haskell, it will be likely non-idiomatic, so as @dflemstr said more information about your problem is welcome.
Upvotes: 5