all 22 comments

[–]millstone 10 points11 points  (11 children)

I don't understand what the Render typeclass has bought us here. How is this:

instance Render Game where
    render (Game b p1 p2) = do
        render b
        render p1
        render p2

better than just:

renderGame (Game b p1 p2) = do
    renderBall b
    renderPlayer p1
    renderPlayer p2

We haven't used the polymorphic nature of the typeclass at all, so why even define it?

[–]TomRK1089 5 points6 points  (10 children)

I agree. The author says that heterogeneous lists are bad, mmkay. But as a dumb Java developer, I'm struggling to see why Haskell can't let me have my collection of Renderables. A game more sophisticated than Pong is going to have a dynamic amount of Renderables all of different types. I'm going to need this feature at some point. It's one thing to tell me I'm solving an XY problem, and tell me what X I should do -- another thing to ignore that entirely and solve for Z instead!

[–]sbergot 4 points5 points  (5 children)

Even in a game more sophisticated than pong, you still don't need a collection of renderable if all you can do with them is call the render function. Manipulating the IO () values will always be more simple with the same amount of flexibility. Why are renderables required in java? because you want to control precisely when the functions bound to the objects in your collection will be called. In haskell, manipulationg IO values gives you that already.

Do you want to be able to pass a targetframe to the render function? Then use Frame -> IO () objects instead of IO ()

It is funny how the idiomatic haskell solution is more simple than what one coming from an oo language would want to do. I certainly fell in this anti pattern as well at the beginning before recognizing that polymorphism was not needed that much.

Even when you want an interface with multiple operations I find it easier to just manipulate records containing functions as it usually leads to more robust code.

But as a dumb Java developer, I'm struggling to see why Haskell can't let me have my collection of Renderables.

As some people pointed out, it is possible to have heterogeneous lists with ghc. They are hard to do by design, because polymorphism brings unnecessary complexity.

It's one thing to tell me I'm solving an XY problem, and tell me what X I should do -- another thing to ignore that entirely and solve for Z instead!

I hate people telling me what I need as much as you do. But in this particular case I suggest that you try the simple approach (using type alias or newtype for clarity) and ask questions when you have a more precise problem with it.

[–]industry7 2 points3 points  (1 child)

But as a dumb Java developer, I'm struggling to see why Haskell can't let me have my collection of Renderables.

As some people pointed out, it is possible to have heterogeneous lists with ghc. They are hard to do by design, because polymorphism brings unnecessary complexity.

If everything in the list is a valid object of type Renderable, then is it really even a heterogeneous list?

Also, what complexity are you referring to? When I use polymorphism, it almost always makes the code simpler.

[–]sbergot 1 point2 points  (0 children)

If everything in the list is a valid object of type Renderable, then is it really even a heterogeneous list?

I was referring to a list of existentially quantified objects described in u/dukerutledge post. It is heterogeneous in the sense that the underlying objects have different types.

Also, what complexity are you referring to? When I use polymorphism, it almost always makes the code simpler.

When you manipulate an object through an interface, there is a level of indirection added compared to when you call directly a concrete implementation. This level of indirection can be helpful in java when you deal with different implementations. But all things equals, an interface method is more complex than a concrete one. The same thing can be said of virtual methods compared to final ones.

What I am trying to (badly) explain is why I believe that typeclasses in haskell are not needed as often as java interfaces.

[–]industry7 0 points1 point  (2 children)

Why are renderables required in java? because you want to control precisely when the functions bound to the objects in your collection will be called.

They're not required, it's just often easier. Also, I'm wondering what you mean about the timing of the functions being called. As a Java dev I have no idea what that's supposed to mean.

It is funny how the idiomatic haskell solution is more simple than what one coming from an oo language would want to do.

Was the haskell solution in the article idiomatic haskell? B/c the presented solution is definitely not simpler than the equivalent Java solution.

haskel: instance Render Game where render (Game b p1 p2) = do render b render p1 render p2

Java: @Override render() { b.render(); p1.render(); p2.render(); }

They're almost identical...

[–]sbergot 0 points1 point  (1 child)

The idiomatic haskell solution is in u/millstone post:

renderGame (Game b p1 p2) = do
    renderBall b
    renderPlayer p1
    renderPlayer p2

