Hackfoofery

Alson Kemp

Reflections on leaving Haskell

Update: Dammit but that’s a great set of comments…  Haskellers rock…  I wrote this post fairly quickly and didn’t expect it to get quite the attention it got, so please read the comments.  There’s a lot of good stuff in them.

Update #2: a comment over on Hacker News points out that the reasons I give are shallow…  I agree.  It works out that simple, shallow issues can become big issues over time and as a system grows larger and larger.  That was exactly the reason I started with Haskell: to wear the hair shirt.  As my systems got larger, the shirt just got too itchy.

I picked up Haskell during MIT’s IAP ’04 (during B-school…) figuring that I should learning a new language during the winter.  I’m an experienced coder, didn’t need yet another object oriented programming language and couldn’t get OCaml to compile my “hello world” program.  Haskell was the first challenging computer language I’d seen in a while and I was quickly addicted (though not quickly productive).

As might be obvious, I haven’t been able to spend much time on Turbinado over the past year.  I have been able to spend a bit of time thinking about Haskell and I think that I’ve left the Haskell camp…  Here’s why.

Things I Could Do Without

No subclassing of datatypes

In Turbinado I passed an Environment back through the function hierarchy and I wound up building the Environment out of a bunch of Maybe types that I would incrementally fill in.

data Environment = Environment {
           getCodeStore :: Maybe CodeStore,
           getRequest  :: Maybe (HTTP.Request String),
           getResponse :: Maybe (HTTP.Response String) }
Yuck…  What I really wanted to do was build a number of EnvironmentXs so that I could safely use EnvironmentX or EnvironmentY in a particular function.  But I would have to define two separate data types in order to avoid the Maybes:
data Environment1 = Environment {
           getCodeStore :: CodeStore,
           getRequest :: HTTP.Request String }
data Environment2 = Environment {
           getCodeStore ::  CodeStore,
           getRequest :: HTTP.Request String,
           getRequest :: HTTP.Request String }
What I really wanted to do is say that Environment2 is Environment1 with a bit more stuff, e.g.:
data Environment1 = Environment {
           getCodeStore :: CodeStore,
           getRequest :: HTTP.Request String }
data Environment2 = Environment1 with {
           getResponse :: HTTP.Response String }
(This has type system implications that I’m sure are more complicated than I’d understand…)

(I’m sure that there’s a monad that could solve this problem, but see below…)

Dependence on Monads is not a Good Thing

Monads are a brilliant idea… on paper.  I’ll be the first to say that I think that my level of intelligence hampers my ability to really work with monads, but I’m a reasonably intelligent guy so I suspect that a lot of coders share this complaint.  Lots of other functional-ish languages have added monad libraries, but I think that they’ve done so more because monads are useful for DSLs than because monads are a foundational building block for the language.

In the real world, monads seem quite constraining.  Composing them is rough and the composed monads are difficult to understand.  I’m ashamed to say that one of the first things I did in Turbinado was to tear down the complicated monads that I’d copied from HSP.

The IO Monad makes me unhappy

It’s something that’s been sitting in the back of my mind and I finally let it bubble up. I feel kinda dirty when I use the IO Monad, as though I’m cheating on the language. I understand that functional programs have to do IO and that the IO Monad is an elegant solution… but I’m still uncomfortable. The majority of the functions in Turbinado are in the IO Monad… and that bothers me.  Half of my code looks like the following and that makes me unhappy because it isn’t functional-ish:

simpleFunction s = do i <- otherFunction s
                      putStrLn i
Why don’t I just use a language that’s friendly to IO? My question exactly.

Things I Loved

Functional Programming

I’m sold. Seriously. That’s fantastic stuff right there. And without Haskell I would be trapped in Scala-land, writing syntax-sugared-Java, confused about why functional programming was so great…

Purity

Haskell is fantastic because it works as advertised.  I considered learning other functional, but was struck by their functional impurity.  Looking back on Haskell, I love the design of the language.  It’s an architectural masterpiece and you gotta love that (though obviously I’ve decided not to live in it…).

Wearing the Hair Shirt

My hat is off to the Haskell community.  SPJ and Wadler did the programming world a service by creating Haskell.  Wearing the Hair is hard and it’s very worthwhile.  It’s why I decided to learn Haskell and it made me a better, smarter developer.

The Community

Who doesn’t like working with people who’re smarter than they are?

So?

There’s probably more I love about Haskell and more that I could do without, but I think that I’m moving on to Clojure.   Rich Hickey has a strong love for functional programming coupled with a good sense of the compromises that can be made for the language to be very productive.  The JVM is great: it runs my Rails apps, is speedy, does great garbage collection, etc.  I wish it had a dialect of ML or Haskell on it, but I’m going to try to teach myself to look past the parentheses and get back into LISP.  It’s been 22 years since I typed a line of LISP, but this dog can learn new tricks… especially with the benefit of the last 5 mind-bending years with Haskell.

Truth be told I’m a little sad.  I’ve kinda felt part of a secret society with Haskell and it’s a bummer to be leaving the secret Haskell society…

Written by alson

March 13th, 2010 at 5:02 am

Posted in Haskell

with 44 comments

44 Responses to 'Reflections on leaving Haskell'

Subscribe to comments with RSS or TrackBack to 'Reflections on leaving Haskell'.

  1. Swapping the secret society for the cult of personality? :-) Thanks for the Hackage package though, Alson. And contributors are always welcome in Haskell-land.

    Don Stewart

    13 Mar 10 at 6:32 am

  2. I share your complaints, but have chosen to speak a dialect of Haskell where they disappear. Dependence on monads is definitely not a good thing! And monads are overused in Haskell — people composing a giant stack of monads in which to write their whole program. This is not The Way, it is a backlash to imperative programming. A monad in my program usually has the scope of a function and its where clauses, and then gets eliminated before it gives its result as a pure value. Overdependence on monads is a failure to program in a truly functional way.

    The IO monad also makes me unhappy. I disagree that is an elegant solution. It is a solution, and that is what has allowed Haskell to be a useful real world language. But there are better solutions, and we need more research to find the best one. But see Conal Elliott’s Tangible Values as one of the alternatives.

    The lack of record subtyping is unfortunate.

    I hope you have a good time with clojure. My brain has been trained out of being able to write good dynamically typed code anymore… but, to each his own.

    Luke Palmer

    13 Mar 10 at 8:13 am

  3. That’s strange – I followed the exact same path. I was delighted to discover Clojure would let me use macros and s-expressions on the JVM. But when I started writing serious apps in it, I found that a lot of confusion is possible when everything is lists and maps.

    This got me thinking about looking for statically-typed languages and I turned up Haskell, which I loved almost right away. Now I can deal with enforced datatypes instead of lots of amorphous maps. And, like you, I was happy to see a functional language take purity so seriously.

    I feel similarly about subclassing datatypes and hope someone will point out an answer.

    One way to make monads more “functional-ish” is to use Control.Applicative:

    import Control.Applicative simpleFunction s = putStrLn otherFunction s simpleFunctionCurried = putStrLn otherFunction

    I find these little shortcuts ( , <*> ) make my code much more readable, especially in simple cases where I’m just applying a single IO function to a pure value.

    As someone who went the opposite direction from you, I have to say that one thing that keeps me in Haskell is the excellent static typing. It is a great thing to know that I can never leave off a function argument or add an extra one and not discover it until runtime. It’s really surprising how often my program just works as long as it type-checks – it’s contrary to all my intuition about programming, but there it is.

    I still give Haskell a lot of credit for its attempt to keep IO separate from pure code. No other language takes this attempt so far. I think the monad abstraction is kind of a stopping-point on the way to pure functional languages that can do IO beautifully. They are still a beautiful solution to the original problem, but no doubt monadic code does not “feel” as nice as a pure Haskell function and, as you point out, composing them is not straightforward.

    I do my best to minimize the impact of IO-monadic code. I try to keep IO code in my Main file and keep everything else pure. For example, recently I was writing a simple Haskell program that had to apply foreign-exchange rates to a list of records. My initial solution was an IO-monadic function that took the records as a parameter then retrieved the rates and returned the result. It wasn’t composable and it was ugly. So I moved the rate-retrieval code into a separate monadic function that returned IO [DenominatedAmount] and from my top-level monadic code in Main, I called that function and then passed the [DenominatedAmount] array into the pure code.

    The new result was more composable and reusable and only Haskell would have made me think to write the code that way. I think the feeling of awkwardness that arises from usage of the IO monad is a good reminder to look for other solutions, something that any other language would let you completely ignore.

    Best of luck whatever you choose.

    bill a

    13 Mar 10 at 9:40 am

  4. I totally agree about the lack of subtyping being annoying (but I don’t think you picked the right workaround).

    Likewise, I agree that monads can be quite annoying at times. But personally I’m willing to pay that price to track effects.

    I don’t think the IO monad is perfect, but nor is it a shame to use it. I also think Haskell is very good for imperative programming, e.g., the first class IO values.

    augustss

    13 Mar 10 at 11:11 am

  5. There is a Haskell-like language called CAL for the JVM. AFAIK, it’s not as pure as Haskell and doesn’t require Monads for everything.

    http://openquark.org/

    Sean Leather

    13 Mar 10 at 11:28 am

  6. I wonder if you’d mind elaborating a bit, both on “couldn’t get OCaml to compile my ‘hello world’ program” and “without Haskell I would be trapped in Scala-land, writing syntax-sugared-Java, confused about why functional programming was so great.” My preferred replacement for C++ is OCaml, and my preferred replacement for Java is Scala, which I tend to write quite functionally, so I’m happy to help with either of them if I’m able. :-)

    Paul Snively

    13 Mar 10 at 12:24 pm

  7. Hi Alson,

    sad to hear you are going… :-(

    I wanted to use Turbinado, but was waiting for some more docs.

    However, Michael is enthusiastically pushing Yesod (http://github.com/snoyberg/yesod), so if you at any time feel some itching and your heart start beating faster, pls. don’t be shy to take a trio into well-know Haskell world.

    Otoh, I personally wish you more happy moments with Clojure, although desiring to see you back at some time.

    Sincerely, Gour

    Gour

    13 Mar 10 at 12:51 pm

  8. Paul,

    Ocaml: it was a number of years ago and I must have been getting tripped up by spaces versus tabs for whitespacing, but the OCaml kept on spitting out incomprehensible errors. I think that I got all the way back to a fairly straightforward “Hello World” type program and still couldn’t get it to compile. I’m sure I was doing something really obvious and stupid, but the compiler could have thrown an error that helped me… This was back in 2004, so I’m sure the compiler much more advanced now. That said, I’m going to the JVM and would love to see OCaml-Java (http://ocamljava.x9c.fr/) mature.

    Scala: Case Classes seemed like design decision that said “we want to keep Java largely intact but that pattern matching stuff is pretty cool so we’ll just kinda bolt it onto the side and call it Case Classes”. Chapter 15 of Programming Scala talks about how Scala figures out which variable, value, etc is being referenced in a case statement (“Variable Patterns”) and the whole section seemed full of unexpected behaviors. That combined with all of the Java-ish syntax turned me off. I really did want to like it because, hey!, functional programming + JVM is like peanut butter + chocolate, but I just couldn’t get into it. And, for hobby coding, I gotta be into it.

    alson

    13 Mar 10 at 1:20 pm

  9. I’d like to hear back from you after getting deep into Clojure. Does the grass just look greener or is it really? I’m just getting into F# and while it does offer some advantages over Haskell there are other things (type classes!!) that are painfully missing. I think I’m figuring out that there are some projects I’d rather do in Haskell and others in F#, but I still need to dig deeper.

    Keith Sheppard

    13 Mar 10 at 1:29 pm

  10. Gour,

    I still think that Turbinado is a great bit of code (down!, ego, down!), but it is very much a port of Rails to Haskell and doesn’t really feel like functional programming. I’m not entirely happy with some of the design decisions, but I’m very happy with the simplicity of developing web apps in Turbinado.

    The various Haskell web libraries have always struck me as a bit complicated, more like PHP web libraries than web frameworks. Although I wish Michael good fortune with Yesod, it looks as complicated as most of the Haskell web libraries. What hooked me about Rails was that it was a great web framework because it allowed me to write only the code necessary to get my web sites going. Turbinado is aimed in that same direction. Yesod and HApps both seem to have lots of not-web-stuff code lying around in their examples.

    See : http://www.snoyman.com/blog/entry/tweedle-beetle-battle/

    alson

    13 Mar 10 at 1:31 pm

  11. Sean,

    I’d seen CAL a while ago and was interested. Thank you for the reminder/pointer. The CAL community seems pretty dead…

    alson

    13 Mar 10 at 1:32 pm

  12. augustas,

    I am certain that I made the wrong design decision around the Environment type, but I could never figure out how to do it properly… I didn’t want to define 4 different Environment types. I didn’t want to define and wrap a big monad around everything since I wound up incrementally tearing down the HSP monads to get to Turbinado. I needed a type-safe data structure that I could build up incrementally over the course of the program’s execution and I just couldn’t seem to find it. I’m using Generics to do a bit of the work, but that cause its own problems.

    I’d love to understand how to do it better since it really bugged me.

    … This gets near another big issue I have with web development in general (maintaining type-safety from Controller -> View), but I’ll save that for another post.

    alson

    13 Mar 10 at 1:42 pm

  13. bill a,

    Great comment. I agree completely with everything you’ve said and I’m concerned about the soup of lists and maps in LISP/Clojure. Not positive I’m heading that direction yet, but I am standardizing on the JVM so my options are limited…

    I love Haskell as a tool for particular jobs, but I found building larger systems in it to be impractical. Check out http://github.com/alsonkemp/turbinado for an example.

    And I am incredibly impressed by the type system of Haskell. I could actually feel my brain changing shape as I got deeper into the type system. It’s about 87% perfect. If it could handle maintaining type safety from Controller->View and if IO could … er … feel less dirty then it’d be perfect.

    alson

    13 Mar 10 at 1:53 pm

  14. dons,

    An aside: in looking back on my involvement in Haskell, I see your fingerprints all over the place: libraries, blogs, Planet, community, IRC. Impressive stuff. And thank you for your work on plug-ins for Haskell. Turbinado would not have existed without your (and others’) work.

    alson

    13 Mar 10 at 2:08 pm

  15. On your Environment data type, I’d try to propose an alternative, but I suspect it would be an entirely new design for Turbinado, which I simply do not feel like taking the time to research for the sake of a blog comment. I do have a suspicion that you are doing something severely wrong to have a need such as the one you appear to have.

    On reliance on monads, I agree. I think monads are excellent abstractions for DSLs, but not so much for where it is required by Haskell, namely IO.

    Which brings us to IO. I’ve been following and attempting to contribute to the FRP movement for a little while now, and I think it’s the future of functional programming… The problem is just that we haven’t discovered such great abstractions for it. I see the IO monad as a temporary crutch. It’s unfortunate that so many Haskellers see the IO monad as a “solution” to the I/O problem.

    And while it’s not going to solve your IO woes, your example IO function could be rewritten to at least appear more functional (although it’s still actually imperative): simpleFunction = putStrLn <=< otherFunction

    Jake McArthur

    13 Mar 10 at 2:09 pm

  16. Hi Alson!

    That’s a good point re: OCaml error messages. I frankly also blame the fact that the developers aren’t native English speakers. With that said, if you wanted to try again, I think you’d find the OCaml community quite helpful and supportive. With http://ocsigen.org the web programming picture for OCaml is also getting quite nice, IMHO.

    I see your point about case classes and extractors in Scala. I guess my reaction is that that’s one point in the object/functional space that certainly can trip people up, but I don’t feel it’s such a big element of functional programming in Scala. If you want to see just how far down the rabbit hole goes, I suggest checking out http://code.google.com/p/scalaz which certainly melts my brain in that really good FP way. :-)

    With all of that said, I think Rich Hickey’s work is genius. Like a previous poster, though, I just can’t go back to dynamic typing. I was a committed Lisper for over two decades, so it’s not that I don’t understand or appreciate the power. It’s just that, having experienced the combination of interactive, incremental development with rich type systems keeping me honest, there’s no going back. Still, De gustibus non est disputandum, and I hope you have good experiences with Clojure should you continue on that path. :-)

    Paul Snively

    13 Mar 10 at 2:10 pm

  17. Luke,

    Nice point. With Turbinado, I was very focused on porting Rails to Haskell (and I think it worked out fairly well), but I probably blinded myself to the possibilities of doing it more functionally.

    Giant stack of monads: Agreed… but then I wound up with IO everywhere. And I wanted to allow DB access in the Views, so everything had to be IO-able…

    Argh. One of the things that was so frustrating with Haskell was that system programming always seemed just slightly out of grasp. As if I was holding my breath waiting for some blinding insight on how to do it idiomatically, lovely-ly, simply, etc-ly and then I could relax… Haskell == Rubik’s Cube of programming.

    alson

    13 Mar 10 at 2:17 pm

  18. Looking at your actual Environment type from the code you linked to, I wonder why you need partial initialization. How many different unique subsets of the information do you need? Could you instead have a couple of different Environment constructors? For instance, one for if you have the response, and another for if you don’t? Alternatively, could you wrap up sets of related information into separate StateT? I can think of a few more exotic solutions which might make the code even cleaner, though I don’t know enough of Turbinado’s architecture to recommend the perfect solution.

    The more important issue: Maybe makes sense sometimes, but in this case it seems like you can’t do anything useful except fail if one of the values holds Nothing when you expect a Just value. You ideally want the type system to tell you about that kind of problem, which means having the types tell you what a function needs from its environment. Your approach can’t do that.

    Similarly, your CodeStatus and CodeType data types have some parallel redundancy, which suggests some duplication elsewhere that could probably go away. And I do wonder whether your approach to dynamic code loading could become a lot simpler.

    Please don’t take this as a slight against Turbinado, because it looks like an impressive framework to me, but it seems like your first major Haskell project represented an attempt to recreate the feel of Ruby on Rails in Haskell. That doesn’t seem fundamentally wrong, but it does seem like a very hard choice for a first major Haskell project.

    It takes a while to stop thinking in another language while writing Haskell, and start thinking in Haskell instead. By consciously attempting to recreate Ruby in Haskell, you’ve made that a lot harder for yourself.

    You said many of your functions ended up in IO; this would likely explain why. The Environment type with all its Maybe values seems very much like an attempt to recreate a feature from a dynamic language like Ruby. So do some of the dynamic code loading bits.

    All the little functions in IO can arise because of a few design decisions that make it necessary to use IO; a few different design decisions and you might find large numbers of those functions can become pure. Also, Haskell users like having as many errors as possible detected at compile time, rather than at runtime; a runtime error always comes as a shock. Haskell users don’t mind recompiling code, because the compiler and its various checks represent the most helpful bug-finding tool they use.

    So, I’ll end with a suggestion. Please don’t give up on Haskell quite yet; you’ve clearly gotten past some of the big hurdles already, and you’ve found some of the key things to like about the language. Instead, put Turbinado on hold, just as you would likely do if you leave Haskell entirely. Consider finding or creating another Haskell project to hack on, one that does not attempt to create a framework for anything or a language for anything. Try to ignore how Ruby or any other language might solve a problem, and think about how you could write more natural Haskell instead. Work with people on #haskell to find Haskelly solutions. Better yet, find someone locally that you can pair-program with. I learned Haskell with a friend by writing a major project (at this point the biggest bit of Haskell either of us has ever written, at a couple thousand lines); that made it more fun and easier to learn, and at this point I can more-or-less think in Haskell.

    Josh Triplett

    13 Mar 10 at 2:27 pm

  19. I’m still relatively new to the haskell world, but couldn’t your Environment trouble have been fixed by some type classes?

    If you need a function to operate on all types of environments (ie, for the getCodeStore), that should be a function that the typeclass guarantees. If it is some ‘subtype’ then a function using it can only operate on that subtype, in which case it should require the concrete implementation (or another typeclass).

    This might lead to slightly more boilerplate (because you have to write out all the different types, you can’t just say ‘extends’…), but would it make using it any more difficult than if there were subtyping?

    Daniel Patterson

    13 Mar 10 at 2:43 pm

  20. Jake,

    Putting aside Turbinado’s Environment type, how do you grow a heterogeneous collection in Haskell? In dynamic languages, you can just jam stuff in and hope that you never get it wrong…

    Better abstractions: agreed. I’m not smart enough to come up with it, but know that it’s nearby. I can’t wait to see what it is!

    alson

    13 Mar 10 at 2:45 pm

  21. “I’m an experienced coder, … and couldn’t get OCaml to compile my “hello world” program.”

    Ayup.

    John Haugeland

    13 Mar 10 at 2:46 pm

  22. Hi Alson,

    Just curious about your derived data type problem. I’ve had a similar issue in the past. What do you think about this way of doing it?

    I think it’s generic, extensible, and type safe. But I may be misinterpreting your problem or my solution.

    – Library types

    data CodeStore instance Show CodeStore data Request data Response instance Show Response

    – Derived classes and properties

    class Environment1 a where env_codeStore :: a -> CodeStore env_request :: a -> Request

    class Environment1 a => Environment2 a where env_response :: a -> Response

    – An implementation of the class

    data Env

    instance Environment1 Env where env_codeStore = undefined env_request = undefined

    instance Environment2 Env where env_response = undefined

    – Usage of the instance

    – For this function we just need Environment1 foo :: Environment1 a => a -> IO () foo a = putStrLn $ “CodeStore: ” ++ show (env_codeStore a)

    – For this function we need Environment2 bar :: Environment2 a => a -> IO () bar a = putStrLn $ “Response: ” ++ show (env_response a)

    Chris Done

    13 Mar 10 at 2:50 pm

  23. Daniel,

    Definitely, but then I’d need to create a number of different Environment types each of which is just the prior Enviroment type + one or two little fields and that didn’t feel good, either. I settled on the gnarly Environment + Maybes as a tradeoff. The whole point of the post is that I don’t like my solution but I couldn’t figure out how not to make a trade-off…

    alson

    13 Mar 10 at 2:52 pm

  24. @alson: Environment doesn’t look like a heterogeneous collection, though. It looks like a Ruby or Python object, meaning a dictionary mapping field names to values dynamically.

    Josh Triplett

    13 Mar 10 at 2:54 pm

  25. As one random suggestion, though not necessarily the right one, you could potentially use something like session types if you generally fill in the environment in the same order.

    A few proposals exist for extensible records in Haskell, which would effectively make it possible for you to implicitly create types like Environment, while still having some static type checking, using a duck-typing mechanism: one function could require a record that had fields “a” and “b” of such-and-such types, and another function could require a record with fields “b” and “c”.

    I’ve also seen some implementations out there that use type-level programming to implement extensible data types, where you can add new pieces to a type using some special operators, and the types end up looking like the data definition (so, instead of a function taking Maybe a, you have a function whose type signature tells you that it takes a type with a nullary constructor called “Nothing” or a unary constructor called “Just” with parameter of type a).

    Josh Triplett

    13 Mar 10 at 3:06 pm

  26. Are you going to upload turbinado to hackage, as a farewell gift?

    Don Stewart

    13 Mar 10 at 3:15 pm

  27. Alson, thanks for Turbinado. I hope to hear about your experiences with Clojure, as I’d like to learn Lisp sometime.

    I agree that the type subclassing thing (which you demonstrate with Environment) is a pain in the ass. What I usually end up doing is… just dealing with it and using a combination of the Maybe monad, and the maybe function, when dealing with a long list of Maybe values. It makes processing the values much cleaner. xP

    Arthur CHan

    13 Mar 10 at 3:19 pm

  28. John,

    Ha! That’s a great quote when written that way…

    alson

    13 Mar 10 at 3:39 pm

  29. I’m sorry to see that Turbinado is being abandoned. I was looking into Turbinado a while back, and I preferred the design to that of Happstack. Large stacks of monad transformers annoy me, but then maybe I haven’t seen the light yet.

    It seems we have similar sensibilities, and I’ve certainly experienced a lot of frustration developing larger projects in Haskell. But I’m not ready to try something new yet (although I do like what I have seen of clojure).

    Sam Griffin

    13 Mar 10 at 3:40 pm

  30. Hi Alson. I’m saddened to hear that you hit the wall with haskell. But i can perfectly understand your struggle and your decision. I’m sad simply because i was hoping that your web wframework will turn out differently than a bunch of other either abandoned or over the top, ridiculously complex haskell web frameworks (happstack)

    I want to welcome you to the clojure side. I use clojure web framework compojure. And there’s a Rails inspired framework http://github.com/macourtney/Conjure

    You’ll find striving, active, helpful and very friendly community.

    Vagif Verdi

    13 Mar 10 at 3:47 pm

  31. Hmmm. You like functional languages a lot.

    You don’t like the monad straight jacket.

    Let’s see, there is this functional language that let’s you do pretty much whatever you can express.

    LISP or variants such as Scheme. It’s super simple and super powerful.

    I tend to use it and other languages.

    LISP for Comp Sci theoretic and some straight practical work.

    Python for server side scripting.

    Lua or Scheme for embedded language work (depends on the customer).

    D for high-performance systems and OO work.

    Assembler or FORTH if it can’t be done any other way.

    eris

    13 Mar 10 at 4:35 pm

  32. for an ML on the JVM, check out Yeti : http://mth.github.com/yeti/

    zorg

    13 Mar 10 at 5:24 pm

  33. zorg,

    Thanks for the pointer. Looks as though Yeti is very early stage, so I’ll keep an eye on it as it progresses.

    alson

    13 Mar 10 at 10:28 pm

  34. eris,

    My thinking exactly. That said, my needs are more limited so I’m moving to JRuby from Ruby and (probably) from Haskell to Clojure.

    alson

    13 Mar 10 at 10:29 pm

  35. “Putting aside Turbinado’s Environment type, how do you grow a heterogeneous collection in Haskell?”

    I’d say the problem is not that it’s difficult to do but that you wish to do it. This is just not how I believe Haskell should be used, generally. It’s much nicer to work with small, composable parts which are simply not aware of each other. A design of this kind tend to avoid monstrosities like heterogeneous collections.

    Jake McArthur

    14 Mar 10 at 1:27 am

  36. @Alson:

    I still think that Turbinado is a great bit of code (down!, ego, down!), but it is very much a port of Rails to Haskell and doesn’t really feel like functional programming. I’m not entirely happy with some of the design decisions, but I’m very happy with the simplicity of developing web apps in Turbinado.

    Yeah, it could be, but, unfortunately, I never really tried it, hoping to see some more docs.

    I’m also sad that nobody jumped in to help fixing design which finally brought you to the end. :-(

    The various Haskell web libraries have always struck me as a bit complicated, more like PHP web libraries than web frameworks.

    I confess that I still do not understand Yesod, but, at least, it has prospects to get proper back-end, to run on non-VPS (FCGI,SCGI etc.). Happs is also too complicated and non-practical for my use-case.

    It would be great to get one common & decent web-framework for Haskell. (I just posted to web-devel list about it. You’re are free to join. ;) )

    Sincerely, Gour

    Gour

    14 Mar 10 at 3:17 am

  37. Speaking as someone who’s written a web framework (massive deficiencies in Rails drove me to write my own in Ruby), and admittedly only having had a brief look at Turbinado, I feel that you’ve made a few decisions that made things more difficult than they needed to be, especially given that you’re doing this in Haskell.

    First, trying to model your system Rails is a bad idea. Not only is Rails more complex than it needs to be, but the overall design is really unsuited for a system with any sort of type discipline. With Rails, pretty much everything is accessible and can be changed anywhere.

    Second, yes, you really did want to split Environment into a few entirely separate types. Right now you’ve got a system where, for example, a function that loads static configuration information (such as your CodeStore) is typed as taking a Maybe Request, and something that operates on a Request is not only typed as taking a Mabybe Request but is also typed as being able to modify your CodeStore. Even starting with the Java Servlet API would have cleaned up a lot of this; you’ll note that Java API is fairly clear about what can and can’t be modified as a ServletRequest and ServletResponse go through the processing chain, though both those classes have parts that can be modified.

    As a (stripped down) example of what you did and should have done, rather than having something like an “Environment (Maybe CodeStore) (Maybe MimeTypes) (Maybe (HTTP.Request String))” you really wanted an “Environment CodeStore MimeTypes”, “Request Environment (HTTP.Request String)”, and “typedef Servelet = Request -> Response -> Response” to indicate that a request cannot exist without a CodeStore etc., a response can be built up through stages, and neither the envirionment nor the original request can be modified by any Servlet (or whatever you care to call your service functions).

    Request Environment (HTTP.Request String)”. That would show that a Request can’t be handled without a CodeStore and a MimeTypes, nor can it modify the CodeStore and MimeTypes

    Third, it appears that you were designing a system, like Rails, where a a request handler (or Servlet, or whatever) can do pretty much anything on the system that any arbitrary program running as the web server user can do. If you really want to go that way, as you saw, the request handling code has to run in the IO monad. For programs of this nature you just need to accept that, rather than getting all uptight about it; even with that you can still end up with a fair amount of pure code, and have fairly good isolation of functions that do impure stuff. Looking at your list of Things You Could Do Without:

    Regarding your subclassing complaint, how one fixes any particular situation is heavily dependent on the exact details of the situation itself, so I can’t provide much advice here, except to say that usually a fairly clean design can be found. Type classes and composition are your basic tools here. I gave a small example of one direction you might take things above, but no doubt it’s wrong in the details for the particular idea of how you want your web server to work.

    Regarding monads, I love them myself, but you do have to find some way to really learn to understand them, both from the inside (writing your own) and the outside (good ways to use the interfaces they can present). You’re right that monad transformers can get clunky quickly, which is why I don’t use them all that much: when they do start to get clunky, I find that to be an indication that you should be writing your own custom monad rather than using a transformer stack. The key thing about monads is that they provide a powerful way of dividing up the total state of your program into discrete chunks, and knowing what functions modify what state. I think many Haskell programmers working on real-world applications concentrate too much on trying to make everything pure, and not enough on using state all over the place because it’s convenient, but typing things so that, while you’ve got lots of functions that deal with state, most of them deal with a very limited amount of state. STM is a good example of this sort of use: you know exactly what functions do and don’t touch TVars and so on, and atomically is what drags a composition of these into the world of IO.

    Regarding IO, as I mentioned above, some sorts of programs just want to have a lot of functions in IO because they do a lot of “real-world” work. If you can’t figure out how to bring more stuff out of IO, don’t get too unhappy becuse you’re still ahead of the game. Remember that switching away from Haskell means putting every function into IO; if that doesn’t make you even more unhappy, you’re just deluding yourself.

    I’m not clear from your example on what the issue with simpleFunction is. If you write it as “otherFunction s >>= putStrLn” it feels fairly functional to me when I remember that this is basically sugar where we’re just passing a World state between the functions in a clever way. Playing a bit with the State monad and expanding the “do” syntactic sugar and then doing some by-hand evaluation of what’s really going on may make this more clear.

    Let me give you one example of something where, even though IO is involved, things end up looking pretty clean. I’ve got a custom monad within my trading system called OrderGen which handles state for one particular loop of the order (as in orders I send to the exchange) generator. It’s typed as “newtype OrderGen a = OrderGen { run :: State -> IO (a,State) }”, and so you can see that anything running in that monad can do IO. However, it’s quite clear to me when exactly I’m doing something in IO because I need to do an explicit liftIO to do that. As it turns out, in something just over a thousand lines of code in that part of the system, I call liftIO exactly nine times in only six out of well over a hundred functions. It’s quite easy for me to find and review all of these cases and convince myself that this whole subsystem is only reading from and updating a very limited set of MVars and Channels, and also in one case doing a threadDelay. Try that with a thousand lines of code in any other language!

    cjs@cynic.net

    Curt Sampson

    14 Mar 10 at 6:03 am

  38. Curt,

    Fantastic comment. Taking it bits at a time:

    Rails: I like it; you don’t. Fair enough.

    Porting Rails to Haskell: it’s a fairly standard exercise to bring favored practices from other languages/disciplines into new languages, so, while I agree with the “square peg – round hole” assessment of the project, it’s not unreasonable to undertake the project. Especially when the project looks like an opportunity to web together a number of libraries into a more useful framework… except when the project then founders because its requirements and the libraries’ construction/monads all conflict.

    IO: I really like Haskell’s explicit usage of IO and I buy that not being careful with IO can get me into trouble… but I don’t think that I’m deluding myself if I choose to move to a language that is less strict about the usage of IO. In fact, I’m acting rationally: I’m more productive across a range of problems and I can’t really think of a time when I’ve been bitten by sloppy IO. (At which point someone will claim that I don’t know Haskell well enough or haven’t worked on large enough projects or something…)

    Rails makes maintaining type-safety hard/impossible: amen, sister! This is more than a problem for just Rails, though. See ViewState in ASP.NET MVC. I’m really interested in this problem. The MVC-ish pattern used in Rails and in ASP.NET seems to force type-UNsafety at the transition from Controller to View. I hate it, but I’m not sure how to fix it especially given that the MVC-ish structure seems to force type-UNsafety at the Controller-View intersection. I held my nose and duplicated ASP.NET MVC’s ViewState.

    Also, I’m sold on creating a number of different environment types. Multiple types would make the program more clear in return for minimally increased complexity. ServerEnvironment, WorkerEnvironment, RequestEnvironment

    Monads: Agreed. If I recall correctly, the problem I got into with Monads was that various libraries had their own monads and composing them together rarely worked, so I wound up incrementally backing down to just IO and then got everything working. But I didn’t like that solution and was frustrated by having to do so.

    alson

    14 Mar 10 at 2:18 pm

  39. I don’t buy your argument about porting Rails being a “standard exercise.” Porti ng an imperative program into an OO language, but still writing it in an imperat ive style, doesn’t make sense; why should doing the moral equivalant from a lang auge without compile-time type-checking to one with make any more sense?

    That said, if you want to do it, it’s not as hard as you make it out to be. Rather than using all those separate data types in your environment, just create a big dictionary mapping String names to a type that’s an enumeration of all your different kinds of values, and your type “problems” will disappear just the way they do in Ruby. It’s even easy to do automated conversions in your accessors, e. g., given “data Value = StringValue String | IntValue Int”, the getInt function can return the Int in the IntValue case, or read the String to an Int in the Str ingValue case.

    If you consider this offensive, you have to ask yourself why it’s not offensive when you do what is morally the same thing in Ruby.

    With IO, it’s the same. I seem to hear you saying that “f s = putStrLn s :: IO ()” is offensive, but “def f(s); puts(s); end” is not. What about “f s = putStrLn s”, without the type signature, then? All three of those have the same type sig nature. How is the Ruby one “more productive” than either of the Haskell ones?

    I think here you’ve just got the handle on the wrong end. Haskell is no more str ict about the use of IO than any other languge: it’s only more strict about dec laring the use of IO. You’re perfectly free to put every function you write int o the IO monad, if you like, just as you always do in any other language.

    Further, when you say you’ve “never been bitten by sloppy IO,” you’ve missed the whole point, unless you really meant that a variable (often a global) being set to a different value than you expected was “sloppy IO”. All this monad stuff is really all about control of access to state. That’s why program designs that re ly on lots of global state being accessable everywhere (such as Rails assumes, if you really analyze it) hurt to build in Haskell.

    As to your controller and view type safety issue: yes, you’re absolutely correct there. In fact, what you’ve stumbled on is an indication of the true problem, which is that the MVC design is not a good match to HTTP’s request/response design. (Consider, for example, how in Rails you’d implement a “view” without a “controller.” You can’t.) “Controllers” and “views” are actually inextricably linked in systems like Rails, and artifically separated for no good reason. (I’m reckoning that someone thought MVC sounded like a cool idea and wanted to apply it somewhere.)

    Rails “solves” the problem by sweeping it under the rug. Haskell, uncomfortably enough, forces you to admit that the problem is there. The solution, of course, is to dump the MVC model, avoid the impedence mismatch, and use the command/response model that matches the way HTTP works.

    I’ve in fact tested this approach in Ruby, and the QWeb framework I designed tur ned out to produce servlets rather less complex than those in Rails; a simple He llo World servlet that returns the result of a database query for example is a h alf-dozen lines of code in a single file, including all “routing” information, whereas in Rails that’s spread across three different files and possibly has thin gs encoded in the source file names and locations themselves. (Determining just what URLs a large Rails application responds to is a nightmare.)

    Curt Sampson

    14 Mar 10 at 8:43 pm

  40. Curt,

    I’m a little confused. Reading through my previous message, I don’t really see myself as saying anything you suggest I said…

    I didn’t say that porting Rails is a standard exercise. I said that it’s a standard exercise to bring favored practices from other languages to new languages. There are a number of successful libraries for Haskell that are derivative of those in other languages. Naturally, the exercise includes adapting the material to the new environment and I assume what you’re saying is that my adaptation was not to your liking.

    I’m not dogmatic about IO and I don’t consider IO to be offensive. In Haskell, IO is treated very specially, so I felt as though I was doing something wrong when I was special-casing all of my functions for IO. In nearly every other language, IO is not distinguished from non-IO so everyone does it blindly.

    Productivity: again, I’m not sure where I said anything about the productivity of the code. I said that I was more productive in Ruby than in Haskell. It bums me to admit that, but it is what it is. I can build fairly robust systems quickly and easily in Ruby or Rails; I struggle with relatively basic stuff in Haskell (and comments here suggest that others do, too).

    I also didn’t say that I’d “never been bitten by sloppy IO”; I’m sure that I have been. I said that I couldn’t remember a time when I’d been bitten. To me, that suggests that I’m working on problems which don’t have the issue that Haskell solves…

    MVC: agreed. The term is used very loosely in the web world… but, just as frustratingly as Ruby, the structure seems to make me fairly productive… I’ve definitely seen Rails projects abuse it and produce an incomprehensible app.

    QWeb: I’ve been thinking more about how to solve this C-V impedance match issue. Would love to see how you did it with QWeb.

    alson

    14 Mar 10 at 9:33 pm

  41. Sorry, I misinterpreted you somewhat about the “standard practice” thing. Still, contemplating things further, I think I still disagree with you.

    You mention that there are “a number of successful libraries for Haskell that are derivative of those in other languages.” Do you have some examples? Looking through them myself, the “ported libraries” that have a very similar interface to the original tend to have interfaces that are either fairly trivial (zlib) or don’t feel very Haskell-ish (various POSIX library ports). The ones that do feel Haskellish tend to have fairly major changes to the interface to make them so (eprocess, regex-base).

    It doesn’t really make sense, by the way, to ask if your adaptation of Rails was or was not to my liking, since the source material is so much not to my liking. I think that Rails goes about a lot of things in a very wrong way. What little I’ve seen of Django, for example, seemed a lot nicer. I’d love to show you QWeb, but unfortunately it’s proprietary and I no longer own the product.

    I think we’re in agreement about the use of IO if you no longer feel you’re always “doing something wrong” when it gets used heavily. As I mentioned, I’ve got areas of my program a thousand lines in size that are all in IO, though you’d hardly know it to look at it, and it certainly doesn’t feel like you’re working in IO.

    As for feeling more productive in Ruby, as I do for certain things as well, I suspect that there are two things that bring this about. The first is that there are some areas where Haskell doesn’t yet have the comfortable syntax and libraries needed for certain types of things, mainly in string matching and manipulation and system areas (think of things like the Dir[...] construct in Ruby). Those will probably come if someone sits down to work on them seriously. The other is that, especially due to the lack of refactoring tools, Haskell isn’t well suited to certain types of prototyping (or prototyping-like programming) where so long as the program is 20% correct, you’re happy to ignore all the broken parts. Haskell really tends to push you to handle all cases rather than just the common one, and especially if you’re writing something like a one-off script this can be far more work than necessary. Still, this isn’t always the case; one example I can think of involved a filter to colour the text passing through it based on certain rules, where after a couple of hours of struggling with it in Ruby I switch to Haskell and had a much more correct version running in about 30 minutes.

    For stuff that really does need to handle all possible cases correctly, though, Haskell is a godsend. I wouldn’t even want to contemplate trying to write my current trading system in a non-ML-based language.

    Curt Sampson

    15 Mar 10 at 2:00 pm

  42. I am surprised to see that Clean has not been mentioned, since it is often compared to Haskell. There are some problems with it, for example that it is proprietary, with a smaller user base and has less libraries.

    However, uniqueness typing really kicks the IO monad’s butt IMHO, while also being useful for destructive updates. See http://en.wikipedia.org/wiki/Uniqueness_type

    I wish uniqueness typing would enter a language that feels more presently alive than Clean.

    Regarding Clojure, it seems to me that it does not really matter if it handles multi-cores well if it is 10 times (often more) slower than C. Haskell is nice that way.

    David

    16 Mar 10 at 10:27 am

  43. David,

    Agreed. UTs certainly seem to straighten out my IO grumpiness, but they aren’t a panacea. Monads are still necessary but can be restricted in their application. See here:

    http://lambda-the-ultimate.org/node/1180

    alson

    16 Mar 10 at 11:20 am

  44. [...] Curt and I were discussing over in my last post, I am bothered by the lack of type-safety in MVC web frameworks.  In most MVC frameworks, [...]

Leave a Reply