I saw Joe speak in London a year or so ago. He was a very good speaker. This three laws is a very good reflection of his plain spoken and humorous style. Whether you like Erlang you should take the time to enjoy Joe IMHO :)
Fun to ask these questions of different languages. Here's two I've been using recently:
- C++:
What they got right? RAII, ability to code "close to the metal" if you want or highly abstractly if you don't, templates for powerful code re-use, and a long-term stable ABI on most platforms. The latter is huge-- it means you can write code in C++ and actually ship it.
What they got wrong? Variations in syntax between headers and code files (e.g. ~Destructor() vs. Destructor::~Destructor()), weird template edge case hell that requires 'typename' and 'template' keyword use in unpredictable places, too much symbol re-use (&, <>, asterisk, etc.) that makes code hard to read and parse in a misguided effort to not break 0.000001% of existing code. Pointers are unsafe since you don't know whether something was allocated on the stack or you should delete it.
What's confusing? A lot, unfortunately. :) Templates are the primary source of this.
- JavaScript:
What they got right? It's functional. Really. It's basically a lite weakly-typed Scheme shoehorned into a Java-like syntax. The language is easy to parse and simple enough that JITS can be made that run it fast. Functions are first-class citizens and you can sling them around like any other variable.
What they got wrong? Numeric types are poorly implemented, making the language unsuitable for "serious algorithm" type stuff. There is no canonical way to define and extend objects, resulting in conflicting styles across projects. There is also no builtin way to do sane async code without the "pyramid of doom" (20-level deep indents). Libraries like async or underscore help, but in a language that wants to be async as bad as JS this should be core. Finally, the fact that it's a functional language shoehorned into looking like Java means there are all kinds of confusing syntactic variations that arise when it's used functionally. function() {} vs x = function() {}; -- note the extra semi.
What's confusing? Type promotion, weird rules around true/false evaluation, the == vs === mess, what 'this' means.
The fact that very few people know about them speaks for them being rare and intuitively not as big of a problem in the real world than people make it seem like.
D is syntactically close to C++, but not C++; Walter Bright made some very judicious choices to make parsing unambiguous and fast.
Rust has 4 pointer types, three of which you will use regulary. This design decision is in perfect accordance with the goals of the language, offering a language with safety and performance in mind. It takes a while to get used to, but I don't think that makes the language any less clean.
Not just that, but C++ has one pointer type. Oh, and if you're building a big app, you will probably use and have to learn about shared_ptr, uniq_ptr... and then you're right back to Rust with more typing and less guarantees.
There are good arguments not to use Rust today, but I think the 'it has four pointers' complaint is a cop-out, personally.
Most reasons not to use Rust come down to the implementation (stability of the compiler, speed of the executables, limited standard library), I haven't seen anyone come out and say that something about the language itself is a complete dealbreaker.
I think D has more features than C++ at this point. Also you are expected to heavily rely on the GC (the standard library itself does) which puts D into a whole different league i.e. the Python and Go league. It can never be a replacement for C++ for that reason alone.
I know but you are left with a crippled subset then. D was not designed for it. You can turn off the GC in most GC language implementations I know, that does not make them alternatives to languages designed for no-GC code. Non-GC (i.e. maximal and predictable performance) is first class in C++, it is a red-headed stepchild in D.
>So would you say that GC is the main reason why D did not become very popular as a C++ replacement?
I think so yes. There are strong anti-GC sentiments among the crowd which still swears by C++ and did not move on to C# etc. long ago e.g. AAA-game developers.
D had other problems in the past (standard library conflict, poor implementation quality) and suffers from poor marketing, lack of corporate support, and the lack of a "killer app". I think few people would argue these things aren't important for breaking into the mainstream.
However, yes, I think the GC issue is the biggest one by far. D is a complex, feature-loaded language (IIRC it has templates, compile-time function execution etc. really C++11-grade and more). It naturally does not appeal to people who are attracted by Python, Go, or even C#. It should attract C++ programmers but largely fails at that and I think the primary issue is indeed its GC-based nature.
I don't really agree; just because a language has first-class functions and closures, that doesn't make it a functional language. Sure, you can write functional-style code in Javascript, but you can do that in almost any language, many of which don't have first-class functions and closures.
It's hard to agree or disagree with that. Javascript, by iyself, yes, you're right, but everyone codes with either coffeescript and/or underscore, so it's possible to do most of your work in a functional manner. jquery's selectors can be used in a fairly declarative manner when you do have to manipulate existing DOM elements, so in practice, it's not that bad.
Scheme (and FP in general) is much more than just first-class functions. If that's all that is required for a language to be considered functional, then nearly every modern programming language is a functional language, and the term becomes essentially meaningless. First-class functions are nothing special or unique anymore.
FP is also about frictionless composition of functions, declarative rather than imperative style, use of expressions instead of statements, and very critically, emphasis on statelessness and immutable data.
JavaScript is anything but stateless--it is extremely awkward, if not impossible, to avoid mutation in idiomatic JS code. Imperative statements abound, and everything is a reference, there are really no pure values to be found. Functions can be composed, but the syntax makes it unintuitive and ugly. And though it is probably not a hard requirement for FP, most FP languages at least make an attempt to distinguish pure functions from stateful procedures, e.g. via types in Haskell, or just naming conventions, as with Scheme's `set!`, but JS does not really facilitate this in any way.
Functions and lexical closures are undoubtedly some of "the Good Parts" of JavaScript, but these alone are not sufficient for functional programming. JS is fundamentally an imperative, OO language which happens to have a very light dusting of FP flavor on top.
Scheme (and FP in general) is much more than just first-class functions. If that's all that is required for a language to be considered functional, then nearly every modern programming language is a functional language, and the term becomes essentially meaningless. First-class functions are nothing special or unique anymore.
If you'd like to express this simply, I've been trying to get people to explain functional programming as programming where the idiomatic method of computation uses the lambda calculus as a base model.
When it comes to mainstream programming languages it's often easy to "see" the lambda calculus or a Turing machine behind the computation.
If you grok the lambda calculus, this is indeed a simple explanation. In my experience, most people who don't know what functional programming is also don't understand what lambda calculus is. In that case, all you've done is substituted one unknown bit of vocabulary for another.
I've also noticed that when I use unknown vocabulary in my explanations, it sometimes intimidates people away from asking more questions. E.g. "What is <some term I really don't understand>?" "Oh, it's just <another term, that the person you're asking seems to expect you to understand, but you really don't>". So I would avoid the term "lambda calculus" for the uninitiated.
I seem to have pretty good luck explaining what a pure function is (no side effects, same input always produces same output, referential transparency[1]) and going from there.
[1] Which, in turn, requires its own mini-explanation.
Possibly even more accessible: High school math. In a math exercise, when we state
let A = 2 + 3π
Then we can be dammed sure that for the rest of the exercise, we can replace "A" by "(2 + 3π)" (and vice-versa) without ever introducing an error. Likewise, functions don't change A's value behind your back no matter what. Something in, something out, period.
If I may, I'd like to log a difference of my intent with the word simple, as opposed to the word easy.
Simple is conceptually clean, while easy may be accessible to those familiar with related concepts.
My description was, I hope, simple: it has very high fidelity and is not prone to many of the misinterpretations of the other explanations. What it may not be is easy. One may indeed require further explanation to really understand the relationship I'm proposing. Sometimes, though, there is no shortcut to knowledge.
Moreover, as far as Lisp dialects go, homoiconicity is a big part of their identity and a major source of their power. It's an orthogonal concept to functional programming in general, just look at e.g. Haskell, but that's one of the first things I think of when I hear the "JS is just an ugly Scheme!" argument.
Very true. Lisp's syntax is as much a component of its distinctive identity as its semantics. A language semantically identical to Scheme but without the homoiconic syntax falls far short of what Scheme is "about", despite what Doug Crockford might say.
javascript has json though, which doesnt make it homoiconic, but it allows you to mimic homoiconicity in a lot of cases. When I write in javascript, I tend to store my data in json and and write code that is generic, with much abstraction that has a small foot print to manipulate the json.
That is scheme-like. That is psuedo-homoiconic.
(If anyone in the know wants to add or refute this, I would love to know whether my non-cs intuition is correct here.)
(also it is still a very mutable language, even with coffeescript/underscore)
Homoiconicity is when the code of a language is represented in a structure that is primitive to the language. It doesn't have to do with how data is stored, it has to do with how the program text itself is stored.
The advantage of homoiconicity is that the language itself is manipulable as a basic type in the language. In Scheme, this means that it's easy to generate, analyze, and modify Scheme code using simple procedures in Scheme. e.g. I could (although I wouldn't) generate a scheme expression to compute an arbitrary fibonacci number with[1]
Notice that I'm not just pushing strings together to write code. I'm actually manipulating data structures in the language to produce and modify code. That is homoiconicity.
> If that's all that is required for a language to be considered functional, then nearly every modern programming language is a functional language, and the term becomes essentially meaningless. First-class functions are nothing special or unique anymore.
While I don't dispute the conclusion, this particular justification is worthless. You don't restrict the application of a term based on whether or not it'll become worthless due to over-application. IF functional programming is defined as "a language with first-class functions", then if "nearly every modern Plang has first-class functions" then nearly every modern Plang is functional.
That first IF isn't true, as the rest of your comment demonstrates, but term over-saturation is not why Javascript isn't functional.
I prefer the term "semi-functional" for imperative languages that provide some support for functional programming.
It's a loose category, but I don't think it's completely meaningless. At a minimum it requires anonymous function expressions and garbage collection. That rules out C++ and (until recently) Java.
Some semi-functional languages have better support for functional programming than others, of course. JavaScript qualifies as semi-functional, but I agree that its syntax is not particularly conducive to the functional style. ML is a good example of a semi-functional language with very good support for the functional style.
I was going to argue with you about your comments about Javascript and callback hell, because a while back I figured out a nice, efficient pattern that pretty much removed it as an issue for me. But then I remembered reading somewhere that patterns are a code smell for a problem in a language.
So yes, you're absolutely right that callbacks would have benefited from in-language support, especially considering the mono-threaded nature of JS. Interestingly enough, my solution was very similar to the prototype chain of JS, which makes me think that a more generalised prototype chain concept, allowing multiple chains for an object, would have made a much stronger language.
I think what's interesting is looking at the constraints around which a language evolves. A lot of the deep nastiness of C++ is related to C compatibility; but C++ without C (++?) wouldn't have had even the awful tool support that it did; it would never, ever have taken off to the same degree. I'm not a C++ jock -- when I was getting started (1992), the pain/pleasure threshold was very different than it is today.
Functional programming is a style. You can do it in nearly any PL, but some make it easier than others. JS's prototype object system is there for a reason, and it ain't FP.
The title makes no sense. The "three laws" are observations about how people respond to a programming language design. They do not make any sense when applied to a design or the design process.
It certainly was not the article I was hoping for, which would say things like:
-- Define and publish a formal syntax for your language. Do not get too clever with the syntax -- if you can't express some feature cleanly in BNF, don't do it that way.
-- Equality must be an equivalence relation: symmetric, reflexive, and transitive. If you don't know what that means, you have no business designing a language. [Equality is not transitive in either JavaScript or PHP.]
-- Comparison operators ('<' etc.) must be transitive. [Violated in PHP.]
-- Transformations that people expect to work must work. For example, 'if (p) a else b' must be equivalent in all cases to 'if (!p) b else a' [Visual Basic violates this].
-- The scope rules must be clear and straightforward, and not cause maintainability nightmares. [JavaScript fails.]
I can't say I agree with his analysis of closures. My understanding is that closures close around variable scope. If the variable changes (something that can't happen in a pure functional language) then the closure sees the change.
Elixir closes around the value instead of the variable. While that may be more convenient/obvious in some places, it removes functionality in others. Specifically the old adage about closure's being a poor man's object and vice versa isn't true if the closure doesn't close around the variables themselves.
What is the semantic difference when everything is immutable? I'm not sure if you are right that it closes over the value rather than a reference, and I can't think of any way to test the idea which suggests to me it is not meaningful.
> the old adage about closure's being a poor man's object and vice versa isn't true if the closure doesn't close around the variables themselves
Sure it is. Oleg Kiselyov wrote a purely functional OO system in Scheme[1], which uses closures for member variables and seems to do just fine without mutation. Of course, it does rely a bit on dynamic typing.
47 comments
[ 2.7 ms ] story [ 88.0 ms ] thread- C++:
What they got right? RAII, ability to code "close to the metal" if you want or highly abstractly if you don't, templates for powerful code re-use, and a long-term stable ABI on most platforms. The latter is huge-- it means you can write code in C++ and actually ship it.
What they got wrong? Variations in syntax between headers and code files (e.g. ~Destructor() vs. Destructor::~Destructor()), weird template edge case hell that requires 'typename' and 'template' keyword use in unpredictable places, too much symbol re-use (&, <>, asterisk, etc.) that makes code hard to read and parse in a misguided effort to not break 0.000001% of existing code. Pointers are unsafe since you don't know whether something was allocated on the stack or you should delete it.
What's confusing? A lot, unfortunately. :) Templates are the primary source of this.
- JavaScript:
What they got right? It's functional. Really. It's basically a lite weakly-typed Scheme shoehorned into a Java-like syntax. The language is easy to parse and simple enough that JITS can be made that run it fast. Functions are first-class citizens and you can sling them around like any other variable.
What they got wrong? Numeric types are poorly implemented, making the language unsuitable for "serious algorithm" type stuff. There is no canonical way to define and extend objects, resulting in conflicting styles across projects. There is also no builtin way to do sane async code without the "pyramid of doom" (20-level deep indents). Libraries like async or underscore help, but in a language that wants to be async as bad as JS this should be core. Finally, the fact that it's a functional language shoehorned into looking like Java means there are all kinds of confusing syntactic variations that arise when it's used functionally. function() {} vs x = function() {}; -- note the extra semi.
What's confusing? Type promotion, weird rules around true/false evaluation, the == vs === mess, what 'this' means.
B. Stroustrup
http://www.stroustrup.com/masterminds_chapter_1.pdf
B. Stroustrup
D or Rust?
D and Rust can hardly be considered small and clean languages. D is syntactically close to C++. Rust has 4 pointer types ( http://static.rust-lang.org/doc/rust.html#pointer-types ).
Rust has 4 pointer types, three of which you will use regulary. This design decision is in perfect accordance with the goals of the language, offering a language with safety and performance in mind. It takes a while to get used to, but I don't think that makes the language any less clean.
There are good arguments not to use Rust today, but I think the 'it has four pointers' complaint is a cop-out, personally.
I'm not a C++ guy at all, but from my cursory readings D seems better than C++ in just about every possible way, unless you really DON'T want GC.
I think so yes. There are strong anti-GC sentiments among the crowd which still swears by C++ and did not move on to C# etc. long ago e.g. AAA-game developers.
D had other problems in the past (standard library conflict, poor implementation quality) and suffers from poor marketing, lack of corporate support, and the lack of a "killer app". I think few people would argue these things aren't important for breaking into the mainstream.
However, yes, I think the GC issue is the biggest one by far. D is a complex, feature-loaded language (IIRC it has templates, compile-time function execution etc. really C++11-grade and more). It naturally does not appeal to people who are attracted by Python, Go, or even C#. It should attract C++ programmers but largely fails at that and I think the primary issue is indeed its GC-based nature.
I don't really agree; just because a language has first-class functions and closures, that doesn't make it a functional language. Sure, you can write functional-style code in Javascript, but you can do that in almost any language, many of which don't have first-class functions and closures.
It's really not, it's a language that encourages unrestricted mutable state which is very not functional.
FP is also about frictionless composition of functions, declarative rather than imperative style, use of expressions instead of statements, and very critically, emphasis on statelessness and immutable data.
JavaScript is anything but stateless--it is extremely awkward, if not impossible, to avoid mutation in idiomatic JS code. Imperative statements abound, and everything is a reference, there are really no pure values to be found. Functions can be composed, but the syntax makes it unintuitive and ugly. And though it is probably not a hard requirement for FP, most FP languages at least make an attempt to distinguish pure functions from stateful procedures, e.g. via types in Haskell, or just naming conventions, as with Scheme's `set!`, but JS does not really facilitate this in any way.
Functions and lexical closures are undoubtedly some of "the Good Parts" of JavaScript, but these alone are not sufficient for functional programming. JS is fundamentally an imperative, OO language which happens to have a very light dusting of FP flavor on top.
If you'd like to express this simply, I've been trying to get people to explain functional programming as programming where the idiomatic method of computation uses the lambda calculus as a base model.
When it comes to mainstream programming languages it's often easy to "see" the lambda calculus or a Turing machine behind the computation.
I've also noticed that when I use unknown vocabulary in my explanations, it sometimes intimidates people away from asking more questions. E.g. "What is <some term I really don't understand>?" "Oh, it's just <another term, that the person you're asking seems to expect you to understand, but you really don't>". So I would avoid the term "lambda calculus" for the uninitiated.
I seem to have pretty good luck explaining what a pure function is (no side effects, same input always produces same output, referential transparency[1]) and going from there.
[1] Which, in turn, requires its own mini-explanation.
From there, you can explain function composition, how operators are actually binary functions, even types. We can go really far with just high school math: http://loup-vaillant.fr/tutorials/from-imperative-to-functio...
Simple is conceptually clean, while easy may be accessible to those familiar with related concepts.
My description was, I hope, simple: it has very high fidelity and is not prone to many of the misinterpretations of the other explanations. What it may not be is easy. One may indeed require further explanation to really understand the relationship I'm proposing. Sometimes, though, there is no shortcut to knowledge.
That is scheme-like. That is psuedo-homoiconic.
(If anyone in the know wants to add or refute this, I would love to know whether my non-cs intuition is correct here.)
(also it is still a very mutable language, even with coffeescript/underscore)
The advantage of homoiconicity is that the language itself is manipulable as a basic type in the language. In Scheme, this means that it's easy to generate, analyze, and modify Scheme code using simple procedures in Scheme. e.g. I could (although I wouldn't) generate a scheme expression to compute an arbitrary fibonacci number with[1]
Notice that I'm not just pushing strings together to write code. I'm actually manipulating data structures in the language to produce and modify code. That is homoiconicity.However unlike JSON, Rebol is more than just a data interchange format because it's like Lisp in being a full homoiconic programming language.
For eg: Here are andolanra's excellent Scheme/Lisp examples in Rebol:
And it's this that makes the difference between JSON+Javascript compared to something like Rebol/Scheme/Lisp.While I don't dispute the conclusion, this particular justification is worthless. You don't restrict the application of a term based on whether or not it'll become worthless due to over-application. IF functional programming is defined as "a language with first-class functions", then if "nearly every modern Plang has first-class functions" then nearly every modern Plang is functional.
That first IF isn't true, as the rest of your comment demonstrates, but term over-saturation is not why Javascript isn't functional.
It's a loose category, but I don't think it's completely meaningless. At a minimum it requires anonymous function expressions and garbage collection. That rules out C++ and (until recently) Java.
Some semi-functional languages have better support for functional programming than others, of course. JavaScript qualifies as semi-functional, but I agree that its syntax is not particularly conducive to the functional style. ML is a good example of a semi-functional language with very good support for the functional style.
So yes, you're absolutely right that callbacks would have benefited from in-language support, especially considering the mono-threaded nature of JS. Interestingly enough, my solution was very similar to the prototype chain of JS, which makes me think that a more generalised prototype chain concept, allowing multiple chains for an object, would have made a much stronger language.
Functional programming is a style. You can do it in nearly any PL, but some make it easier than others. JS's prototype object system is there for a reason, and it ain't FP.
-- Define and publish a formal syntax for your language. Do not get too clever with the syntax -- if you can't express some feature cleanly in BNF, don't do it that way.
-- Equality must be an equivalence relation: symmetric, reflexive, and transitive. If you don't know what that means, you have no business designing a language. [Equality is not transitive in either JavaScript or PHP.]
-- Comparison operators ('<' etc.) must be transitive. [Violated in PHP.]
-- Transformations that people expect to work must work. For example, 'if (p) a else b' must be equivalent in all cases to 'if (!p) b else a' [Visual Basic violates this].
-- The scope rules must be clear and straightforward, and not cause maintainability nightmares. [JavaScript fails.]
I can't say I agree with his analysis of closures. My understanding is that closures close around variable scope. If the variable changes (something that can't happen in a pure functional language) then the closure sees the change.
Elixir closes around the value instead of the variable. While that may be more convenient/obvious in some places, it removes functionality in others. Specifically the old adage about closure's being a poor man's object and vice versa isn't true if the closure doesn't close around the variables themselves.
Sure it is. Oleg Kiselyov wrote a purely functional OO system in Scheme[1], which uses closures for member variables and seems to do just fine without mutation. Of course, it does rely a bit on dynamic typing.
[1] http://okmij.org/ftp/Scheme/#pure-oo