44

What are some techniques I might use to consistently refactor code removing the reliance on existential types? Typically these are used to disqualify undesired constructions of your type as well as to allow consumption with a minimal of knowledge about the given type (or so is my understanding).

Has anyone come up with a simple consistent way to remove reliance on these in code which still maintains some of the benefits? Or at least any ways of slipping in an abstraction that allows their removal without requiring significant code churn to cope with the alteration?

You can read more about existential types here ("if you dare..").

10
  • 8
    @RobertHarvey I found the blog post that made me think about this question the first time: Haskell Antipattern: Existential Typeclass. Also, the paper Joey Adams mentions describes some poblems with existentials in Section 3.1. If you have contrary arguments, please share them.
    – Petr
    Commented Jan 27, 2013 at 8:10
  • 5
    @PetrPudlák: Keep in mind that the antipattern there is not existential types in general, but a particular use of them when something simpler and easier (and better supported in Haskell) would do the same job. Commented Jan 28, 2013 at 14:21
  • 10
    If you want to know why the author of a particular blog post expressed an opinion then the person you should probably ask is the author. Commented Feb 25, 2013 at 16:17
  • 6
    @Ptolemy That's a matter of opinion. Using Haskell for years, I can't hardly imagine using a functional language that doesn't have a strong type system.
    – Petr
    Commented Mar 7, 2015 at 21:00
  • 4
    @Ptolemy: Haskell's mantra is "if it compiles it works", Erlangs mantra is "let it crash". Dynamic typing is good as a glue, but I personally wouldn't build things using just the glue.
    – Den
    Commented Mar 13, 2015 at 12:17

2 Answers 2

8

Existential types are not really considered bad practice in functional programming. I think what's tripping you up is that one of the most commonly cited uses for existentials is the existential typeclass antipattern, which many people do believe is bad practice.

This pattern is often trotted out as a response to the question of how to have a list of heterogeneously typed elements that all implement the same typeclass. For example, you may want to have a list of values that have Show instances:

{-# LANGUAGE ExistentialTypes #-}

class Shape s where
   area :: s -> Double

newtype Circle = Circle { radius :: Double }
instance Shape Circle where
   area (Circle r) = pi * r^2

newtype Square = Square { side :: Double }
    area (Square s) = s^2

data AnyShape = forall x. Shape x => AnyShape x
instance Shape AnyShape where
    area (AnyShape x) = area x

example :: [AnyShape]
example = [AnyShape (Circle 1.0), AnyShape (Square 1.0)]

The problem with code like this is this:

  1. The only useful operation you can perform on an AnyShape is to get its area.
  2. You still need to use the AnyShape constructor to bring one of the shape types into the AnyShape type.

So as it turns out, that piece of code doesn't really get you anything that this shorter one doesn't:

class Shape s where
   area :: s -> Double

newtype Circle = Circle { radius :: Double }
instance Shape Circle where
   area (Circle r) = pi * r^2

newtype Square = Square { side :: Double }
    area (Square s) = s^2

example :: [Double]
example = [area (Circle 1.0), area (Square 1.0)]

In the case of multi-method classes, the same effect can be generally accomplished more simply by using a "record of methods" encoding—instead of using a typeclass like Shape, you define a record type whose fields are the "methods" of the Shape type, and you write functions to convert your circles and squares into Shapes.


But that doesn't mean that existential types are a problem! For example, in Rust they have a feature called trait objects that people often describe as an existential type over a trait (Rust's versions of type classes). If existential typeclasses are an antipattern in Haskell, does that mean that Rust picked a bad solution? No! The motivation in the Haskell world is about syntax and convenience, not really about principle.

A more mathematical way of putting this is pointing out that the AnyShape type from above and Double are isomorphic—there is a "lossless conversion" between them (well, save for floating point precision):

forward :: AnyShape -> Double
forward = area

backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))

So strictly speaking, you're not gaining or losing any power by choosing one vs. the other. Which means the choice should be based on other factors like ease of use or performance.


And keep in mind that existential types have other uses outside of this heterogeneous lists example, so it's good to have them. For example, Haskell's ST type, which allows us to write functions that are externally pure but internally use memory mutation operations, uses a technique based on existential types in order to guarantee safety at compilation-time.

So the general answer is that there's no general answer. Uses of existential types can only be judged in context—and the answers may be different depending on what features and syntax are provided by different languages.

1
  • That is not an isomorphism.
    – Ryan Reich
    Commented Oct 31, 2019 at 12:50
2

I am not too familiar with Haskell, so I will try to answer the general part of the question as a non-academic functional C# developer.

After doing some reading, it turns out that:

  1. Java wildcards are similar to existential types:

    Difference between Scala's existential types and Java's wildcard by example

  2. Wildcards aren't implemented in C# completely: generic variance is supported, but call site variance is not:

    C# Generics: Wildcards

  3. You might not need this feature everyday, but when you do you will feel it (e.g. having to introduce an extra type to make things work):

    Wildcards in C# generic constraints

Based on this information existential types / wildcards are useful when implemented properly and there is nothing wrong with them per se, but they can probably be misused just like other language features.

Not the answer you're looking for? Browse other questions tagged or ask your own question.