Less is more: language features
blog.ploeh.dk
blog.ploeh.dk
The paper gives laziness as an example. It’s made possible by idempotency. Which is made possible by the removal (or careful management) of side-effects. Which then allows certain otherwise impossible ways to express infinite data structures.
just my $0.0200000000001 on that topic.
just my 0.00603698 KWD Kuwaiti Dinars on that topic
On the other hand I'm decidedly old school and get very uncomfortable around languages that at least don't separate floats from ints.
The way "goto" is used in modern C (named breaks and defined function escape routes) is perfectly acceptable. Newer languages include these features in the language, in much the same way that C encoded structured "goto"s as "while" loops and "if" statements.
Dijkstra was complaining about unstructured programming compared to structured programming.
Also a lot of the things he lists are not at all as clearly harmful as he makes them out to be.
GOTO: Used in some of the cleanest code bases. For example the lua programming language, redis db, and linux kernel OS. Yes the use is limited, but it's there not because of mistakes but rather because it is sometimes the most clean way.
Numbers: I don't understand this one. In the very least having different types of numbers seems to be a necessity for anything performance oriented such as graphics. JS not having them (they seem to have been added for WebGL as an extension, no?) is not a good reason for all new languages to do the same.
Mutations: I am personally of the view that mutation often simplifies the informal reasoning complexity of a program. There's a reason functional programming hasn't caught on and it likely never will. A hybrid approach of having both immutability and mutability like Rust seems most sensible.
Reference equality (Referential transparency?): If you have immutability do you not get this for free? How can you be sure about getting rid of mutations but not be sure you want objects to be equal based on data?
Each language has it's own quirks that make it special and that's part of the reason programming is fun. There's never a single right way.
Please explain?
Mutations can cause side-effects but don't have to. You seem to think that limiting program behavior _necessarily_ alleviates informal reasoning, but that's not the case. Your argument is also highly hyperbolic in terms of testing. A properly designed system with immutability needs to be tested no less than properly designed system with mutability.
Mutations ARE side effects. Consider two situations with a function and a mutable variable:
First: Your function depends on a value that can mutate. Now you have a hidden parameter to your function. You have to test the function under all reasonable conditions by which the value can change. The function is not isolated from the rest of your program any more, and you have to check who has access to that value. That's a lot to keep track of.
Second: Your function mutates a value. Now you have to keep track of everyone who references that value, and everywhere your function is invoked.
In both situations, the number of scenarios you must test is greatly expanded compared to when you have a 'pure' function that does not operate on mutable memory.
Referential transparency means that for a fixed input value you can replace a function invocation with a static value. It's just a mapping that is always the same, whether it's computationally expensive or not. That means that you can always expect the same behavior when you compose that function. But even more important, you can assert that your function is equal to something else -- always, under all conditions.
This means that you can make a series of equational substitutions. And THAT means that you can use equational reasoning to prove things about your function.
And if your program is just a bunch of functions that are composed together, well then you can prove things about your program, too.
Programs are getting really complex. Really, really, really complex. Having the power to make more safe assertions about your programs is becoming more important. Being able to prove things about your programs using well known proving techniques, such as those borrowed from more formal maths like abstract algebra, is really useful.
You still need to test, but what you need to test becomes of much narrower scope and much less cumbersome. This is why functional programming is indeed becoming more popular.
About your two situations, I am guessing you mean that the functions rely on state not being passed in, but rather on state referenced from the outer scope? I agree that that complicates both reasoning and testing, and so it should be avoided. In C/C++ this type of design (i.e. relying on static scope state) is practically always avoided as there's rarely any need for it.
If instead you mean that mutable references are being passed in as parameters then logic wise isn't this similar to assignment/binding in a functional language as long as you avoid propagating the side effect to other functions?
I am personally a great fan of immutability, just not functional languages. I can see the justification for limiting the tools at your disposable to guarantee certain things, it's just that in this case I think they are not sufficient.
---
For those downvoting, gee thanks. I see how trying to have a discussion is hurting your feelings.
If your programming language takes the power to mutate away from you then you have even less to worry about: You don't have to rely on self-discipline to not write a bad program, the compiler tells you it's not good.
Even in really small functions, with tiny scopes, I think reasoning about mutation takes a tidy mental toll. You can express about almost everything you need (well, I don't really know you or what you are programming) with maps and folds and recursion.
In object oriented languages the traditional belief has been that encapsulation with privacy modifiers and getters and setters is sufficient to make well behaved programs. I don't think that goes far enough. I think that to reason about programs effectively you need some sort of immutability guarantee.
Assume that the reader also knows how substitutability makes some variants of FRP much, much harder to both implement and reason about, has Haskell code in production that only two people at the company claim to understand, knows about the Principle of Least Expressiveness": "When programming a component, the right computation model for the component is the least expressive model that results in a natural program." And also knows about how to interpret it, namely: "Note that it doesn't say to always use the least possible expressive model; that will result in unnatural (awkward, hard to write, hard to read) programs in at least some cases" (http://c2.com/cgi/wiki?PrincipleOfLeastPower)
Assume that the person has seen people be very productive with simple dataflow tools such as Quartz Composer, and then hit a wall because that simpler programming model doesn't just have a low threshold, it also has a low ceiling. And has read in a report on 15+ years of constraint programming that students consistently found it difficult to think ("reason") in a functional style. (see http://blog.metaobject.com/2014/03/the-siren-call-of-kvo-and...)
So.
What evidence do you have for your assertions? Not arguments or logical inferences from your assumptions, but actual evidence?
You can reason about that intuitively even, without trying to prove anything. The less stuff I need to test, the less stuff that my brain needs to think about.
I'm not quite sure what kind of evidence you're looking for. I'm not quite sure that you calling all of the evidence I provided as handwaving is valid, either!
But I can give you all kinds of anecdotal evidence if you like? I gave, I think, two pretty concrete but general examples.
On the other hand, there was a paper just recently (can't currently find it), with empirical evidence that surprisingly few tests were required to weed out large swathes of potential misbehavior, much fewer than could reasonably be expected.
Or there is Robert Smallshire's data mining of github errors that showed type errors in dynamically typed languages as being exceedingly rare (1-2%), when everyone just knows that they must be common and problematic.
I wouldn't really call my assertions unproven. Speaking of referential transparency and equational reasoning is kind of the core of a lot of computer science.
I'm not really sure what kind of evidence you are asking for.
I will say that I think some programming languages are more interesting than others, and some are a more productive, better use of time than others.
I'd actually argue that most of the software that runs our lives today is impossible to prove correct and yet seems to be doing quite alright overall.
You're setting up a similar false dichotomy as people who say that programs that ship without automated tests are worthless and broken.
1. Mutability makes your program harder to reason about.
2. There are better ways to spend your time than writing tests for conditions that could not exist with pure functions.
You could totally extrapolate a ton of statements from those two statements though. But I'm not doing that now, and not here.
How so?
Functional languages may make the former easier (though from the proofs we did in University, I don't really see it), but they make the latter much harder in many cases.
That's a red herring and a scare-story that FPers tell each other. They can't happen "anywhere". They happen where you tell them to happen.
Do you have any evidence that actual reasoning is simpler?
"At the end you usually get a tight loop that is easy to follow. It is also much more imperative/operational than before, which may bug Haskell-style people." -- Jonathan Blow
https://twitter.com/jonathan_blow/status/588007366618062848
"I wanted to see how hard it was to implement this sort of thing in Go, with as nice an API as I could manage. It wasn't hard.
Having written it a couple of years ago, I haven't had occasion to use it once. Instead, I just use "for" loops.
You shouldn't use it either." -- Rob Pike
https://github.com/robpike/filter
This matches my experience pretty well. When I first created Higher Order Messaging, I was also really into creating chained higher order ops. After a little while, I noticed that a small for-loop was actually more readable than the really cool filter-chains I had created. Less cool, but more readable, comprehensible, easier to reason about, not least because of named intermediate values, something that tends to get lost in point-free style (and trust me, having been a heavy user + implementor of Postscript, I know a little about point-free style).
I would call referential transparency, which gives rise to equational reasoning, very strong evidence that reasoning becomes simpler.
In fact, most proofs I have seen don't particularly help me reason about the code in question. On the other hand, a simple imperative execution model does help me reason about the code.
Being able to reason about things equationally makes my code easier to reason about. Having a type system and a compiler assist me makes my code easier to reason about. Having fewer variants makes my code easier to reason about. Not needing to know the order in which functions have been invoked to know their return values makes my code easier to reason about.
However, I will make the statement -- and this is indeed conflating! -- If I can prove something about my code, then it is easier to reason about. The proof can be simple or complex.
Something to think about. Maybe.
All of these things are restrictions, as well as features. The idea that "features == more power" is not accurate... features and power have a complicated relationship, the moreso since there's more than one useful definition of power, but they are almost no matter what neither strictly in opposition, nor strictly working together. But pretty much every useful high level feature is some restriction on what code sequences you can cause to run with the language, together with something built on top of that restriction that would be either harder or less safe without the restriction.
That said, gotos are still much less structured than tail calls. You can make some much more spaghetti-like control flow patterns and you are also encouraged to use shared mutable variables instead of passing parameters by value.
And if you have closures you generally have a garbage collector of some sort or another. But anyways, I don't think closures are more "general" than pointers. Closures form a proper subset of memory access vectors (to use a fairly simple definition of "pointer"), so there are things pointers can do that closures can't. But for that matter pointers themselves are an abstraction for memory addresses that the Real Programmers of Yore used before compilers came in and made us all wimps...
* Golang does away with inheritance and exceptions.
* Rust does away with mutability, or at least makes it explicit.
I wonder if a convenient language with all of these taken out could exist. I'm skeptical about a single number-type.
Also, wouldn't reference equality simply be gone if you take away pointers?
Also, his point about interfaces is pretty narrow, not every interface's manipulation can be characterized by one or one set of manipulations. I think Golang is a fantastic example of a language that forces a role-based interface pattern.
You don't need to expose a pointer datatype to offer a reference equality check operation. Java, JavaScript, C#, etc, all do reference equality by default for user-defined classes.
Also, Go has exceptions as described here. Panic and recover allow the same semantic power, since every exception-based program can be easily transformed into the equivalent program with Go panic/recover, and vice-versa.
Meh,regarding exceptions you still have errors that you bubble up by returning them. It's hardly different from exceptions.
On the other hand, Go went way too minimal with its type system so much that, you can opt out with inteface{} because it's not expressive enough. I would prefer no interface {} , no reflection ,but to allow types as parameters and value.Which they are not in go.
finally, go has GOTO ... I would have gotten rid of that at first place.
It's completely different: you have to manually bubble them if you want them to bubble. You can perfectly not deal with them.
Yes, the initial reaction to seeing an err pop up is to return it. No, you don't have to return it.
Also, in the summary list of features to be removed: in that case the diagrams of "Languages that disallow X" should all be mutually intersected, and I suspect that the resulting intersection would exclude too many valid programs (i.e., you'd end up with the graph "languages taking away the wrong features).
More generally, I'm still waiting for a massively successful piece of software made in a language that would involve no mutation, no null pointers, only one numeric type, etc. Even John Carmack has not yet delivered a revolutionary game engine programmed in Haskell.
One can program with mutations in Haskell, and all the side-effects can be kept "sandboxed" at the same time; also I believe the side-effectful parts of a Haskell program can be compiled (in principle) in a way that results in very efficient code, as fast as e.g. C++, though I'm not sure how well current compilers are doing in this respect.
So in a way, Haskell (as a language) offers the best of both worlds.
Sure, but maybe the reason is the people that choose to use F# and Caml like languages are a bit smarter that the regular coder. Or maybe F# projects have a different nature and people don't write "enterprise applications" in F#.
Does he want to forbid factoring? His feature looks more like a deficit of the linker to me.
Not sure about Cyclic dependencies and I don't think it have Reflection unless I'm wrong.