3 ms·
The syntax in Haskell for dealing with side effects is tedious/verbose by design. The intention is to push one in writing pure programs since pure functions and
by hiker 13y ago
The syntax in Haskell for dealing with side effects is tedious/verbose by design. The intention is to push one in writing pure programs since pure functions and compositions are easier to test, verify and reason about.
Good Haskell programs are pure; side effects are usually isolated in a "shell" wrapping the purely functional core of your program that interacts with the world (IO, networking, graphics, etc).
- Chris_Newton 13y agoThe syntax in Haskell for dealing with side effects is tedious/verbose by design. In the entire history of computer programming, I’m not sure language designers taking that kind of attitude has ever worked. Good Haskell programs are pure; side effects are usually isolated in a "shell" The real world is stateful, and many useful programming techniques rely on mutable state. Indeed, programs with no externally observable effect would be rather uninteresting, since by definition they can’t do anything useful! Please don’t misunderstand my position here. I’m strongly in favour of programming languages providing tools to control that state and avoid unexpected effects (or combinations of effects, or ordering of effects). This would prevent many common classes of programmer error, surely improving the quality of production software significantly. However, I don’t accept that trying to completely separate the two worlds is necessarily the best way to achieve that benefit. I think adopting the hard line you describe is one of the reasons that functional programming languages haven’t taken off in the industry as a whole, yet several useful tools found in those languages have been widely adopted in recent years.
- TuringTest 13y ago"functional programming languages haven’t taken off in the industry as a whole" You mean, except for Erlang?
- davidw 13y agoIf Erlang is an example of "taken off"... I am currently using Erlang on a project because it was a good choice, but I would not exactly consider it popular. Popular is stuff like Javascript, Python, Ruby - even Go. I think it's indicative that in an example of how simple Haskell is supposed to be, you find this several paragraphs in: > This is an example of a monad transformer, namely the composition of StateT ( State Transformer ) with the IO Monad. The values from the IO monad are lifted into the State monad with the liftM function. That makes me curious - I'd like to figure out what all that means, exactly, what the concepts are, what they're useful for, and so on. But I don't know if I'm a "typical web developer" either.
- TuringTest 13y agoMonad transformers is one of the trickiest parts of using monads, and certainly they're not well explained at the example - I'm afraid the linked article does an awful job at introducing monads to web developers, when most of the basic terms used remain without a proper definition. For what I've learned about transformers (although I haven't used those at practical projects), they are used to combine several monads and wrap them around a single value with several layers of monadic types, so that the adequate monad can handle the inner value when the occasion arises. But don't take my word for that; understanding transformers "does" require knowing a good deal of category theory.
- dllthomas 13y agoUsing monad transformers doesn't require any category theory, just Haskell and an understanding of Haskell's use of monads (which are inspired by category theory but aren't going to help you much with proofs). It might help in writing them - I've not written any that weren't toy examples, so I couldn't say. The idea behind monad transformers is simply that they wrap a monad and give back a monad (which could in principle be wrapped again). So you can have, for instance, StateT Int (ReaderT String IO) You would have a monad that carries a modifiable integer, an unmodifiable string, and which sequences IO actions. There is a function called lift which wraps a monadic value in a transformer. So for the above, for the unfamiliar, ReaderT provides ask :: ReaderT a m a which here would be specialized to ask :: ReaderT String IO String If I were working in that monad in do notation, I could do something like: str <- ask and get back the string the reader provides in str. If I were working in our entire stack, though, ask isn't the same type - so I could instead do something like: str <- lift ask to turn a ReaderT String IO String into a StateT Int (ReaderT String IO) String. So if I had: flip runReaderT "foo" $ flip runStateT 7 $ do str <- lift ask modify (\ x -> x + length str) i <- get lift $ lift $ putStrLn $ str ++ show i we could look at the type of the entire do notation expression. It must have a StateT Int on the outside, because the first thing we do with it is runStateT; it must then have a ReaderT String because the next thing is unwrapping a ReaderT. Running through it: str <- lift ask grabs a String ("foo") out of an ask that's been lifted into our stack from a ReaderT String IO String modify (\ x -> x + length str) At the top level, updates the state in our StateT with the result of applying the function to the current value. In this case, 7 + 3 = 10, so the StateT on the outside now holds 10. i <- get grabs that 10 from the StateT and calls it i lift $ lift $ putStrLn $ str ++ show i Appends the string we grabbed ("foo") and the stringification of i ("10"); this is passed to putStrLn which gives us a value of type IO () that, when evaluated, will print "foo10". IO is not the monad we're working in, though, so we lift it into ReaderT and then lift the ReaderT into StateT, to be ultimately unwrapped. The whole expression here has type IO (), and could serve as a definition for main if you want to play around with it. I should admit that I lied a little bit. The lifts are actually not necessary, because of some typeclass voodoo that I think makes it slightly harder to understand coming in but usually easier to use (unless you need to nest two transformers of the same type...). What's done is that get and ask and such aren't defined just for the one type but for anything that implements MonadState or MonadReader (respectively, as it happens). Then, there is an instance defined for "any monad transformer wrapping anything that implements MonadState", &c, which obviously propagates up the stack, so most of the time you can leave out explicit calls to lift.
- tmoertel 13y ago> The syntax in Haskell for dealing with side effects is tedious/verbose by design. I don’t think Haskell’s designers have ever had the goal of making imperative programming more difficult. Simon Peyton Jones himself, as far back as thirteen years ago, wrote that “Haskell is the world’s finest imperative programming language.” [1] If you want to do imperative programming in Haskell, it’s easy: Just code everything within the IO monad. It looks just like imperative code in most other languages, only you get to use Haskell’s light syntax and advanced runtime (with great concurrency abstractions, modern GC, and lightweight threads), so it is indeed a pretty fine imperative experience. But Haskell also gives you another option for imperative programming, one that mainstream imperative programming languages do not: the option to provably wall off the small amount of impure logic in your code from all the rest. And once you’ve tried this option for a while, you’ll probably never go back to doing it any other way, at least for non-trivial projects. Keeping the main bulk of your code pure makes life so much easier that it’s worth paying the small additional cost to wall off the impure bits. Yes, you must centrifuge off the impurities. Yes, you must create a protocol wall to safely communicate with those impurities. And, yes, these costs are real. But they are bounded by a small constant effort factor and applied to only a small portion of your overall code. In exchange for this small cost, you get to keep the rest of your code (most of it) pure and collect the purity dividends for the project’s lifetime. For most non-trivial projects the dividends add up to a huge payoff. So you can use Haskell as a regular imperative programming language, and it's a pretty good experience. But if you use Haskell as Haskell, you can have an even better experience. [1] http://research.microsoft.com/en-us/um/people/simonpj/papers/marktoberdorf/ http://research.microsoft.com/en-us/um/people/simonpj/papers...