Live data from Hacker News

Thinking in Types

robots.thoughtbot.com

61–70 of 124 posts

Re: Thinking in Types

#61
post #22

On a related note: The article (quite reasonably) avoids discussion of the graphics library, but I want to know more about that side of things. I wish graphics got more attention in the Haskell ecosystem in general. The options right now are pretty dismal. There is quite literally not a single Haskell graphics or GUI package that I've been able to install on OS X. I'd love to use Haskell to build games or desktop GUI…

As a fellow green Haskell developer, I get the sense that the best option is going to be GHCJS[0]. The browser is far and away the best environment for graphics programming because of its ubiquity and easy to use APIs. Sure, someone could step up to the plate and write a nice, idiomatic wrapper for SDL and OpenGL and whatever, but a good Haskell library that targets the browser would spread like wildfire in compariso…

I hear that, and I hope that project works out. But I'm still interested in developing native apps. There's a reason so many professional applications (games, intensive apps like Photoshop and Blender, etc) are still native.

You may have seen The Birth and Death of JavaScript:

https://www.destroyallsoftware.com/talks/the-birth-and-death...

I don't know whether that prediction will come true. But obviously it hasn't thus far. For now, if I need native performance, I need native code. Sadly, that means C, C++, or Java until such time as Haskell libraries compile reliably.

Re: Thinking in Types

#62
post #11
post #7

I'm getting tired of reading: > This is why we hear that Haskell reprise if it compiles, it works. If this were true then functions would not need bodies, you would just define their signatures and move on with life. The truth is that even with its superb type system, Haskell still needs to run your code. Your code might be statically correct but its runtime is up to you. I would prefer it if people rephrased this cl…

Well, as you go to the next steps you can use proof search techniques to do exactly that: write your types and your programs write themselves as the "only possible implementation". This is already possible sometimes in Haskell so long as we restrict ourselves from pathological values like exceptions and non-termination. In fact, the first place this phrase shows up is Russel O'Connor talking about highly polymorphic…

> write your types and your programs write themselves as the "only possible implementation".

But there are only very few type signatures for which this is true, even if you stick to totality.

Re: Thinking in Types

#63

I'm not sure that this works in more general cases, though. If I have a more general game with more objects of more types in it, adding each type to the render function is going to get old. In object oriented programming, I'd just call render() on each entry in the list of game objects. But this approach is going to lead me to: - render each entry in the list of Foo objects - render each entry in the list of Bar obje…

That problem gets especially harry once you consider every subsystem in the game is going to behave similarly, AI, physics, Sound, etc.

It seems like the right answer is the same as it is in OO languages favor composition. Grab the Renderable out of the Foos, Bars, and Bazes then pass that list to the render function. It follows the articles suggestion about think about your types more. Why does anyone think the render function needs the entire player state instead of just the part it needs to render something on the screen?

Re: Thinking in Types

#64
post #62
post #11

Earlier quoted context omitted.

Well, as you go to the next steps you can use proof search techniques to do exactly that: write your types and your programs write themselves as the "only possible implementation". This is already possible sometimes in Haskell so long as we restrict ourselves from pathological values like exceptions and non-termination. In fact, the first place this phrase shows up is Russel O'Connor talking about highly polymorphic…

> write your types and your programs write themselves as the "only possible implementation". But there are only very few type signatures for which this is true, even if you stick to totality.

In practice types often winnow the possible implementations to be a relatively small set. This effect is improved if you also include notions of law-abiding implementations as Haskellers often do. At the end of the day, it's true that implementations (don't yet) write themselves, but, more realistically, that the constraints of type and theory drive you naturally to the correct solutions even if you never once figure out the "operational" aspect.

That occurs quite often.

Re: Thinking in Types

#65
post #54

Earlier quoted context omitted.

Get in touch with the developers and work with them to get it building. Often times they don't have access to your platform, so just being that helps. Anything you can do on top of that is gravy.

While what you suggest is reasonable and the right thing to do, it just highlights the original point. The state of UI bindings is pretty bad.

I wasn't making any point. Fwiw, I have been able to meet my own (quite limited) gui needs in Haskell with gtk...

Edit: On reflection, I find your assertion a little strange here. Anecdotal failure to build on one particular setup doesn't seem like a particular highlighting of bad bindings.

Re: Thinking in Types

#66
post #60
post #10

Heterogenous lists are always coming up when people try to translate OO ideas into Haskell. It's great that this example used the barrier of heterogeneity as a reason to think harder about their design instead of barreling forward. In particular, heterogeneity causes a form of information loss via type erasure (existential typing). The problem is that this is pretty heavy machinery and is not always well-suited to su…

Could you explain what you mean here by initial/final encoding, and why you say OO favors final encodings but initial encodings generate type information? I'm familiar with the notions of initial algebra, and slightly less so with final coalgebras, but I don't quite see what you're getting at.

I'm running pretty fast and loose here, but you can see objects as being ADTs defined by their eliminators [0] while constructors produce new forms and their relevant type information. Eliminators can do that as well (in negative positions), but I want to drive home that a lot of what feels weird about Haskell comes from the effects of working with constructors/pattern matching. A similar comparison might be made between case classes and "regular" classes in Scala, but I don't know Scala as well.

[0] http://www.cs.ox.ac.uk/jeremy.gibbons/publications/adt.pdf

Re: Thinking in Types

#67
post #46

Earlier quoted context omitted.

I'm not sure which issues you're running into, but for what it's worth, I've had a lot better success using the newer cabal sandboxes[1] than just running `cabal install foo` all the time. (If you're from the Ruby world, it acts a lot more like bundler with `--standalone`). Previously, I had more issues with conflicting version constraints which I think is more of an issue with library authors and not necessarily cab…

I'd love to, but Haskell Platform ships with an old version of Cabal that doesn't have sandboxes. The new version of Cabal itself doesn't compile on the Mac. (In keeping with the theme of "nothing compiles.") Edit: At least, I can't get the new version of Cabal/Cabal Install working on my Mac.

There was a relatively serious bug caused by Mavericks' clang being a non-standard CPP, but I think that's fixed in newer versions of GHC. It's certainly possible to get a Cabal compiled on Mac, though.

Re: Thinking in Types

#68
post #47

Earlier quoted context omitted.

Manual is OK if there's at least a clear path to getting something to install. I don't see that path, though. My biggest issue is that I don't have the expertise to debug an obscure Haskell compilation error. I won't develop that expertise unless I can use Haskell over the long term on real projects. I can't do that unless libraries are available. So it's a chicken and egg problem. I think the same is true for many p…

Certainly the case. Much eased (though not eliminated) by the recent addition of cabal sandboxes. There's still no good way to see all the native libraries required by a cabal install, and occasionally there are actual conflicts between packages... I've been meaning to populate http://en.wikibooks.org/wiki/Haskell/Resolving_Cabal_Hell but have been kinda hoping (almost certainly in vain) that someone with deeper know…

The sandbox sounds great. I haven't been able to get Cabal 1.8 to install on a Mac, but I'd love to try it.

Re: Thinking in Types

#69
post #68

Earlier quoted context omitted.

Certainly the case. Much eased (though not eliminated) by the recent addition of cabal sandboxes. There's still no good way to see all the native libraries required by a cabal install, and occasionally there are actual conflicts between packages... I've been meaning to populate http://en.wikibooks.org/wiki/Haskell/Resolving_Cabal_Hell but have been kinda hoping (almost certainly in vain) that someone with deeper know…

The sandbox sounds great. I haven't been able to get Cabal 1.8 to install on a Mac, but I'd love to try it.

Huh. Pop into #haskell on irc.freenode.net, someone there can probably help you. I've got basically no experience with Mac myself.

Re: Thinking in Types

#70

"A type class defines a set of functions which must be implemented for a type to be considered in that type class. Other functions can then be written which operate not on one specific type, but on any type which is in its given class constraint." Call me crazy but this just sounds like a Java interface to me. Edit: On further thought, I guess the difference is that in Java, the interface itself is a type. So all ins…

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
Post reply on HN