7

Haskell supports type classes, like equality:

class Eq a where 
  (==)                  :: a -> a -> Bool

Rust does the same with type traits:

pub trait Draw {
    fn draw(&self);
}

Now, it's possible to declare in Haskell a list whose elements must belong to the equality type class: Eq a => [a] (I believe a is called a constrained type in Haskell). However, the elements of the list still must all be the same type! Say, all Integer or all Float or something. In Rust, however, one can have a list (a vector) of values where each implements a given trait but they are not necessarily the same concrete type: Vec<Box<dyn Draw>>. Is there a way to do the same in Haskell? Like, I want a list of values but all I care about is that each belong to some type class but not necessarily the same concrete type.

4
  • 3
    Yes, and it's a very good question. But it's also an excellent demonstration of how different paradigms interact. I've used both Rust and Haskell extensively. I use dyn trait objects in Rust fairly frequently, and I almost never use existentials in Haskell. It's just a difference in the way the language nudges you and the way you solve problems in different languages. Commented Jan 30, 2022 at 19:44
  • Note that with Haskell Eq (I don't know how Rust does equality checks), having a list of objects of different types that all implement Eq would be entirely useless. That's because (==) :: a -> a -> Bool requires two objects known to be of the same type for the call to even typecheck. Only an interface that can do interesting things with a single object makes much sense to use with this capability. Commented Jan 30, 2022 at 22:36
  • 1
    @Ben I don't really think that's true. For example, data ExContainer where EC :: Num a => IntMap a -> ExContainer seems like it could plausibly use binary operators like (+) and (*) (or even (==) if you added Eq) nontrivially, even though the a is existentially quantified. Commented Jan 31, 2022 at 1:20
  • @DanielWagner True, although there the "interface" you get out of the existential is that it is specifically an IntMap of some unknown a that implements Num, and one could argue that that is an example of an interface that lets you do interesting things with a single value (being the IntMap a as a whole, which of course can containmultiple as). A list of your ExContainer, as the OP was thinking about, would have every element in the list need to contain an IntMap (just for varying a), and you couldn't use the instances across values from the two different maps. Commented Jan 31, 2022 at 8:40

1 Answer 1

9

In Haskell, you can use existential types to express "some unknown type of this typeclass". (In older versions of GHC, you will need a few standard extensions on.)

class Draw a where
   -- whatever the methods are

data SomeDraw where
   SD :: Draw a => a -> SomeDraw

type MyList = [SomeDraw]

However, note that this is often overkill, and leads to a known anti-pattern.

For instance, if we had a class as follows:

class Draw a where
   draw :: a -> String

then the type MyList above is isomorphic to [String] (or at least morally such). There is no advantage to store an unknown "drawable" object whose only method converts it to string compared to storing the string directly. Also note that Haskell is lazy, so you can "store a string which is not evaluated yet", so to speak.

Anyway, existential quantification on typeclasses can also be defined in a generic way:

import Data.Kind

-- Ex has the same role of "dyn" in Rust here
data Ex (c :: Type -> Constraint) where
    Ex :: c a => a -> Ex c

type MyList = [Ex Draw]
Sign up to request clarification or add additional context in comments.

6 Comments

Also note that you'll see some older docs on existential types that use the awkward original syntax data SomeDraw = forall a. Draw a => SD a (Yes, the word forall was used for existential quantifiers). This answer uses the newer and much cleaner GADT syntax, which I highly recommend. But you should be aware that the old way exists.
@SilvioMayolo "Yes, the word forall was used for existential quantifiers" I don't think this is an accurate way of describing what it means. Isn't it more accurate to say that it's a result of (completely validly) transforming "(∃x. P(x)) -> Q" into "∀x. (P(x) -> Q)"?
Maybe show an example of existential types actually being useful? Perhaps Coyoneda or Day?
@dfeuer GHC 9.2.1 defaults to GHC2021, in which all of the extensions that this code needs are already enabled, so it wouldn't be fair to say you need them anymore unless you also say you need ImplicitPrelude in basically every snippet everywhere.
@dfeuer I don't think those examples are related to this question which is about using existentials on typeclasses. Feel free to edit, though.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.