Widget in the anti-pattern example is only a typeclass, so if we want to refer to “some Widget” we write forall a . Widget a => a. This is a useful type in some circumstances, but you can’t, e.g., write listOfWidget :: [forall a . Widget a => a], you’d instead have to write listOfAWidgetLikeThing :: forall a . Widget a => [a] which is semantically different.
As it turns out, it could be possible to write the former, but Haskell’s type system is a little too weak. The antipattern approach basically says that wishing for a stronger type system to solve this problem (either via use of existentials like AnyWidget or impredicative inference which would allow listOfWidget directly) is the wrong approach—just simplify things instead of trying to get the type system to handle your more complex ideas.
To my understanding, this is basically just passing around manually what typeclasses allow you to pass around implicitly. Presumably the existential type implementation would have more or less exactly the same performance (with the opportunity to specialize code written for specific widget types). I can see the appeal of doing it manually (existential data certainly feel un-idiomatic), but I’m not sure I’d call it an antipattern.
I’m not a very experienced functional programmer, so what I’m about to say might be nonsense; IMHO it is the need to resort to existential typeclasses that is the antipattern, so I don’t think passing around records of functions is a remedy. I think this kind of approach is in general against the spirit of functional programming where you define your program as composition of smaller functions.
For example, in OOP, you start by thinking about where to keep your objects. You might say things like, “let’s define an interface for all game objects, so I can keep them inside GameObject containers”. I tend to think of functional programming rather as constructing machines, as in, when you’re building a machine to grind wheat, you typically don’t have any wheat lying around. So, in FP, it’s more like you build little cogs and pipes, and when you assemble them they become a machine that will receive the ingredients. I guess the distinction is, you don’t think in terms of “there’s this thing, and this happens to it and then that, and then that”, but rather “there are these and these kinds of relationships between the things you’d see flowing in this machine”.
I’m sorry, that’s probably too abstract to be useful to anyone, and it might be total nonsense.
Existentials themselves are okay. For example, ML’s abstract types (created using either abstype or opaque signature ascription) are second-class existentials, and they’re very useful. Haskell doesn’t have abstract types, and has to simulate them using newtype or by hiding constructors, and this causes issues.
The problem described in the blog post is the use of superfluous existentials. More precisely, the type exists a. (a, a -> Foo) is already isomorphic to Foo (in a lazy language) or () -> Foo (in a strict one), so using an existential doesn’t buy you anything. For a more concrete example, if you have a list of Showables:
data AnyShow = forall a. Show a => AnyShow a
test :: [AnyShow]
test = [ AnyShow foo, AnyShow bar, AnyShow qux, ... ]
You effectively have a list of Strings, because the only thing you can do with an AnyShow is unpack the existential constructor and apply show to the result. So you might as well directly write:
test :: [String]
test = [ show foo, show bar, show qux, ... ]
I agree with you in the case of show, but what if your existential typeclass entails dozens of functions rather than just one?
An example that I came across recently that led me to use existentials was in the design of a toy game engine. The game engine has actors, which have behaviors, can be rendered, etc. I ended up using
data SomeActor = SomeActor (forall a . Actor a => a)
And then you can define Actor instances (and all implied instances) trivially.
Yes, I could have put this into a big record with tons of functions. But that’s annoying to wrangle and hard to extend. It’s also probably not very efficient to replace e.g.
class Physical a where
impact :: a -> Impulse -> a
class Physical a => Actor a where ...
With
data Actor = Actor {
...
impact :: Impulse -> Actor
....
}
Because now you have dozens of allocated closures per Actor, each referencing your Actor’s data, rather than a fixed number of statically defined functions that could all operate on the single piece of data you have saved.
If there’s a better approach, I’d love to hear it, because I do think existentials are ugly. But this seems like one of those cases where a vtable might just be the right thing.
I don’t think the antipattern is so much using existentials in general, as it is using exists a. (a, a -> Foo) (of course, wrapped in a proper existential constructor) when you can use Foo directly (in a lazy language) or () -> Foo (in a strict language).
I’m not sure I understand the anti-pattern. Why does the author need to wrap things with
AnyWidget, given that all the widgets are alsoWidget?Widgetin the anti-pattern example is only a typeclass, so if we want to refer to “someWidget” we writeforall a . Widget a => a. This is a useful type in some circumstances, but you can’t, e.g., writelistOfWidget :: [forall a . Widget a => a], you’d instead have to writelistOfAWidgetLikeThing :: forall a . Widget a => [a]which is semantically different.As it turns out, it could be possible to write the former, but Haskell’s type system is a little too weak. The antipattern approach basically says that wishing for a stronger type system to solve this problem (either via use of existentials like
AnyWidgetor impredicative inference which would allowlistOfWidgetdirectly) is the wrong approach—just simplify things instead of trying to get the type system to handle your more complex ideas.To my understanding, this is basically just passing around manually what typeclasses allow you to pass around implicitly. Presumably the existential type implementation would have more or less exactly the same performance (with the opportunity to specialize code written for specific widget types). I can see the appeal of doing it manually (existential data certainly feel un-idiomatic), but I’m not sure I’d call it an antipattern.
I’m not a very experienced functional programmer, so what I’m about to say might be nonsense; IMHO it is the need to resort to existential typeclasses that is the antipattern, so I don’t think passing around records of functions is a remedy. I think this kind of approach is in general against the spirit of functional programming where you define your program as composition of smaller functions.
For example, in OOP, you start by thinking about where to keep your objects. You might say things like, “let’s define an interface for all game objects, so I can keep them inside GameObject containers”. I tend to think of functional programming rather as constructing machines, as in, when you’re building a machine to grind wheat, you typically don’t have any wheat lying around. So, in FP, it’s more like you build little cogs and pipes, and when you assemble them they become a machine that will receive the ingredients. I guess the distinction is, you don’t think in terms of “there’s this thing, and this happens to it and then that, and then that”, but rather “there are these and these kinds of relationships between the things you’d see flowing in this machine”.
I’m sorry, that’s probably too abstract to be useful to anyone, and it might be total nonsense.
It’s kind of abstract but has the benefit of being totally correct so far as I understand it.
Existentials themselves are okay. For example, ML’s abstract types (created using either
abstypeor opaque signature ascription) are second-class existentials, and they’re very useful. Haskell doesn’t have abstract types, and has to simulate them usingnewtypeor by hiding constructors, and this causes issues.The problem described in the blog post is the use of superfluous existentials. More precisely, the type
exists a. (a, a -> Foo)is already isomorphic toFoo(in a lazy language) or() -> Foo(in a strict one), so using an existential doesn’t buy you anything. For a more concrete example, if you have a list ofShowables:You effectively have a list of
Strings, because the only thing you can do with anAnyShowis unpack the existential constructor and applyshowto the result. So you might as well directly write:I agree with you in the case of show, but what if your existential typeclass entails dozens of functions rather than just one?
An example that I came across recently that led me to use existentials was in the design of a toy game engine. The game engine has actors, which have behaviors, can be rendered, etc. I ended up using
And then you can define Actor instances (and all implied instances) trivially.
Yes, I could have put this into a big record with tons of functions. But that’s annoying to wrangle and hard to extend. It’s also probably not very efficient to replace e.g.
With
Because now you have dozens of allocated closures per Actor, each referencing your Actor’s data, rather than a fixed number of statically defined functions that could all operate on the single piece of data you have saved.
If there’s a better approach, I’d love to hear it, because I do think existentials are ugly. But this seems like one of those cases where a vtable might just be the right thing.
I don’t think the antipattern is so much using existentials in general, as it is using
exists a. (a, a -> Foo)(of course, wrapped in a proper existential constructor) when you can useFoodirectly (in a lazy language) or() -> Foo(in a strict language).