Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

An interface in OOP langs is more similar to the Existential data type which the author is trying to avoid using in this post, because it is sometimes seen as an anti-pattern in Haskell (although some people take this to the extreme and tell you to avoid Existentials completely).

If we take for example, a simple IRenderable interface

    interface IRenderable {
        void Render();
    }
There's a bit of boilerplate to add, but we can get something pretty similar in Haskell:

    class Renderable a where
      render :: a -> IO ()

    data Render = forall a. Renderable a => Render a
    instance Renderable Render where
      render (Render a) = render a

The obvious difference between the author's proposed solution and this OOP-style interface is the open versus closed world assumption. By having an interface, we have an open world in which we can easily add new types to render, without changing existing code - only adding new instances. In the solution proposed by the author (and the various other "solutions"), they break the open world assumption and fall back to a closed world - where you need to create specific types which encapsulate all the known types that can be rendered - this is demonstrated by the author's Game and ExtendedGame types - if you need to create a new "ExtendedExtendedGame" type each time you add a new renderable type - this solution obviously does not scale, does it?

With the Existential now, we can create a list of Render, which would be similar to having a list of IRenderable in Java. We can't do anything with the list other than call render on each item - which is almost always what we want to do anyway - so the oft-reported "existential anti-pattern" is usually no such thing. In fact, the claim that this is an antipattern stems from the idea that existentials are "type-erasing" - that you might want to convert back from a Render to a Ball for instance (or from an IRenderable to a Ball).

Even in Java, this would be a bad idea - it requires an explicit cast which could fail at runtime - you simply wouldn't do such thing unless you were certain of its type, or you guarded such cast by first using the `instanceof` operator.

    if (r instanceof Ball) {
        Ball b = (Ball)r;
        ...
    } 
You would not normally do this directly on each type, as you'd be back to a closed-world assumption. Instead, you'd usually create a mapping of types->functions, where you can dynamically test a type and do the relevant action, and you can continue to add new items to the map.

In the event we do want a list of renderable items, and we don't want to lose type information - Haskell can also provide a similar type-cast to the one you'd use in Java - it's rightly called `unsafeCoerce` because it is unsafe - as is the Java version, which throws a ClassCastException when used incorrectly.

If we modify the existential data type to also include type information, via Haskell's Data.Typeable module, we can encapsulate the bad behavior of unsafeCoerce, and ensure that we only expose a safe version - one that returns "Maybe x" instead of "x, but may fail".

    {-# LANGUAGE ExistentialQuantification #-}

    module X.Render (
        Render,
        toRender,
        fromRender
        ) where
        
    import Data.Typeable
    import Unsafe.Coerce

    data Render = forall a. (Typeable a, Renderable a) => Render TypeRep a

    instance Renderable Render where
        render (Render _ a) = render a

    toRender :: (Typeable a, Renderable a) => a -> Render
    toRender a = Render (typeOf a) a

    fromRender :: (Typeable a, Renderable a) => Render -> Maybe a
    fromRender (Render t a) =
      case unsafeCoerce a of x | t == typeOf x -> Just x
                               | otherwise -> Nothing
You can even go as far as emulating Java's instanceof operator (althought not quite the same - it doesn't handle subtyping), just create a function instanceof in a typeclass, and use it as an infix operator.

    class InstanceOf a where
        instanceOf :: a -> TypeRep -> Bool
        
    instance InstanceOf Render where
        instanceOf (Render tReal _) tWanted | tReal == tWanted = True
        instanceOf _ _ = False

    ...

    (toRender Ball) `instanceOf` (typeOf Ball) == True


Consider applying for YC's Summer 2026 batch! Applications are open till May 4

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: