Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Credible empirical investigations of programming language efficiency are too hard to carry out (though they could be done in principle). However, what we can do instead is look the evolution of programming languages: what features do the latest creations have, what features are older languages retro-fitted with? What ideas have seen little uptake?

(i) Higher-order functions: Yes, slam-dunk win. Essentially all new languages have them, Java and C++ retrofit them.

(ii) Lazy evaluation order by default: mostly dead. All new language use eager evaluation, even SPJ is sceptical that lazy evaluation should be the default. Scala has an interesting approach: eager by default, but you can switch to CBN (although not full laziness) if required. This hybrid form enables you to define control-operators easily, while keeping the simplicity and efficiency of eager evaluation. Exceptions are languages with dependent types that are really theorem provers (e.g. Agda).

(iii) Purity: dead. All new languages have side-effects. The widely used FP languages like Haskell, Ocaml and F# have side-effects. Exceptions are languages with dependent types for Curry-Howard-based theorem provers (e.g. Agda), because there is no convincing Curry-Howard correspondence using state.

(iv) Everything is function application: open. This may work reasonably well in purely sequential languages, but there are theoretical reasons to believe that message passing is not function application (while function application is a special case of message passing).

(v) Types. The answer on this one depends on whether one is a proponent of statically typed languages or not, clearly an unresolved question. For dynamically typed languages, the question is moot, so the rest of the discussion is irrelevant to those who think that static typing is a bad idea.

(vi) Types (1), type inference: Slam dunk winner. All new languages have inference or wish they could have it but don't know how to do it (Scala). There are probably some hard trade-offs between expressivity of types and efficient decidability of type inference (B Pierce termed Hindley-Milner a "sweet spot"), so we might have to tolerate some type annotation being required, e.g. in local or bidirectional inference.

(vii) Types (2), algebraic data types, pattern matching: winner. Most new languages have them in some form.

(viii) Types (3), monadic encapsulation of effects: open. Most new languages have not yet embraced this. Some conceptual problems not yet solved, e.g. destructors for state monads. There are also expressivity problems, e.g. not all effects can easily be expressed as monads, and composition of monads doesn't always work as desired. Moreover there are pragmatic questions about whether monadic encapsulation should be required or optional.

(ix) Types (4), higher-kinded types: open. Not all new languages have them, but HKTs are generally considered useful, but not super important, cf Rust's evolution. If monadic encapsulation of effects is used, then HKTs become highly desirable, cf the recent Scala fork.

   --------------------
Any other suggestions?


I wish more discussions of the values of FP were this pragmatic. FP has brought some very valuable ideas into the mainstream, along with a few bad ones and many for which the jury is still out. I get a little tired of the people that see Haskell, for example, as the One True Way when it's clear it's a language design with its own set of tradeoffs, just like all the rest.


I guess there are a few people out there who would claim a "One True Way," but mostly that seems to be a stereotype?

As for me, I get a little tired of these pseudo-objective "debates" trying to decide which programming language is "better" by evaluating bullet lists, trends, and so on.

It seems pretty clear that different people can be very productive in different languages and using different techniques. Different projects and companies use different language; that's a plain fact.

For someone who's working with a happily functioning system written in, say, Haskell, it's a little weird to go on discussion forums and see people arguing over whether or not Haskell is a "productive" language.

In almost every HN thread about something related to Haskell, there'll be a few comment threads started by someone who claims that "functional programming doesn't work in the real world" or that "laziness is only good for research papers" or something like that. That's frankly flamebait by now, and I'm generally saddened by how much of the discourse about functional programming (on here, specifically) becomes this kind of groundless fight about whether or not it's possible to build real software with it.

Yes, of course Haskell, as a real-world thing instead of a perfect Platonic idea, has flaws, things that could be improved, problems one has to work around, and so on. That is inevitable and it is true for all programming systems ever!

It's always interesting to hear from people who have made good-faith efforts to use functional programming techniques and systems, and failed. Specific complaints, even feelings and vague opinions, are useful information—when they come from experience.

Anyway, that's my rant for today about the discourse on functional programming...


I think we all agree that the most important variables determining a programmer's productivity with a language (a) are how familiar/comfortable the programmer is with the language and (b) what libraries and tools are available. Language discussions take this as given. They are about what we can say about languages if we hold these factors (familiarity, ecosystem) constant. Is there still a difference, and those who participate in such debates think, yes there is.

There is another dimension to language wars: discussing languages is part of language learning, part of becoming a master. After all, teaching/discussing is one of the most efficient learning tools, and having to defend one's language helps to clarify one's own thoughs, in parts by looking at it from other peoples perpectives.


I'm not sure I agree with your exact formulation of what determines a programmer's productivity (and I think the notion of "productivity" is somewhat mystifying), but I somewhat agree that we can abstract away the concrete differences in programmer familiarity and pragmatic tooling.

Although even there, I'm hesitant. There's a sense in which the idea of the language as separate from its tooling is a judgment in itself, one that tends to divide the language community into camps already. For example, Smalltalk, Forth, and Common Lisp are radically "integrated" systems, in which the languages themselves can be redefined at runtime. There, it's in a way academic to talk about the formal grammar and semantics of the language in a "Platonic" sense.

But that might be a tangential point.

Probably I am uninterested in and somewhat averse to the kind of language discussion you're defining. I think that "language systems" operate in the world in complex ways, only a fraction of which are accessible through bullet-point-style comparisons of PL features.

And something about the interminability of "language wars," like the eternally recurring static-vs-dynamic skirmishes, fuels my aversion. I've heard so many pseudo-objective arguments for and against static typing, and these arguments have had so little cash value, that I've become skeptical of the whole mode of discussion.

So I'm much more interested to hear about, say, concrete successes or failures (or ongoing attempts) using static or dynamic typing (or laziness, or what have you) in concrete real-world settings.


> For example, Smalltalk, Forth, and Common Lisp are radically "integrated" systems, in which the languages themselves can be redefined at runtime. There, it's in a way academic to talk about the formal grammar and semantics of the language in a "Platonic" sense.

Not as academic as you might think. We could analyse how easily a language makes such an integration possible. I'm not sure you could have a lispy environment in C, for instance.

Once you know what kind of ecosystem a language enables, you can compare those ecosystems. (As opposed to existing ecosystems, which are highly influenced by sheer popularity and historical accidents.)


Thought experiment: if Haskell were actually the One True Way, what would you expect to be different? Would you be able to tell?

To say "every language design has its own set of tradeoffs" implies that all languages are equal, but that's clearly not true; we can all think of languages that are worse than others. I'm not at all convinced that there isn't an overall best language out there (I don't think it's Haskell, FWIW).


'To say "every language design has its own set of tradeoffs" implies that all languages are equal'

I don't think that implication is there at all. It absolutely doesn't hold for strong notions of equality - strong equality of all languages should imply that they all share a single set of tradeoffs.

It also clearly doesn't hold for "equal suitability to any purpose", since something less suitable to the purpose may be traded off for something more suitable.

I think any general notion of "equality of quality" can probably be viewed as a special case of the above reasoning - we are picking a set of attributes, of benefits and drawbacks - but we've no guarantee that they all net out the same however we are measuring them.


Is there a best human culture? Clearly some are further down the tech tree than others, but...is imperialism really inevitable?

PHP isn't a bad language if its tradeoffs work for you. But you would be hard pressed to see anything good today in BASIC from the 80s (like we might miss things if we all of a sudden were sent 1000 years back in time). There is no best PL, but there are new PL technologies that make programmer experiences better; using a language that doesn't include at least a few of those technologies is probably going to be a handicap (compared to your peers who are using them). Haskell is bit up the tech tree, but not incredibly so compared to other modern languages like Scala and even C#.


It's politically unacceptable to say so, but some human cultures are clearly worse than others, and yes, I expect there probably is a best culture. I wouldn't begin to guess at which one that is, but I would take people seriously if they're saying that a particular culture has got particular things right and other cultures can learn from that. The problem with imperialism was less the culture and more the exploitation and theft, which is not really relevant to the programming language case.


There is probably not a best culture (though there are definitely bad ones), just cultures that are further down the tech tree and therefore can out compete other ones. The same is true with programming languages.


I would be interested to hear what the bad ones are.


(iv) I come from an EE background so as when it comes to fundamentals I find it a help to think in terms of message passing and transmitters and receivers. It clears up some of the debate for me about which is more fundamental, objects or functions. To me they are all things (transceivers) that transmit and/or receive messages. In the case of a function the transceiver is alive for the duration of the call. In the case of an object or actor it may be alive for longer (and, if stateful, respond in ways that are harder to predict and reproduce)

But this is just a way of thinking that makes me feel comfortable because I like to have a physical model. If someone else is happy with function application as being the fundamental building block then I have no problem with that. However you said that "there are theoretical reasons to believe that message passing is not function application (while function application is a special case of message passing)." I would be interested to get more background on this. Would you have any references for this? Thanks.


To the best of my knowledge, the insight that function application (and other control structures) can be seen as special cases of message passingh comes from the actor people [1, 2]. The first proper mathematisation is Milner's breakthrough encoding of lambda calculus in pi-calculus [3]. This lead to fine-grained investigations into what kinds of interaction patterns correspond to what kinds of functional behaviour (CBV, CBN, call/cc etc), which in turn inspired a lot of work on types for interacting processes.

I don't remember off the top of who first showed that parallel computation has no 'good' encoding into functional computation (lambda-calculus). I'll try to dig out a reference and post it here if I find it.

But the upshot of all this is that message-passing is more fundamental than functions / function application.

[1] C. Hewitt, H. Baker, Actors and Continuous Functionals.

[2] C. Hewitt, Viewing Control Structures as Patterns of Passing Messages.

[3] R. Milner, Functions as Processes.


You can take this work much further in an "FP" kind of way by noting that the pi calculus is a good proof system for linear logic. Frank Pfenning has some lectures on this.


I don't fully agree with Pfenning here. The pi-calculus is not that good a proof system for linear logic: pi-calculus imposes sequentiality constraints on proofs that impede parallelism. Take for example the term

     x!<a> | y!<b> | x?(c).y?(d).P
You have to sequentialise the two possible reductions on x and y. The culprit is the pi-calculus's input prefix, which combines two operations: blocking input and scoping of the bound variables (here c and d). This sequentialisation is not true to the spirit of linear logic proofs (think e.g. proof nets).

Conversely, I don't think linear logic is a good typing system for pi-calculus for a variety of reasons: among them that linear logic does not track causality well, and because it doesn't take care of affine (at most once) interactions well.


These are good points and I'll have to consider them. I was planning on going back through Pfenning's notes again in a bit and I'll keep a more critical eye this next time. Thanks!

(Also, to be clear and with reference to other threads we've conversed on here, I think of linear logic and pi calculus, even if they aren't well-corresponding, as fantastic, unavoidable examples of non-function-application style programming.)


Thanks very much for that. I'll read them all. (I did a bit of reading around actors before including some Hewitt but didn't pick up on the message passing equivalence. Will read closely. The Milner paper looks very interesting)


"Some conceptual problems not yet solved, e.g. destructors for state monads"

I'm not sure what you're talking about here - could you elaborate?

My best guess would be that by "state monads" you mean streaming libraries like pipes and conduit, where there have indeed been some interesting questions around finalization. Calling these "state monads" is surprising enough that I seek clarification, however. Regarding those libraries, I don't know the current status of that discussion (which mostly took place a couple years back) but I do know the libraries themselves are quite useful.


Thanks for your question. Consider this stateful program in pseudo-code

    def f x = 
       let i = ref 17 in
       while i > 0 { i := i-1 }
       return i 
It is referentially transparent. The literal translation into Haskell needs the state monad, despite referential transparency.


This is a regionalized or "benign" effect. To the degree that this can be proven in Haskell's synthetic effect calculus it's OK. Since Haskell does have `unsafePerformIO` we can, with great care, introduce new rules to the type system. This is how `ST` was created

    -- we need a primitive first
    while :: Monad m => m Bool -> m a -> m ()
    while conde bodye = do
      cond <- conde
      if cond then bodye else return ()

    -- then we can make a direct, verified pure translation
    f :: a -> Int
    f x = runST $ do
      i <- newSTRef 17
      while (readSTRef i >>= \ival -> return (ival > 0)) $ do
        modifySTRef i (\ival -> ival - 1)
      ival <- readSTRef i
      return ival


I think your "while" is "when".


Oh, thanks! Good catch. Here's `while`

    while :: Monad m => m Bool -> m a -> m ()
    while conde bodye = do
      cond <- conde
      if cond then bodye >> while conde bodye else return ()


There are several candidate translations. Which you might choose depends on those constraints on the actual problem (instead of this pretend one) that make "f x = 0" not an eminently better solution.

The one that probably most closely matches the semantics of the pseudo-code would be:

    let f0 x = unsafePerformIO $ do
        iRef <- newIORef 17
        whileM_ (fmap (>0) $ readIORef iRef) $ do
            modifyIORef iRef (\ i -> i - 1)

        readIORef iRef
Better would be to let Haskell help check that the mutation is local and no references escape:

    let f1 x = runST $ do
        iRef <- newSTRef 17
        whileM_ (fmap (>0) $ readSTRef iRef) $ do
            modifySTRef iRef (\ i -> i - 1)

        readSTRef iRef
There's the more idiomatic translation, where for something this simple we wouldn't bother with State:

    let f2 x = go 17
      where
        go i | i > 0 = go (i - 1)
             | otherwise = i

The literal translation using State is:

    let f3 x = execState countdown 17
      where
        countdown = whileM_ (>0) $ modify (\ i -> i - 1)
In all cases, the function as a whole is referentially transparent.

In your function, "while i > 0 { i := i - 1 }" is clearly not referentially transparent. Attempting to transliterate in a way that preserves this expression, it is unsurprising that we wind up with expressions that depend on state. The State monad is one way of encapsulating this. Note that f3 and f2 are actually very similar. The execState function doesn't do any deep voodoo - a value of type "State s a" is a thin wrapper around a function of type "s -> (a, s)"; execState simply extracts this function, applies it to a value, and returns the second part of the output.

The actual definitions are equivalent to these (albeit slightly more complicated to keep things DRY w/ transformers):

    newtype State s a = State (s -> (a, s))

    execState (State f) s = let (_, s') = f s in s'

What I don't see, in any of this, is a problem (conceptual or practical) involving "destructors". Is your objection to the need to call execState (or its friends)?


Thanks. Do any of these renditions have the target type int -> int?


All of them.

More precisely, since the argument isn't used it's type is unconstrained, and the result could be any numeric type, so they all actually have type (Num b => a -> b), but that can be used anywhere you're expecting an Int -> Int.


> The literal translation into Haskell needs the state monad, despite referential transparency.

Sure, literally translating imperative procedures into Haskell code often needs the State monad since imperative procedures gratuitously use state modifications that are logically unnecessary for their function.

But I'm not sure why any meaning should be ascribed to this beyond "translate function, not code".


Could you be more precise? The obvious way to translate this into Haskell is 'f x = 0'.


While f x = 0 is extensionally equal to the original program, it doesn't implement the algorithm in question (iteratively counting down a local reference).


Ok, so how about

    f x = g 17 x
        where g i x = if i > 0 then g (i - 1) x else i


The point of the example was to (1) use mutable state (hence force the state monad to show up in the function's type), but (2) in a way that defines a pure function.

The example is intended to point towards an expressivity gap of the monadic encapsulation approach to effects, in that one can't (as far as I'm aware -- I have not programmed in Haskell) hide state usage, even when this is extensionally OK.

It is an open research question to define type/effect systems that (a) enable programmers to hide such benign state usage, (b) preserve type inference for interesting typing systems (say F-omega which is the heart of Haskell's types), and (c) are pragmatically viable.


"The example is intended to point towards an expressivity gap of the monadic encapsulation approach to effects, in that one can't (as far as I'm aware -- I have not programmed in Haskell) hide state usage, even when this is extensionally OK."

You absolutely can do exactly that. Even without cheating (unsafePerformIO and friends), usually.


If this is always possible without cheating, then I have definitely misunderstood something about Haskell.

Could you point me towards an explanation how the hiding of stateful expressions works in Haskell in general?

Let me clarify my question (a bit). Are you saying one of the following is true?

(1) You are saying for any program M that uses state (i.e. M's type mentions the state monad in an essential way) but in a pure way, i.e. M's stateful behaviour is not visible from the outside, there is a context C[.] (not involving tricks like UnsafeIO) such that C[M] has the same behaviour as M (as seen from the outside), and C[M] has (essentially) the same type as M, except that the state monad is gone?

(2) There is a 'nice' translation function f on programs such that for any program M that uses state in a pure way (same as (1)), f(M) has the same behaviour as M (as seen from the outside), and f(M) has (essentially) the same type as M, except that the state monad is gone?


I'm not sure I understand the distinction (quite possibly just 'cause it's early).

A fruitful way to look at monads is to say a value of type M (where M is an instance of Monad) is a program in a language. State is a language for describing stateful computation. Reader is a language for describing parameterized computation. ST is a language for describing controlled, local mutation. IO is a language for describing arbitrary side effects.

Most of these have pure interpreters, with the most generic typically called runM (so, runState, runReader, &c). The only place cheating comes in is unsafePerformIO - interpreting IO is the job of the runtime, which does it in a more controlled way relative to the rest of your program.

The type of the return value of an interpreter, of course, doesn't tell you what kind of program you ran - it just tells you the result.


Revisiting to speak directly to the question you've asked, both 1 and 2 are true for the State monad.

For 1, to get from a value of type "State s a" to a value of type a, you simply call evalState and supply an initial state of type s.

For 2, you could look at the structure of the program and unwrap all (State s a) values to have type (s -> (a, s)), updating the various functions used to operate on (State s a) values to match.

In either case, you would not see State in the resulting type signature. In case 2, you would still need to provide an initial value of type s - you can't do much interesting with a function but apply it.

Note that the function in 2 is more theoretical than practical. You could in principle write it with Template Haskell, but since it doesn't change the behavior and you can just do 1 to change the type the same way there isn't reason to.


Thanks. This is very interesting, but different from what I used to believe about Haskell (as you can imagine, I'm not an active Haskell programmer). I will have to revisit my ideas.

I still wonder how this would work with scope extrusion of references. Consider this little Ocaml program

    let ( impure, pure ) =
      let i = ref 0 in
      let impure x = ( i := !i + 1; x + !i ) in
      let pure x = (
        i := !i + 1;
        let res = !i + x in
        i := !i - 1;
        res - !i ) in
      ( impure, pure );;
which returns two functions called "pure" and "impure". Both are of type int -> int. The function "pure" is computing the successor function (let's ignore corner cases), but using hidden state. The function "impure" is using the same hidden state, but is clearly impure.

Is it possible to do the same in Haskell? By "the same" I mean giving "pure" the type int -> int, despite using hidden state that is used elsewhere?


It's harder to be confident about impossibility than about possibility, but I think this would require "cheating". I'm not sure if you meant it as a bug, but note that outside of a single-threaded context "pure" isn't referentially transparent - there seems to be a race condition that could lead to pure computing different values if impure is called during pure's execution.

Something with a similar form that would be more realistic to ask for would be a cache for a pure-but-expensive function and some IO operations to inspect/manipulate that cache. I believe you would need a call to unsafePerformIO, but that could be a reasonable thing to do.


Thanks. I know that "pure" is not thread-safe. I'm quite happy to consider the expressivity of Haskell's monadic approach to effects in a sequential setting first -- much easier to reason about. I wonder if anybody has investigated this expressivity problem. If not, there's an interesting problem waiting to be tackled.


I don't see the problem. Pure code being automatically thread-safe is a boon. I would much rather be unable to write thread-unsafe code without noticing (and able to when I deliberately decide to).


My interest is in proving expressivity results. Such proofs are much easier in a sequential setting. Indeed, at this point I don't even have a strong intuition what exactly the expressivity limitations of Haskell's monadic effect control are.


Really, really have to say I disagree with (iii). Purity is growing more and more day by day. From other comments you seem to have taken a fairly non-standard definition of purity and I'd encourage you to stick with the normal one as it'll let you talk about more interesting things—see further comment on (viii).

I don't think (iv) is open at all. The fact that you can encode everything as function passing is an interesting trick, but it's merely one of several (you can, e.g., also encode everything categorically or via supercombinators and graph reduction, which turns out to be more practical for lazy languages). Function application is just one really well understood semantic foundation.

I find (v) far more subtle than you make it out to be. The standard static/dynamic types divide is silly because both sides are talking about different things. The static side wins completely because honestly, taken at face value, they don't claim that much. All languages are typed, some have interesting type theories, some compilers reject programs which are syntactically OK but violate the type theory.

(viii) is interesting. In my mind, you've conflated this with what most people would call purity---however, monads are merely an implementation of effect typing/purity. Others exist and are experimented with. Also, composition of monads always works as desired... there's no strange edge-case to it. It's merely that they don't compose like functions in a simple way. When you examine what they would entail you learn that they ought not to as well.

(ix) HKTs are sort of a gateway drug to type-level computation. It's remarkable how many languages just more or less let their type-level language wither and die. I'm not claiming that HKTs are a slam dunk victory, but I would be willing to bet that the evolution of languages will lead us to (a) having them universally in the next two decades and (b) wondering why everyone was so hung up on HKTs---I mean, I suppose I can understand singletons, but HKTs? Really?


     monads are merely an implementation of effect typing/purity. 
Yes, and the one Haskell has chosen. Is it the right way? Most newer languages are not following Haskell here, instead they have effects like state and exceptions 'directly. That's what I was pointing out. And yes, composition of monads is a bit non-canonical.

As to HKTs, I agree, languages should have them. I was just pointing out the state of the art, post Haskell.


I would suggest that effects which are "directly" embedded are impure effects (w.r.t. our other thread). Essentially, without reifying them into values with types it's harder to reason about them.

A more interesting dividing line are things like computation effect typing in languages like Bauer and Pretnar's "Eff" or McBride's "Frank".

http://www.eff-lang.org/

http://homepages.inf.ed.ac.uk/slindley/papers/frankly-draft-...

But yes, if you consider other "practical, modern" languages, many retain impurity.


There are very different options.

First, approaches like Eff take pure functional computation as a basis and layer effects like call/cc or concurrency on top of that. I think it's better to go the other way and take concurrency, or, if you want to start sequential, a lambda-calculus with jumps a la call/cc or lambda-mu, as a basis see pure functional computation as a restriction of the base calculus.

Second, it's unclear that effects should be reflected in types for reasoning. That road leads to complicated typing systems without type inferences (see also dependent types). I think the following alternative is more appealing: stick with lightweight types (think Hindley-Milner), and leave reasoning to an external calculus (variants of Hoare logic).


That "analytic" style you suggest is really interesting, too. I'm not sure I have a dog in the ultimate race and I'd love to see both analytic and synthetic styles flourish, but from a mere terminology point of view I'd distinguish between the two languages in means of describing them as "pure" or not.

I also really worry that external proofs will ultimately be neglected in practice. I suppose this is again a "UX of Types" thing, but I'm something of a believer that anything that you do not literally demand of the programmer will eventually be sacrificed.


I'm not totally sure what you mean by "external proofs will ultimately be neglected".


     I find (v) far more subtle than you make it out to be.
I don't want to get start or be involved in another static-vs-dynamic typing debate. I have a strong preference for static types, but I appreciate the arguments of the other side, and content that neither side has really convincing empirical arguments supporting their respective positions.


Eh, I just find the whole "debate" silly. The two systems aren't comparable and can and do live together side-by-side in every program. I feel I agree saying that empirical evidence here is going to either be silly or impossible to achieve for a long time.

So, I guess I just tire of it. If you're making a language today you can either take advantage of information known statically or not. I think there's a relative burst in activity here as some of the more genuinely useful forms of static analysis have become recently more popularized and I personally feel it's silly to not use this information... but it's just a choice, not a religious war.


    Purity is growing more and more day by day.
What I meant was this: a language should not be pure by default, rather, it should be possible to say (and get checked by the compiler): this is pure. Quite how to set up a pragmatically viable type and effect system to guarantee this is unclear.

PS what is the "normal" definition of purity?


Hm, I think given your stance now I'm not sure whether I properly understood what you said elsewhere on this matter.

"Purity" is a tricky technical concept that can be defined, but rather than do so explicitly I'd rather note that it necessarily requires the asserter to take a stance on what "observable" means. Haskell is clearly impure in that its laziness is implemented using mutation "behind the scenes", but it is also taken to have an "observable" semantics where such mutation is invisible and IO is merely an interesting value. This gives it purity [0] according to that degree of observability.

I think I'm having difficulty parsing your interpretation of purity because you're moving smoothly between analytic and synthetic judgements of the notion. Agda, as an example, is a system which has a synthetic notion of purity as the "language" is thought to not contain terms which represent effects. Haskell plays a trick where it has, essentially, two sets of semantics. The first shares this property (modulo non-termination, see [0]) and is where most "programming" occurs---here we use monads as a system for constructing terms of the second semantics, IO. The second, IO, semantics are vastly impure.

To the degree that this trick is "purity in a language" then purity is becoming a very popular idea. Haskell uses its type system to provide synthetic distinction between the two choices of semantics, but if you use a language lacking that discipline then you can merely call this "isolation of side effecting code" and then nobody will disagree with you.

If you don't want to call this "purity", and maybe you don't, then I feel you're in the minority though you aren't without an island to stand on.

Frankly, both are sort of an overloading of the term, though.

[0] Actually, it gets a lot trickier. It's well known that non-termination is an effect and it's a little slippery to define Haskell's notion of "observability" so as to no longer observe non-termination. I mean "slippery" here to say that it takes some technical finesse and that it tends to go against people's intuition a bit more harshly than the rest.


     I don't think (iv) is open at all ... Function application is just one really well understood semantic foundation.
I disagree, see my reply to "discreteevent" above. Parallel computing cannot be reduced to functions.


No, sorry, to be clear. I don't believe that "reduction to functions" is anything but a clever or perhaps illuminating toy and that (iv) is closed in the opposite way: it's certainly not true that "reduction to functions" is right.


Regarding purity and encapsulation of effects - I would say instead say that it's just not decided if monads are the best mechanism for controlling effects, but there is still a lot of interest in some mechanism for control and reasoning about them.


I fully agree with that, that's why my point (viii) was: "monadic encapsulation of effects: open". But it's important to realise that (a) the existence of effects and (b) their control at the level of types are two differnt things.


I agree mostly with all your points.

Regarding lazyness by default, although you are right, I would say the lazy modifier found in Scala/ML derivatives/C#/D and the use of sequences/ranges kind of help half way there.


Scala doesn't have lazy evaluation of function arguments, it's got call-by-name. The difference is that lazy evaluation caches the value an argument evaluates to the first time it is needed, so it's evaluated at most once, whereas call-by-name simply passes the parameter unevaluated and the result of evaluation is not cached, so might be computed more than once.

Consider this Scala program

     object Main extends {

       def tryit [ T ] ( block : => T ) : Unit = {
         println ( "----- Running 1st block -----" )
         block
         println ( "----- Running 2nd block -----" )
         block
         println ( "--------- Finished ----------" ) }

       def main ( args : Array [ String ] ) {
         var i = -1
         tryit ( { i += 1; println ( s"i = $i" ) } ) }

     }


Does scala not have lazy vals anymore?


Sorry, I had forgotten about lazy vals. Thanks for reminding me. They are cached, so proper lazy [1], see e.g. https://stackoverflow.com/questions/3041253/whats-the-hidden...


Holy shit ... is your space bar broken?


> Purity: dead. All new languages have side-effects. The widely used FP languages like Haskell, Ocaml and F# have side-effects.

You don't consider Haskell to be pure? A big win of using monads for side effects was to keep purity right?


Before using the word "pure" it should be mandatory to define the sense in which you are using it. There are at least two senses, and people talk at cross purposes and get awfully antagonistic.

Sense (1): The return value of f can only depend on x, and not y (or anything else)

    x   = ...
    y   = ...
    ret = f x
Sense (2): A program written in the language can't interact with the outside world.


"The answer on this one depends on whether one is a proponent of statically typed languages or not, clearly an unresolved question" - I have yet to see a static-to-dynamic transpiler ;-).


Purity is not dead. Haskell 'has' side effects but they are explicitly managed. Managing side effects and relegating them to well defined areas lives on in newer languages.


There is no need to put "has" in scare quotes. Haskell does have side effects like C, Java and Python (although maybe not quite as conveniently). If you want to look at side-effect free languages, consider Miranda, Coq, Agda.

What Haskell contributes (in a concrete implementation, the ideas go back to Moggi's theory) is the monadic encapsulation of effects, which I treat as a separate point under (viii). Monadic encapsulation of effects is not absence of effects. It's a reflection of effects at the type level.


You're using a somewhat non-standard definition of "purity", which makes your claims more confusing than they need to be. Haskell is usually referred to as pure, and has had encapsulated effects from the start. Your claims are true using your definition of "purity", but false using the more common one.


As a point of historical note, Haskell has not had encapsulated effects from the start. Monadic IO was introduced in version 1.3, roughly 6 years after version 1.0.

http://research.microsoft.com/en-us/um/people/simonpj/papers...


Thanks for the correction! I wondered about that when I was writing it but didn't have time to go double-check.


It is an absence of side effects because side effects are defined as effects that cannot be accounted for. As soon as you have a mechanism to document those effects, they are not "side effects" any more.

hello :: IO () hello = putStrLn "Hello World"

`hello` is a "pure" value of type IO (). putStrLn a "pure" function of type -> IO ()

IO () is a value like any other "pure" value, like [Int].


C is "pure" too.

The statement

   puts ("hello");
is a "pure" value of type int.


No, that statement evaluates to a pure value of type int. In the process, it does something else as well that effects the visible behavior of the program.

The C equivalent of

    putStrLn "hello"
is probably actually

    void putsHello(void) {
        puts("hello");
    }
Unfortunately, function definitions aren't something you can really manipulate at runtime in C.


Here is a comment I wrote about the same issue some time ago:

https://news.ycombinator.com/item?id=9085403

Haskell IO is severely misunderstood.


Can you clarify the essential difference between Haskell's and Agda's versions of monadic I/O?

Or are you talking about divergent side-effects like bottom and exceptions?


Thanks for your question. I should have been more careful. I meant Agda used as a prover, not Agda used as a 'conventional' programming language (Indeed in years of usage, I've never executed an Agda program, I've only ever used the type checker). In other words, Agda, the proof calculus for Martin-Loef type theory.

That said, I was talking about effects in general, including non-termination, exceptions, concurrency ...


Is purity dead? Do you consider Idris a theorem prover? It's dependently typed but seems to be designed for writing operational programs rather than proofs.


I'm not familiar enough with Idris to know exactly how state is handled, but yes, Idris is a prover, since it has dependent types.

If you want to verify your programs (and in 99.9% of programs you probably don't as you get it sufficiently correct by more lightweight techniques such as testing and code-reviews), then purity is fine, since you have to track effects explicitly anyway to prove non-trivial results, and doing this in a pure functional way or say with auxiliary variables in a Hoare-logic are kind-of/sort-of notational variants.


If "prover" just means "has dependent types" then that's a much larger class than it sounds like. I write a lot of Scala, which has dependent types, but most of the code I write is just ordinary web services.


I meant dependent types a la Agda, Coq etc that you can use for proving essentially arbitrary properties. Scala and Haskell do not have this.


I don't understand the distinction you're drawing. Idris' type level functions are easier to write than Scala's but they don't seem to be able to do anything that Scala ones can't.


Dependent types aren't only useful in languages that prove theorems, so I think a better qualification for "provers" might be termination checking.


To be clear, Haskell "has dependent types" to various degrees as well.


Purity is not dead. My newish functional language is pure.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: