Hacker News new | ask | show | jobs
by michaelochurch 4193 days ago
This is a really interesting article (and a blog I want to come back to). Thanks, OP.

What Haskell seems to achieve, and I'm not an expert on it yet, is an incentive system that encourages small functions especially when you're "in" a monad, because of the "any side effects makes the whole function 'impure'" dynamic as seen in the type system (also known as "you can't get out of the monad"). Of course, it's sometimes a pain in the ass to thread your RNG state or other parameters (that remain implicit in imperative programming) through the function, and that's where you get into the rabbit hole of specialized monads (State, Reader, Writer, RWS, Cont) and, for more fun yet, monad transformers.

I think that the imperative approach to programming has such a hold because, at small scale, it's far more intuitive. At 20 lines, defining functions feels dry and mathematical and we're much more attracted to the notion of doing things (or, making the computer do things). And, contrary to what we think in functional programming, imperative programming is more intuitive and simpler at small scale (even if more verbose). It's in composition and at scale (even moderate scale, like 100 LoC) that imperative programming starts to reach that high-entropy state where it's hard to reason about the code.

3 comments

The most important feature of an effect typing system is that it's a pain in the ass. More technically, the complexity of effects is (usually) infectious so a type system either lies to you about effects or is really obnoxious.

The upshot of either thing is that you're heavily encouraged to shatter code into the purest composable fragments you can discover.

This is an excellent synopsis of my experience with Haskell.
At 20 lines, defining functions feels dry and mathematical and we're much more attracted to the notion of doing things (or, making the computer do things)

I think the "feels dry" problem can be solved by always writing functions in an environment rich with sample inputs and which passively calls your function for you! The text of a function would be the central component of a screen with all kinds of 'doo-dads' poking, prodding, testing, exercising that function in all kinds of interesting combinations.

What this does is underscore the concrete utility of a function, which is fundamentally tied to the application to concrete arguments (and it's ability to cooperate with other functions. Function application always results in a flurry of instructional von Neumann-esque side-effects; and I don't think that can be avoided without violating the Second Law.

Haskell is lazy, which paradoxically means that its execution is sequential. In order to be truly liberated the execution order needs to be unspecified.
Lazy and eager evaluation are both sequential evaluation schemes, and are properties of language implementations, not of languages. Haskell is thus not lazy (nor is any other language). Rather, Haskell is a non-strict language, meaning that lambda is just abstraction, not abstraction-plus-strictification as in "strict" languages. Non-strictness does not imply sequential evaluation or indeed any particular evaluation order. (Ditto for strictness.) For instance, speculative evaluation is a parallel strategy that can implement non-strictness.
> Haskell is lazy, which paradoxically means that its execution is sequential.

I don't understand what you mean.

Although I don't agree that "lazy => sequential", what he/she probably means is that Haskell's lazy execution model sets constraints on program execution, in analogy to an imperative language, which also sets contraints on execution order.

Advantages of an unspecified execution order: more compiler optimizations allowed/possible.

Disadvantages: even harder to reason about. In fact, some functions may or may not terminate, depending on the whims of the compiler.

Termination (really, non-bottom-ness vs bottom-ness) is a semantic (denotational) property, independent of the whims of any correct compiler. Execution/evaluation order, on the other hand is an implementation (operational) choice. For an implementation to be correct, the termination or nontermination of generated code must be agree with the semantics of the language.
In principle, you can leave denotational properties unspecified in the language spec...