10 ms·
Haskell is a hard language because (a) laziness is not intuitive, especially when space-performance matters (sadly, it does) and (b) pure functional programming
by michaelochurch 15y ago
Haskell is a hard language because (a) laziness is not intuitive, especially when space-performance matters (sadly, it does) and (b) pure functional programming is just not practical for most people. Like the OP said, it's awesome for brain-stretching, but not the easiest language to use.
I started writing a game in Haskell and found that the scaffolding necessary to do randomness in the "right" way was just too painful. It could be that I hadn't learned the idioms well-enough to see a better way of doing things; but if I had trouble, I think it's fair to say that most people would. Don't get me wrong: I'm a huge fan of functional programming. I just think pure functional programming is impractical. Mutable state is like radioactivity: it's necessary, powerful, and sometimes very useful, but must be handled with extreme care, not promiscuously thrown about.
My favored computation model is one in which the waterline between lambda and pi calculus is clear: message-passing between agents who should ideally be referentially transparent, unless referential non-transparency is part of their design. The upshot of this is that if an agent needs to be optimized using mutable state, none of the others care. That is, it's what OOP should have been.
- lurker17 15y agoHmm, as long as you do randomness in the IO monad, it's not any harder to do random numbers than it is to print to the screen. http://hackage.haskell.org/packages/archive/random/1.0.0.2/doc/html/System-Random.html http://hackage.haskell.org/packages/archive/random/1.0.0.2/d... getStdRandom :: (StdGen -> (a, StdGen)) -> IO aSource Uses the supplied function to get a value from the current global random generator, and updates the global generator with the new generator returned by the function. For example, rollDice gets a random integer between 1 and 6: rollDice :: IO Int rollDice = getStdRandom (randomR (1,6)) Of course, printing to the screen can be a pain in the neck. The cultural aversion to IO is Haskell's largest psychological problem. Newbies learn to avoid IO at all costs, and then never back off from the precipice and realize that sometimes IO is with the price you pay. But yeah, it's harder to do IO in Haskell than non-pure languages, and you have to bend your brain to a different model. In the worst case, though, you can just run your entire program in the IO monad, and then factor out your pure code bit by bit. None(?) of the Haskell tutorials will tell you do this, but it is the gentlest way to get real work done as a new-to-intermediate Haskeller.
- michaelochurch 15y agoThis is a fair point, but I'd generally prefer not to use the IO monad to generate random numbers because it feels "wrong", just like using IORef for mutable data when there are more "right" types feels wrong. Strictly speaking, pseudo-randomness isn't doing IO unless the source of randomness is considered something to which one is I/Oing.
- seabee 15y agoRight, but pseudo-randomness is still monadic because you are mutating the state of the random number generator. That it is in IO is an implementation detail of where that generator's state lives. It's been a little while since I worked with Haskell but I'll surprised if nobody ported e.g. Mersenne Twister to it, complete with its own Random monad. If not, maybe that could be a new project...
- jerf 15y agoYou have to get the randomness out of IO, because referential transparency and randomness of any kind (psuedo or otherwise) are fundamentally in opposition, but once you have the random source in hand, you can use it any way you like in otherwise pure code. The Random support in Haskell is set up to permit and to some extent support this usage, with the ability to "split" a random generator into something you use now and something you pass along. After that it's your responsibility to properly split & use, but I haven't yet found it to be a big problem in practice.
- waqf 15y ago> I started writing a game in Haskell and found that the scaffolding necessary to do randomness in the "right" way was just too painful. It could be that I hadn't learned the idioms well-enough to see a better way of doing things; but if I had trouble, I think it's fair to say that most people would. This. My current opinion as to what would constitute a perfect language is: something built on top of Haskell which would, in some as-yet-unconceived-of way, make it easy to thread mutable state exactly where it needed to go in your code. Like monads were supposed to do, except readable. Frankly, the code contains all the information you need to do this already, so perhaps we just need a source-code übereditor that marks it up so that you can see the dataflow.
- michaelochurch 15y agoMonads are not unreadable. When you finally get do-notation and realize that you can use the same idioms for (a) list comprehensions, (b) imperative-style programming, and (c) error-handling and option-chaining, there's a "Wow" moment. However, we can give up on selling monads to the masses. The name alone... The concept of the monad is beautiful and awesome, but it's too different to succeed with the masses.
- deleted 15y ago[deleted]
- SkyMarshal 15y ago>However, we can give up on selling monads to the masses. The name alone... A monad is just a monoid in the category of endofunctors, what's the problem? (for anyone not familiar with the in-joke: http://stackoverflow.com/questions/3870088/a-monad-is-just-a-monoid-in-the-category-of-endofunctors-whats-the-problem http://stackoverflow.com/questions/3870088/a-monad-is-just-a...)
- Periodic 15y agoCan you explain a little more what you mean about the dataflow? When I was writing some game simulations I ended up creating a few different monads for different execution contexts. I would have my main world monad which contained configuration data, world state, information about all the actors, the random number seed. Then I would have other contexts like AI which is where the AI would figure out what to do, having handy stuff like all the current actor's data easily accessible, and which would return actions on the World. Then there would be the monad for Actions themselves, which would have a source and a maybe a target. In short, I found using a few monads to clearly segment how different things interacted with each other actually made the program fairly clear. The Monads were all just stacks of State, Reader, Writer, and Maybe monads. It seemed like a very natural way to write something where the primary goal is to iterate on a function of type "World -> World", i.e. "Game ()".
- jberryman 15y ago> laziness is not intuitive That's just, like, your opinion, man. In the context of a declarative programming language, laziness is so intuitive it hardly needs a name.
- abi 15y agoWith regards to generating random numbers, the Foreign Function Interface is also really useful. Here's a one-line wrapper around random(3). foreign import ccall "stdlib.h random" c_rand :: CDouble -> CLong
- monopede 15y agoThat will most likely bite you since you're giving a pure type to a non-referentially transparent function. There is a reason why random functions are in a state monad or IO.
- JoshTriplett 15y agoIn particular, while you don't really care what order the compiler makes the underlying calls to the C function, you do care that it actually makes all the calls rather than CSEing them away. As a pure function, the compiler has every right to optimize away multiple calls to your FFI-bound random function, in favor of a single call and multiple references to the value.
- yason 15y agoBy the way, what concrete benefits do you think lazy evaluation offers in contrast to lazy data structures? I've noticed that with Clojure I can enjoy most laziness just the way I want to enjoy it by using lazy sequences and data structures. There's no lazy evaluation but I haven't really bumped into any problems with eager evaluation in Clojure. But Haskell goes deeper and has lazy evaluation as well. What additional, further good does it bring? (Besides nearly impossible debug prints...)