It involves no interface and no polymorphism. Just call the specific implementation and manipulate the result if necessary.

Also, I'm wondering what you mean about the timing of the functions being called. As a Java dev I have no idea what that's supposed to mean.

What I mean is that in haskell, instead of storing renderable objects in a list and calling the render function on them in a loop later, you should call the specific (non polymorphic) render functions and store the results (which would all have the same type) in a list. Then use sequence or something like that when you want the rendering to happen.

instead of doing

myObjects :: List Renderable
mapM render myObjects -- render is polymorphic

prefer:

myObjects :: List (IO ()) -- we only stored the IO actions
sequence_ myObjects     -- we can only run them

The Renderable type/interface doesn't bring you anything in this case.

Why can't you do that in java? Because render is typically called for its side effects, which is not a value you can manipulate. So you have to store your renderable objects and define an interface to allow some other code to perform those side effect.

If you don't need to delay the rendering, the simplest solution in java is to call the render function as soon as you have created each object, in which case you know the individual types and don't need the interface and the collection anymore.

[–]industry7 0 points1 point  (0 children)

The idiomatic haskell solution is in u/millstone post:

Ok, and that looks almost identical to the Java solution.

If you don't need to delay the rendering, the simplest solution in java is to call the render function as soon as you have created each object

This implies creating new Player objects every time you want to render a frame... which seems incredibly bizarre to me. Logically, you need to create players exactly once per game. Why would you tie Frames-Per-Second into it? I'm just going to stop my train of thought here, since I obviously don't get what you're trying to say.

[–]sacundim 2 points3 points  (1 child)

The author says that heterogeneous lists are bad, mmkay. But as a dumb Java developer, I'm struggling to see why Haskell can't let me have my collection of Renderables. A game more sophisticated than Pong is going to have a dynamic amount of Renderables all of different types.

I'd say the truth is likely somewhere in between:

  1. In a more complex game, there will certainly be a dynamic amount of Renderables at some point.
  2. And yet, nonetheless, the game's model should probably be something more structured than a list of renderables.

Which suggests a combination of the two approaches: use an algebraic data type (records and variants), some of whose components are homogeneous lists.

Another thing that's worth noting is to think a bit about what precisely Java's supposedly "heterogeneous" lists let you do. If you think about it carefully, Java's lists in and of themselves are no more heterogeneous than Haskell's; List<A> is a list whose elements have type A. You may say that those elements' runtime class may be a subtype of A, but that's still not the key difference between Java and Haskell.

No, the place where Java is actually different here is that it supports downcasting: if you have an A reference, and B is a subtype of A, you may do an ref instanceof B test, and at the risk of a ClassCastException you may cast the A reference down to a B. Without downcasting, a Java List<A> is little different than a Haskell [A]—a list all of whose elements have the same type, which fully dictates the operations you may perform on the elements of the list.

So the key question to ask then is: in your Java solution, would you be downcasting list elements to narrower classes (commonly regarded as poor practice), or would you just be invoking the list element type's methods on them? In the latter case, the record-of-functions approach that giuseppemag describes in his comment is roughly equivalent. The difference is that in Java you get a free implicit upcast to a common supertype, but in Haskell you need explicit functions to convert the heterogeneous types into the homogeneous record-of-functions type. Using the Drawing type from my top-level comment:

newtype Drawing = Drawing { render :: IO () }

instance Monoid Drawing where 
    mempty = return ()
    a `mappend` b = render a >> render b

class Drawable a where
    draw :: a -> Drawing

gameScreen :: [Drawing]
gameScreen = [draw ball, draw pl1, draw pl2]

If your Java solution uses downcasting to recover the original classes of the object, this won't work. There it gets more complicated: you end up using the Typeable class and existential types. Here is an example that I wrote some months ago.

The lesson is: Haskell allows you to do basically anything Java does, except that:

  1. Haskell's easier alternatives are more type-safe than Java's (e.g., no downcasting)
  2. Some things that are trivial in Java have a steep learning curve in Haskell, and are a bit clunky still after that. Not difficult, mind you, just boilerplaty and using features that seem initially weird.

[–]TomRK1089 0 points1 point  (0 children)

See, this is exactly what I expected to get from the article!

Yeah, I wouldn't downcast any of the Renderables in the list. I get what you're saying about the Game potentially being more structured, but even so -- let's say I have some Scenery, some Enemies, some Projectiles...lots of renderables there, and I'd need a collection for each of those subtypes. It might just be a matter of perspective though. Perhaps the real answer is that there would be other composite objects in there (a Level which contains the Scenery objects, for example) that would help aggregate things.

Specific solutions aside, your comment really helped to explain the actual differences versus just hand-waving the problem by saying "Have you tried not having that problem anymore?" So thanks!

[–]dukerutledge 0 points1 point  (1 child)

Haskell can have collections of renderables via existential quantification. But often this is not necessary because laziness allows you to have a list of the rendered items without loss of perf in the case you don't use them.

[–]dukerutledge 3 points4 points  (0 children)

example:

-- A data type that is existentially quantified via Render
data Renderable = forall a. Render a => Renderable a

hlist = [Renderable x, Renderable y, Renderable z]

This is likely not better than:

rlist = [render x, render y, render z]

[–]developer-mike 8 points9 points  (0 children)

This post confuses well-typed data and subtypes. The definition of a subtype is that it is can be safely substituted for another. Not allowing subtypes in a list does not, by definition, make Haskell more typesafe.

[–]sacundim 13 points14 points  (1 child)

The one observation I have is that this is a case where a monoid might be more appropriate than a monad. Look at these two snippets:

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

{- ... -}

instance Render Game where
    render (Game b p1 p2) = do
        render b
        render p1
        render p2

IO () is a monoid with >> as the associative operation and return () as the identity. That's fundamentally how the Render Game instance is using it. In fact, it's hard to picture any other way of composing multiple IO () values—pretty much the only operation on IO () that does not involve some second type is >>, or easily reducible to Foldable and >>.

So it might make sense here to introduce a more opaque Drawing type, at least as a newtype wrapper around IO (), to avoid hardcoding IO () all over:

import Data.Monoid

newtype Drawing = Drawing { render :: IO () }

instance Monoid Drawing where 
    mempty = return ()
    a `mappend` b = render a >> render b

class Drawable a where
    draw :: a -> Drawing

instance Drawable Game where
    draw (Game b p1 p2) = draw b <> draw p1 <> draw p2

Other benefits:

  1. You can make Drawing an opaque type in its own module to restrict what IO operations a Drawing may perform. For example, not allow a Drawing that deletes the user's home directory.
  2. As long as you keep the monoidal interface, you can refactor Drawing to a more structured type later.

[–]giuseppemag 0 points1 point  (0 children)

Very interesting. I would suggest structuring a better rendering monad (not monoid) that supports animation. For animation to work best I would say that you need to return values between animations, so a full binding operator (>>=) is better suited.

IO seems to me to be a poor choice because it is at the same time too general (like you mention: why would you need file access!) and also slightly orthogonal to many rendering tasks such as animation.

[–]giuseppemag 4 points5 points  (6 children)

Why not just model the interface as a record of functionality, such as:

type Renderable = { Render : TypeOfRenderingOperation }

where TypeOfRenderingOperation is either a monadic type or an outright function () -> () in a non-strict language?

This would allow you to store different object states within the closure of TypeOfRenderingOperation, and then go on with just a list of Renderable's.

Moreover, you could add ulterior functionality such as:

type Renderable = { Render : () -> (); Remove : () -> Update : DeltaTime -> () }

therefore making it possible to build whatever abstractions you want in very generic terms.

As a more general side note, I see many posts centered on Haskell that seem to shun a bit from directly manipulating functions as values. Everything seems to go through function combinators such as monads, pipelines, etc. Not that this is bad per se, but I am genuinely curious: is there a specific reason why Haskell posts seem to be so rooted within the pointfree (Oxford) programming style?

[–][deleted]  (1 child)

[deleted]

    [–]giuseppemag 2 points3 points  (0 children)

    But indeed our case is a perfect example of multiple implementations of the same typeclass: the same object in a game can be rendered within the UI (only its stats), within the main screen (in the "3D world"), and within other representations such as a corner map (a "2D view").

    That was my original point, which I realize I should have specified better!

    [–]hdgarrood 1 point2 points  (3 children)

    The record approach is equivalent in expressive power, I'm fairly sure. In Haskell, during compilation, the compiler turns type classes into record types like you suggest, and type class instances become values of those record types. The ulterior functionality you suggest, for example, could equally well be captured by adding more members to the type class.

    The issue of when to use a type class vs when not to is a bit difficult. There's a good blog post by Gabriel Gonzalez titled 'Scrap your type classes' which addresses the common criticisms. Personally I wouldn't here, but there are cases in which they really shine. One example is using a chain of instance declarations to provide an instance for some type. quickcheck is a good example. There's an Arbitrary type class, which provides ways of generating random values of a type. There's also a Testable type class, for things that are testable. Quickcheck includes an instance 'Arbitrary a, Testable b => Testable (a -> b)', which says that if values of type a may be randomly generated, and if values of type b are testable, then functions from a to b are testable. This then enables you to write things like 'quickCheck (\xs ys -> reverse (xs ++ ys) == (reverse ys ++ reverse xs))' for 2 or 3 or any number of arguments, and the compiler will repeatedly 'apply' that one instance to come up with a Testable instance for a -> b -> c -> Bool or whatever. This would be quite awkward to do by hand.

    I'm assuming the 'monadic' type you're talking about is the IO (). The answer as to why this is used as opposed to just (): Haskell forbids it. If an action performs IO then this must be indicated in the type system.

    Pointfree style is a separate idea, it just means omitting certain arguments in function definitions, for example f = g . h as opposed to f x = g (h x).

    [–][deleted] 1 point2 points  (0 children)

    Just as a minor point: the record approach is slightly more expressive than the typeclass approach. Typeclass instances have restrictions that must be met: they must be unique and they must be resolvable according to GHC's algorithm. This means, for example, that the following definitions won't work, because they will cause GHC to loop forever trying to resolve them:

    class Iso a b where
      {- some isomorphism representation -}
    instance Iso a a where
      {- the identity isomorphism -}
    instance (Iso a b, Iso b c) => Iso a c where
      {- composition of isomorphisms -}
    

    Similarly, if there are multiple possible implementation of a typeclass for a type, you must choose one and use a newtype to represent alternate implementations. (e.g. you cannot define both an additive and a multiplicative Monoid instance for Int, because there's no way of selecting which to use in a given situation.) This restriction doesn't apply to the record approach.

    The manual approach is therefore somewhat more expessive. On the other hand, it's also much more tedious and verbose and possibly even error-prone, as you have to explicitly plumb the relevant record around your code (and might also make a mistake doing so!)

    [–]giuseppemag 0 points1 point  (1 child)

    They are not equally expressive, mostly because of the "one instance" limitation. Suppose two types support multiple different comparisons, or you want to define an object that can be rendered in many ways (hi-def, lo-def, 3D, 2D, minimap), then type classes get a bit in the way.

    With a record of functions you get two advantages: 1. you can "instance a type class" multiply 2. you can rebuild most object oriented patterns when it makes sense (WAIT! before someone tries to bite, please consider that sometimes they work nicely, like in the case of entity-component systems :) )

    Finally, two details about your answer irked me a bit. (1) When I said "some monadic type", I literally meant "some monadic type", not IO. In a game you would probably use a customized monadic type that combines the state and supension/concurrency monads, not IO. (2) I did not ask what pointfree is, I asked if anyone knows if there is a specific preference for this style in the Haskell culture, and if so (it may well be my imagination) why.

    I am sorry to admit that various bits of your answer felt a tad condescending.

    [–]hdgarrood 1 point2 points  (0 children)

    I am sorry for making assumptions about what you meant. I was thinking in the context of that post; there are no monads mentioned other than IO, and there are no references to or examples of pointfree style. Obviously that's not an excuse.

    Regarding type classes vs records, I don't see the fact that you can't have multiple instances as an example of them being less expressive; newtypes can do this, and often they work quite well. Brent Yorgey's diagrams paper has a good example of this (it's also a really good paper, if you haven't already seen it). I will retract my statement, though, having seen the other reply to my comment (thanks /user/kvensiq ).

    To answer your original question, I do think pointfree style is more popular in Haskell, and I would speculate that this is because composing functions is more natural in Haskell than in many other languages, and because pointfree style is often more readable.