You can't dig upwards
evanmiller.org
evanmiller.org
At the risk of getting myself shivved:
The mistake here is that Python is a bad example. There isn't much beyond table stakes to learn, conceptually, from Python.
There are concepts that C is uniquely good at engaging: the memory hierarchy, object lifecycles and memory allocation, locality, implementation of indirection.
And there are concepts that Lisp is uniquely good at engaging. Metaprogramming, recursion, the bag of ideas involving closures, lambdas, and their relationship with encapsulation and abstraction.
But the most popular languages --- Python, Ruby, Java, PHP --- don't offer much more than productivity.
I think you need C. I think you need a serious high level language --- a Lisp, an ML, Haskell --- as much as you need C. And then I think you need a Python or a Ruby, but if you have the previous 2, you get Python and Ruby for free anyways.
Simply saying C is the "best" way also misses that point that different people are motivated to learn in different ways. For many, ramping up quickly in Python and plugging something into a Twitter library would show them that they can actually do something interesting right now, which is enough to keep them coming back for more. Others are driven by understanding how things work, and would be happy to crack open a core dump to figure out what happened.
But there's nothing you engage with in Python that you can't get by learning C, or Lisp.
Like I said: Python is just a bad example.
What worked for me was early exposure each of: a specific teaching language (somewhat like Eiffel, within a simplified environment), C/C++ and Scheme. They each taught me very different things and I'm glad they were all offered in my undergrad, although I think they should offer Scheme earlier (IIRC it was part of an optional third year course, which also touched on e.g. Prolog).
I see this repeatedly in our internship program, where people often ask after we "promote" them to other language work "Why can't XYZ look as clean and simple as Python?". I do think that's a lesson worth learning, and it's a little hard to learn except by osmosis. If I just tell someone "empirically, syntax matters a lot more than you might think in doing real work" it's easy to dismiss as understood-but-not-important. Jonathan Blow has a bit in one of his videos about "understanding" vs "really viscerally understanding", and I think this is one area where the latter simply can't be achieved without direct experience.
Furthermore I'd wager that Go and Node have nothing in the realm of concurrency that Haskell doesn't have. If I'm wrong, I look forward to finding out ;)
One parallelizing a sudoku solver and the other making a reminder program concurrent. Both are from Parallel and Concurrent Programming In Haskell[0].
Sequential version[1]:
import Sudoku
import Control.Exception
import System.Environment
import Data.Maybe
main :: IO ()
main = do
[f] <- getArgs
file <- readFile f
let puzzles = lines file
solutions = map solve puzzles
print (length (filter isJust solutions))
Parallel version[2]: import Sudoku
import Control.Exception
import System.Environment
import Data.Maybe
import Control.Parallel.Strategies (parMap) -- line that changed
main :: IO ()
main = do
[f] <- getArgs
file <- readFile f
let puzzles = lines file
solutions = runEval (parMap solve puzzles) -- line that changed
print (length (filter isJust solutions))
Here's an example making a small reminder program concurrent:Sequential[3]:
import Control.Concurrent
import Text.Printf
import Control.Monad
main =
forever $ do
s <- getLine
forkIO $ setReminder s
setReminder :: String -> IO ()
setReminder s = do
let t = read s :: Int
printf "Ok, I'll remind you in %d seconds\n" t
threadDelay (10^6 * t)
printf "%d seconds is up! BING!\BEL\n" t
Concurrent[4]: import Control.Concurrent
import Text.Printf
import Control.Monad
main = loop
where
loop = do
s <- getLine
if s == "exit"
then return ()
else do forkIO $ setReminder s
loop
setReminder :: String -> IO ()
setReminder s = do
let t = read s :: Int
printf "Ok, I'll remind you in %d seconds\n" t
threadDelay (10^6 * t)
printf "%d seconds is up! BING!\BEL\n" t
0: http://chimera.labs.oreilly.com/books/1230000000929/index.ht...1: http://chimera.labs.oreilly.com/books/1230000000929/ch02.htm...
2: http://chimera.labs.oreilly.com/books/1230000000929/ch02.htm...
3: http://chimera.labs.oreilly.com/books/1230000000929/ch07.htm...
4: http://chimera.labs.oreilly.com/books/1230000000929/ch07.htm...
Knowledge does not need to be directly applicable to be immensely valuable.
- 1st problem: Functional Programming is not "programming", since in "pure" functions you do not have algorithms and "programming" seems to be defined as making "algorithms".
- I believe it should be called "functional composition" instead of "functional programming".
- 2nd: Monads. "Monads" seems to be the way to introduce "impurity" into Haskell, so, if you need an algorithm, you enter the "monad" space.
Just to clarify, in Haskell there is no issue with algorithms being contained inside functions.
Here's a naive implementation of quicksort:
qsort [] = []
qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
where
smaller = [a | a <- xs, a <= x]
larger = [b | b <- xs, b > x]
Nothing about this algorithm requires side-effects, it doesn't mutate any state, it doesn't make use of counters. You can implement it anywhere inside of a Haskell program.People may charitably point out that this is a pretty bad naive implementation, because it doesn't just mutate values in the list up and down, which is inefficient. People uncharitably will take issue with even calling it quicksort, as some people believe that the property of just mutating values in the list is an integral part of it being quicksort. I'm not smart enough to have an opinion about whether or not it's a "true" quicksort.
But the point is that you can absolutely have algorithms in Haskell that are not restricted to only run in a Monad.
Monads exist in Haskell as a way to reason about side-effects. Whether or not an algorithm (or any function) needs to be executed inside a monad is a function of its use of side-effects, it's not fundamental.
I do not "see" any clear algorithm in the code (no step-by-step procedure). I see recursion only.
¿Can you explain in words what this code is doing?
¿Can you implement this without using recursion?
qsort [] = []
This means that if you hand the qsort function an empty list, you get an empty list ([] is the empty list in Haskell). qsort (x:xs) = qsort smaller ++ [x] ++ qsort larger
Ok, so this means that we are making a list out of three parts: (qsort smaller, x, and qsort larger). The '++' operator is just concatenation. where
smaller = [a | a <- xs, a <= x]
larger = [b | b <- xs, b > x]
Here we're defining what "smaller" and "larger" are. They're list comprehensions. "Smaller" returns a list that is made up of pulling out all the numbers that are less than or equal to x. "Larger" pulls out all the numbers that are greater than x. Then it does it again.Using that example list above ([3, 5, 1, 4, 2]), here's if we did it by hand:
qsort [3, 5, 1, 4, 2]
= {applying qsort}
qsort [1, 2] ++ [3] ++ [5, 4]
= {applying qsort}
(qsort [] ++ [1] ++ qsort [2]) ++ [3] ++ (qsort [4] ++ [5] ++ qsort[])
= {applying qsort}
([] ++ [1] ++ [2]) ++ [3] ++ ([4] ++ [5] ++ [])
= {applying ++}
[1,2] ++ [3] ++ [4,5]
= {applying ++}
[1,2,3,4,5]
That "step-by-step procedure" is an algorithm.Is it that there are no loops that you don't see how it's an algorithm? It is definitely true that some algorithms are very obvious to implement using loops (quicksort is actually one such algorithm), and can in fact be a pain in the neck to implement in some functional languages.
Also, quicksort is a pretty bad example of an pure algorithm, since it loses a lot of its performance benefits when not done in place.
I went over the issues with this version of quicksort in the original comment, but I still think it was a good example because most people are familiar with quicksort, and I was trying to address the original posters claim that algorithms in Haskell could only be used in Monads.
Also, quicksort is one of the few I can do from memory.
Now I can see the mathematical elegance of the haskell code.
An explicit algorithm of the same (pseudo)code (using a mutable list) could be:
function qsort(list)
if list is [], return []
let x = extract_first_element(list) // car
let smaller = [a | a <- list, a <= x]
let larger = [b | b <- list, b > x]
return qsort(smaller) ++ x ++ qsort(larger)
And by writing this pseudo-code, I can see that in the haskell code, "smaller" and "larger" are clearly parallelizable, while in the "imperative"-"explicit algorithm" pseudo-code, the compiler cannot do such optimization...And then, I'm enlightened. Thank you.
You could make the argument that OOP is not a fundamental concept of programming, but you could say the same for monads. Even if it's just a productivity tool or, as pg argues, a substitute for genuinely intelligent programmers, it's still something. It's certainly something that employers expect you to know.
My first serious language was z80 assembly, followed by C, and I have a voice in my head that, much like the author, says "of course someone who's learned assembly/C first will be a better programmer". But we need to fight back those instincts, because they are not based on data. Sure, they seem logical, and we can make eloquent parallels with stick shift driving, and we can all remember that one programmer who started as a Python programmer and totally sucked, so of course learning Python first doesn't make great programmers - but at the end of the day, we need data. It's all pseudo-science otherwise, and it's not any better reasoning than pop psychology.
It's kind of ironic how hackers, usually extremely data driven people, tend to fall into these fallacies just as easily as others as soon as it stops pertaining to engineering. We wouldn't want to debug a program or assess its performance without any hard evidence, so why do we do that when it comes to thinking about other things? Some of pg's essays come to mind, e.g. the one about smart kids being unpopular and miserable in high school [0]. He might be right, he might be wrong - I don't know. But I do know that building an entire social theory just based on your hunches and own subjective experience is not scientific. It's very tempting to want to do so - we're very logical people, and in many cases in our jobs we can derive objective truth just from the power of deductive reasoning - but we need to fight those instincts when it comes to reasoning about fuzzier systems, like society or other human beings.
The idea exposed by the author is mentally pleasing - many of us are tempted to agree because it feels right, and the author's reasoning seems right. But there are no correlations between mentally pleasing, elegant ideas and their correctness. It was once mentally pleasing to think that the Sun went around the Earth, or that electricity was a fluid.
EDIT: as for the claim that stick shifts make for better drivers, then why do countries with 99.9% of cars having manual transmissions (e.g. France) have comparable, or worse, accident rates per kilometer driven per person compared to the US? The fact that this is the case should at least cause one to pause rather than blindly assert "stick shifts make better drivers".
For every anecdote of a Python developer who doesn't have the slightest idea of memory management, i can bring out an anecdote about a C developer who learned Perl and wrote C in it because he didn't trust hashes and had no idea of how to capitalize on high level algorithms.
It's also important that programming is still very young. The entirety of the human race has only been programming for about 3 generations, and only now are the first programmers beginning to die of old age. It will be a long time before we actually have enough data on what works and what doesn't.
If you really care about cars, you should learn a stick shift. If you really care about programming and computing, you should learn C. But just because you learn an automatic first, to ease you into driving, doesn't necessarily mean you're any less likely to go and learn to drive a stick. If you're really interested in using and understanding cars, you'll realize automatic is not the end-all-be-all. The same is true for programming.
>It's also important that programming is still very young. The entirety of the human race has only been programming for about 3 generations, and only now are the first programmers beginning to die of old age.
I often think about this too. What languages will be the gold standards in 30 or 50 years from now? Or 100? Will people view Python and Ruby as many people view COBOL and Pascal today?
It's hard for me to believe that they will view Python and Ruby as charitably as we view COBOL and Pascal. Cobol is only 32 years older than Python (1959/1991) and Pascal only 25 years older than Ruby (1970/1995).
I imagine the gold standard programming language for the 2040s is either a twinkle in someone's eye or just invented.
Maybe in programming, but I think there are plenty of areas wherein this "manual transmission" learning curve should be practiced.
I hate to stomp all over this guys grave, but that air france pilot who responded to the STALL STALL STALL blaring in his face for two minutes by pulling up on the yoke as hard as he could ... perhaps that guy could have used some more "manual transmission" knowledge of the plane.
http://en.wikipedia.org/wiki/Air_France_Flight_447#Human_fac...
From that same link.
"this led to a perverse reversal that lasted nearly to the impact: each time Bonin happened to lower the nose, rendering the angle of attack marginally less severe, the stall warning sounded again—a negative reinforcement that may have locked him into his pattern of pitching up"
I learned to drive in an automatic transmission car. I was still in terror when first learning to drive that car. Later on I got a new car, and since manual transmissions where cheaper I got a manual one. I was again terrorized every time I had to stop and get into first gear again, or when I was stopped in a hill. I can only imagine the absolute nightmare I would have been trough if I had to learn both how to operate the stick shift AND the rest of the car and laws of the road at the same time. I actually think progressive learning was better for me.
Same with programming, I learned GW Basic as a kid doing games and what not. Then switched to Pascal, then C and then ASM. I can only imagine how motivating it would have been to 10year old me having to write pages and pages of ASM just to do the simplest thing. Instead learning a easy high level language helped me stay motivated and learn progressively getting into harder stuff little by little.
I've interviewed (and worked with) a lot of programmers now, and I can definitively say that the best ones know how to program in C, and the worst ones do not. I'm guessing that Evan Miller has had similar levels of exposure to good programmers and bad programmers. I make no claims of causality, but the correlation is a damned strong hint.
Said another way: data actually is the plural of anecdote, for sufficiently large values of N.
In this regard, "pointers" in CS can be analogous to "stick shift" in driving cars.
This remind me of this article of Joel on Software:
"All the kids who did great in high school writing pong games in BASIC for their Apple II would get to college, take CompSci 101, a data structures course, and when they hit the pointers business their brains would just totally explode, and the next thing you knew, they were majoring in Political Science because law school seemed like a better idea"
s/java/python
http://www.joelonsoftware.com/articles/ThePerilsofJavaSchool...
All we see is that the programmer who is motivated to teach herself C or Lisp (or any other "difficult") is a better programmer than the programmer who is not. Which, on further reflection, is almost self-evident. Of course programmers who are motivated to keep learning and keep practicing become more skilled. But it doesn't make any special claim about the benefit of C in particular.
Coming at it from another angle, have you worked with a lot of electrical or computer engineers? C, in many cases, is the highest-level language they know. From personal experience, I can tell you that their code is no better (and in many cases, considerably worse) than code written people who've had experience with languages at the top of the abstraction hierarchy.
EDIT: And, of course, I scroll down to see that tptacek has already made this exact point, and more eloquently, to boot.
(1) people who know C are better programmers
(2) people who learn Python first never learn C
I think (1) is generally true. I would be interested to hear someone explain why they think having low-level programming knowledge makes you a worse programmer than one without low-level knowledge.
I disagree with (2). I took a high school class on Java, they spent several years programming in Python and becoming relatively proficient. As the author described, I eventually hit the "hard dirt" where I started to having to worry about the OS, memory, etc. I then learned C and C++, operating system internals, high performance computing, GPU computing, etc and all of the low-level ideas that the author thinks that those who learn Python will never know.
However, both statements (1) and (2) are generalizations, and I've only shown that I violate (2). I believe (2) may actually hold for many people, and I agree with the author's idea that if people learned C first we would have fewer programmers but perhaps also better programmers. Of course, "better programmers" is very hard to define and depends a ton of the domain of tasks over which you're evaluating the programmer.
This isn't related to the argument per se, but it's interesting to observe that website ninjas are making >$150k, whereas most low-level ninjas are making less. The reason is because most people want websites, and all of my SIMD knowledge becomes irrelevant in that case.
"Better programmer" is very hard to quantify. Whereas "makes more money, on average" is a solid metric.
From my limited experience, big companies (Google, Facebook, etc) are a good place to do low-level programming with good pay. Additionally, a lot of the programming in the financial industry (particularly high frequency trading) is very low-level work, and the pay is significantly better than the standard "Bay area web company".
However, I disagree with the assertion from TFA that knowing C is somehow more "intelectually stimulating" than knowing Python. Specifically, I disagree with this:
> "I get the sense that Python-first programmers are missing out on a lot of the fun of programming. There’s a mistaken notion that programming is most enjoyable when you’re doing a diverse array of things using the smallest amount of code, leveraging client libraries and so forth. This is true for people trying to get something done to meet a deadline; but for people motivated by learning — and who aspire to be extremely good programmers — it’s actually more fun to create programs that do less work, but in a more intellectually stimulating way."
I find the above assertion ridiculous, and I know C better than Python. Learning programming with Python is more fun than learning with C. That's not to say C isn't fun; programming is fun as a whole. But Python is not for people "trying to meet deadlines", and it's way less puzzling and lets you get into the "interesting" part of your project faster, with fewer pitfalls and less boilerplate than C. That's the definition of "fun".
US driving is IME (Boston, SF) easier than UK driving because there is much more road space and larger parking spaces, lower and more frequently varying speed limits, and less aggressive driving.
So that's my experience. Back to programming: we would do well to look at "countries" other than our own when talking about all programmers. What about embedded-land? The endless rolling plains of LOB internal applications? Stranger languages: J/APL? Labview?
A stick-shift get you way much closer to the engine. A stick-shift driver can show it's emotions via the engine. stick-shift invites more aggressive driving. Your mental state "shows" thru your driving.
The programming language equivalent is clearly memory management. Yes, you can make it more efficient if you do your own allocation - but you have to get it right every time, and C offers you very little help to do this. C++ offers more, and Rust holds out the prospect of getting it right provided you can understand and fix all the compile-time errors. If memory allocation isn't critical to what you're doing this is a distraction that consumes time and effort to no result.
http://scholars.opb.msu.edu/pubDetail.asp?t=pm&id=7795212016...
When Michigan State University switched their first course from C++ to Python, the Python students did just as well on the 2nd C++ based course as those who took the first course in C++.
CS2 final exams in SS08
Group n Mean StdD
Python 21 172 40
not-Python 62 177 24
CS2 programming projects in SS08
Group n Mean StdD
Python 21 324 82
not-Python 61 340 56
It really depends on their definition of "significant", but just from this data alone I see the not-Python group clearly beating the Python group on the two items here (and with lower variance.) There might be too much noise here to see a big correlation, but there's also the possibility of the data being "below the noise floor", so I would not draw too much conclusion from this."In addition, multiple regression analysis showed that students' past performance (overall GPA) in the University predicted final grades, final exam scores, and programming project scores for the course, but there was no effect of the programming language feature: Python or non-Python."
Much of the data I've seen has suggested that, of all the hard sciences, "engineering" is the one most fraught with magical thinking. It seems to be populated by people looking to shape the problems they face to the solutions they already know -- whether or not those solutions are the best that can be reasonably implemented.
It is the worst part of my experience transitioning from physics to software: everyone seems to go about citing their clever catch-phrases as fact.
> we know what the Overdrive gear in automatic-transmission cars means, and why you use it to pass a truck on the highway
That is not what overdrive is [1]. Overdrive is the very high gear that lets you nearly idle at highway speeds. Stepping on the gas would strain the engine, until the automatic downshifts, which is what the author seems to describe.
[1] http://www.wikiwand.com/en/Overdrive_(mechanics)
EDIT: it seems strange, coming from a miata driver, since those gearboxes definitely lack an overdrive, requiring high RPMs at highway speeds (like a racing transmission, tight ratios)
By this definition the Miata does have an overdrive as most generations have a final ratio of around 0.85 in top gear.
The wheels of the car are not spinning faster than the engine (in the overwhelming majority of cars) because there is still a final drive ratio between the output shaft of the transmission and the wheels. That is rarely taller than 2.50:1 and often greater than 3:1.
Look at the revs per mile in a tire's specification, drive that car at 60mph, and see that the engine RPM is higher than the revs per mile figure, even in the tallest overdrive gear that you have.
And don't talk to me about teaching the ideal gas law without having taught quantum statistical mechanics and quantum mechanics so that students can truly understand all the approximations involved in deriving the ideal gas law from first principles.
Furthermore, don't even think of teaching people programming in C until you've taught electro-magnetism, used it together with statistical mechanics as foundation to understand condensed matter theory so that they can understand what those NPN and PNP mean, and what transistors are built.
This might actually be a good point.
A lot of students struggle with these ideas because they can't match the naive model to reality and so it's a struggle to understand the purpose of it.
Then, a lot more students struggle to get past this type of naive model because they understood the naive model was the point of what they were learning and it turns out - not really close to the end goal.
Sure, most anyone can learn the naive model of mechanics well enough to pass high school physics. But, it takes an excellent teacher and/or an excellent student to get past those two cognitive obstacles and progress any further. I recall physics to have a shortage of students, shortage of teachers and shortage of practitioners, a deficit that's getting worse.
I understand you are trying to make an ironic point but it's not obvious that it should be ironic. Is a naive model the best way to teach people mechanics? It works for some people but it's at least arguable.
As for the deficit of practioners ... there are at least 10 times as many people getting a Ph.D. in Physics in Canada and the U.S. as there are positions ... so, no, there is no real shortage. (There is a shortage of qualified high school teachers though.)
Why is the closed form considered a prerequisite for a numerical approximation? Just because we ultimately end up at the latter, doesn't mean it should be taught in that order.
I may be mistaken about the practitioners but I doubt many Physics PhDs are short of work. It certainly seems like employers want more of them.
But it's perhaps not a coincidence that calculus and classical mechanics were invented at the same time in history - indeed, both were advanced significantly by the same person. Classical mechanics and calculus are two sides of the same coin.
Fundamental Theorem of Calculus: <=1670 Principia Mathematica: >=1687
So which order do you prefer to teach them?
In truth, HS math students do learn about differential equations, and easily have the capacity to apply them numerically. Some historical context and a little creativity in teaching could greatly help the understanding of both and benefit many students.
The massless ropes that do not stretch and massless pulleys and cogs are great approximation for the real world in nearly everything available at home.
If kids can not relate to those, it's because they don't look into the machines they have around them, not because the model isn't realistic.
There is absolutely an art to finding the correct way to simplify models. I'm reminded of Alan Kay's comment at his Ted talk, where he holds up the Gapminder-project for a useful simplification and highlighting/illustration of (rather complex) data, and derides a 3d model of biological processes in cells -- because they show proteins magically dancing stately around and matching up to form reactions -- which is completely different from what is actually happening (large complex molecules spinning really fast, thus making the incredible improbable matching of to such proteins just so possible).
That said, when it comes to mechanics, I think most realize that the model is naive -- students don't feel "deceived" -- and the ratios etc do actually translate to some useful intuitions when doing real-world work (eg: clearing rocks with wire, winch and tackle).
Truer words never said. I'm one of those data points.
Luckily I had the fortune of having a science teacher who identified this and did teach me the basics of the underlying theories in high school. My mind rejects "rules" and wants "reasons." While everyone else was learning about Newtonian gravity, I was grasping the basics of special relativity. While everyone else was learning the "rules of bonds" in chemistry I was learning the basics of QCD. From these base theories I was able to remember via back-tracking all the way back up to the "rules" that I had learnt along with the rest.
It's about being a student versus being a parrot. Wanting insight into the underlying complexities is being a student while regurgitating information in an exam is being a parrot.
I started off with VICBASIC at 8, then QBASIC at 10. I only started C# when I was 16 or 17. Even then C# has a lot of abstractions away from the hardware. The only time I ever truly learnt what was going on is when I was thrown into the deep end: a customer was having problems and I had a memory dump, WINDBG and SOS. Only recently have I been taking the time to actually learn C/++.
Now that I am no longer "parroting" code I am starting to identify parroted code. No care is given to memory usage "because the GC will deal with it." Mutexs are sprinkled everywhere "because that's how you deal with concurrency."
At some point every developer needs to be thrown into the deep end. I would say that learning Python as your first language is fine.
However, if you never take the time to learn the nuances of how Python works, you are a dangerously ignorant.
The thing about driving a stick shift is, when you make a mistake, you know it immediately: you hear gears grinding, or see the tach leap up because you downshifted too far, or start rolling backwards downhill. The car gives you instant feedback on your performance as a driver. You learn from this feedback and become a better driver. You know you're a better driver because the bad feedback doesn't happen anymore.
Making a mistake when programming in a language where you manage memory manually is frequently not like this; you don't get the instant feedback when you screw up. The code compiles, the binary runs, everything looks fine. It's only days or weeks or months or years later that you realize that, while it looked fine, it has actually been slowly leaking memory, or in certain circumstances can lead to a buffer overflow, or so forth. Maybe you never realize it. So, without the prod of that instant feedback, you don't become a better programmer. You just keep writing crappy, broken C forever.
Optimizing a lisp program is very similar to optimizing a C program: a profiler will be used, and the disassembly will be inspected. The main difference is that the pattern of allocations matters a bit more, as there is a garbage collector consuming CPU time along with the mutator (Note that in some cases heap allocation can be just as cheap as stack allocation, as a generational garbage collector that uses Cheney's Algorithm for the nursery will collect short lived objects at essentially no cost).
2) In any event, I wouldn't call someone whose total lisp experience is a SICP class a "lisp developer" and my introductory class in C didn't mention a cache line either (nor did we ever look at disassembly).
[1]: A quirk of the lisp community likely surprising to those who aren't involved is that whether or not Scheme is Lisp is debated. For the least flame-ish response by a prominent lisper, see https://groups.google.com/d/msg/comp.lang.lisp/Bj8Hx6mZEYI/6...
I have heard this statement quite often, but never really understood it. What modern features does C support so poorly? Multicore? GPUs? Distributed? SIMD? Cache Layers?
Of course in each case, C has been extended so as to do "something" with those features. (Excepting GPU programming, which is just such a different abstraction that C just stops working entirely.) But they are extensions bolted on after the fact, to varying degrees, not something well-integrated into the core language and its core runtime, which remains an abstraction of the 1970s computers from which it emerges.
Which is, all things considered, not that great a criticism, seeing as how few modern languages are terribly optimized for these cases either. The very cutting edge of general-purpose languages today are arguably focused for hardware now merely 10 years old. It doesn't mean I think C is "bad" (... or, at least these reasons aren't related to my real objections). It just means that C, while a great guide to how computers worked 40 years ago, is not all that great for the much richer and more interesting machines of today.
I suppose one can make a solid argument for teaching the 1970s model as a valid simplification. Still, I'd prefer that professionals see it as such, rather than have the unexamined idea that C is truly "how computers work".
This is true with a but; it's true in the extent that it's not like C gets it worse than the average high-level language, but the current selling point for C is that it's "close to the hardware" or "portable assembly" and the less that is true, the less strong C is.
There is a lot wrong with this paragraph.
1) The comment I replied to mentioned CPUs
2) the "S" in STM is software, "hardware STM" is a misnomer, it is rather "Transactional Memory" and isn't a particularly modern feature. Hardware designers have dabbled in it for decades, and I don't think it's particularly more popular today than previously
3) C89 already had fairly strict aliasing rules that systems programmers hate so much that every modern compiler has a flag to disable it. Furthermore C99 added the restrict keyword which eliminates this problem (though yes, the motivation for not including "restrict" in C89 was braindead.
Assembly does not count, because it is not a single language. Multicore is mostly a library issue and C11 is as close to the machine as any other language here. There are languages aiming at distributed programming (Erlang,X10), but none of them claims "close to the machine". SIMD is bolted on in C, but no other language does better. Caches are supposed to be transparent; Are there languages with explicit support for scratchpad memory?
This leaves GPUs. OpenCL is supposed to be the solution here. Would you consider OpenCL closer to the machine than C?
- If you stay low level you don't understand high level abstractions. And this will weight as a negative thing in your C code as well.
- If you stay high level you don't understand how it actually works at the machine level.
You freaking need both. The problem of certain languages is that they are not like C, but they are also not like SmallTalk or Lisp or FORTH. They just resemble a more dynamic version of C, so you don't really gasp fully any of those levels.
However if you pick the languages to learn wisely, the magical thing is that once you take a trip in the higher order abstractions, you return back to C and code in a different way.
As a result, C Programmers grow stuck in their own path, unable to advance. The real issue is that most programmers stop learning eventually.
True, getting "stuck" at the Python level and refusing to learn about the inner C workings of OSes can stunt your growth.
But similarly, getting "stuck" at the C level and refusing to learn how powerful abstractions (ie: Metaprogramming... like Hygenic Macros from Scheme / Templates from C++, or OOP / Design Patterns) is just as much of an issue.
And believe me, if you work with "typical" C Programmers, you'll know that they are almost assuredly "stuck" in their ways of programming.
The typical programmer gets stuck. The goal of any programmer who wishes to get better at his craft is to _always_ progress towards the unknown. Try to understand programming at levels beyond whatever your current work environment is... it is a big world out there.
Never get stuck. It doesn't matter if you started in C, Python, Visual Basic or anything else. Just keep practicing and try to understand everyone's perspective.
While C and manual transmissions are fun and rewarding for enthusiasts, I don't think teaching them first would benefit the programming and driving communities.
Learning to program is difficult and frustrating. Learning C first, more-so. I should know. I took data structures (taught in C) when I was 13. I'd been working on a homework assignment for a couple of days, and couldn't get it to behave as desired. I'd inspected every line of code countless times. I'd saturated the code with printf()s, but I still couldn't figure out why it wasn't working. It was as if a condition was always true when it shouldn't be. I was almost in tears when I asked my dad for help. He immediately saw an if statement with an = instead of a ==. Back then, gcc didn't default to warning about assignment in conditions. I was angry at having such a small error cause me so much grief. After having that experience, any reasonable person would say, "This is crazy. I'm going to do something else with my life." Many of my classmates did say that. About 50% of students failed. I imagine more would have persisted if the language was Python.
It's a similar story for learning to drive stick, though the stakes are higher. Learning to drive is difficult and dangerous. Adding the complication of shifting makes it even more difficult and dangerous. In my first week, I managed to roll backwards into another car at a stoplight. I had some very close calls due to my mind being occupied by shifting. In hindsight, I'm lucky nobody was injured due to my inexperience.
Teaching C and manual transmissions first will likely get you better programmers and drivers, but you will have significantly fewer of both. The author implicitly makes this trade-off by not recommending that everyone first learn to use assembly and motorcycles. After all, both are more difficult than (and will teach you skills that are useful for) everyday programming/driving.
But when does this grief stop?
A few months back I had the task of implementing the receiving end of what was basically a single-sign-on scheme. I had to use SAML. If you don't know that that is, consider yourself lucky -- or, just google "I hate SAML." That was one of the early hits I found when I first began researching the subject.
In the end, the implementation wasn't all that difficult. But the documentation was another story. The overview documents were buzzword bingo; the spec was an exercise in tedium; the libraries were poorly documented; the mailing lists were horrible. The complaining of people on the Internet about all this was demoralizing. When I finally figured it out and got it working, I felt like the payoff was poor compensation. It was just too much frustration for what I accomplished.
Programming has its share of this kind of frustration. (So does systems administration.) Just taking your average manpage as an example, we work in an industry where people have no capacity to explain anything to someone who doesn't already understand. A programmer needs inquisitiveness, yes; but that inquisitiveness needs to be steeled by an unreasonable amount of grit.
If programming were full of nothing but the fun of for-loops, it would be for everyone. As it is though, I perfectly understand why many otherwise smart and capable people choose to do something else.
I love Python; but maybe using it as an introductory language -- in college, no less -- is a little bit like luring children with candy.
It doesn't, but we get used to it. I take the same view as Douglas Crockford:
I think there has to be something seriously wrong with you in order to do this work. A normal person, once they’ve looked into the abyss, will say, “I’m done. This is stupid. I’m going to do something else.” But not us, ‘cause there’s something really wrong with us.[1]
I'm pretty sure this "brokenness" can be trained, but tossing someone in the deep end is not the way to do it.
1. From Programming Style & Your Brain: http://www.youtube.com/watch?v=taaEzHI9xyY#t=26m50s
I don't feel that learning C would have been the best choice to start with. And the stick shift analogy really not a very good one.
Nobody ever needs to learn to use a stick shift if they don't want to drive a stick. End of story. If you don't need C, you don't need it.
C is great for embedded programming, although I'd prefer at least a limited version of C++ myself. Pure C? I personally don't fine a lot of uses for it.
If your a web developer, or any one of a large number of modern programming areas, you may well NEVER need C or C++. To imply that all programmers need C, is, kind of antiquated IMO.
e.g. Intro CS 1/2 in python, followed by a computer architecture course that introduces an assembly/machine language (you could write an assembler/disassembler in python), possibly followed by C (since pointers will make sense now), and finally something different like lisp or ML family.
Additionally, I don't know how to drive a manual transmission car, but surely you would use a lower gear, not overdrive, to pass a truck on the highway.
And, in any case, structured programming languages in general (including C) are pretty poor for developing "a proper mental model of how computers work". You'd be better with assembly -- or even BASIC, which despite its more beginner-friendly syntax is structurally very simply to assembly.
OTOH, Python offers a lot less distraction than something like C when learning CS concepts (much of my early formal CS instruction was in C, and I've seen people more recently go through similar things in Python).
Also, I think, contrary to the claims in the article, that there are far more people now than at any time in the past capable of writing "extremely good software" of the type the article discusses (that is, extremely well-tuned on a low level), and that lots of them are writing such software, often the tools that make it unnecessary for most programmers on most tasks to focus on low-level performance optimization in order to have software that is fit for its purpose.
Now, its true, that the programmers capable of doing that kind of low-level tuning may be a smaller percentage of all programmers than a generation ago, when C was considered a fairly high level language and not a fairly low level language (when that ratio was lower than it was a generation earlier than that.) But, so what?
BASIC (if remember correctly, has no pointers) so it is far from assembly. BASIC seems close to Python in this regard.
BASIC, like Assembly, has no special "pointers" -- just values (numbers) that can be treated as addresses and used to read (in via PEEK statement) or write (via POKE statements) the corresponding memory locations.
C, by having distinct pointers but supporting arithmetic on them is somewhere between languages like BASIC or Assembly (where there are just numbers, which can be used as addresses, but not special "pointers") and something like Python (where there are object references, but you can't do arithmetic on them like C pointers.)
I'm told that the number of manual transmission cars is declining year over year as well.
Same with manual transmission, which offers less benefit over better automatics (and even less over CVTs.)
Also makes the classic smug C programmer logical fallacy that assumes that C is the perfect way tp learn how a computer really works rather than Assembler or raw machime code.
Too many people have mental blocks of "programming is too hard". That is, until they see 2+2 or print 'hello world' and it just works.
Far more average people out there than brilliant, let's focus on those and let the geniuses fend for themselves, i'm sure they'll do fine.
I don't know if C is necessarily the best language to familiarize users with these concepts. Rust makes users grapple with the implications of explicit memory management but does so in a way that is far less dangerous at runtime than C (at least in many cases). So maybe figuring out how to reason about pointers and lifecycles and ownership in an explicit way in order to get a Rust program to compile is better than messing around with code that segfaults and does weird things until it happens to start working properly.
But I still think there is some value to hands-on experimentation. Concepts like "primitives are passed by value, but objects are passed by reference" in a higher-level language might seem like arcane trivia to a beginning programmer, but someone who has played around with the same idea in C (or another language where this sort of thing is reified) might be better positioned to understand the underlying rationale.
No further comment. There is absolutely nothing bad about learning the inner workings of your machines. Go even lower than C if you can -- learn assembly, learn about logic gates and memory, learn some electrical engineering, and the physics underpinning it. It just makes the magic more amazing.
And you may think it is a waste of time, but you have to have a little bit of faith that it isn't. Because you will be a different kind of programmer once you cross that threshold.
How most colleges seem to work these days is that your first 2 years or so are nearly all Java or Python. That makes total sense: at this point, you're learning conceptual techniques (algorithms, data structures, etc.).
C is introduced through an Operating Systems course, and there its value is absolutely apparent. The big problem though is that C and OS are hard. Without the compulsion of a grade, I almost certainly would have given up dozens of times, even though looking back I truly appreciate the course. While I've never touched C in my professional career, that one course was enough for me to reason about the impact of OS choices on my programs. Unfortunately I don't think 99% of "self-taught programmers" will ever bother to struggle through OS because it's very hard and there's no immediately obvious gain.
Some people will have the drive and opportunity to learn to code in C, just like the author had his father's car with a manual transmission and parents who forced him to learn how to use it. However, not everyone is coming from the same background.
Joe Nerd Jr could probably start with C. Joe Public Jr probably needs an easier introduction. We don't have med students immediately dissecting cadavers, nor do we have basic math classes that start you off with calculus. While some people will feel starting with these advanced classes makes you understand the subject better, many people will bomb out completely and become alienated with the topic. Perhaps some like the concept of survival of the fittest, but it may not be beneficial to society in the long run.
Most computer science students end up in the programming field. They do not stay in academia perpetually. Usually if someone wants to become a pro coder, they're told to go get a BS in CS. We could debate what a True Computer Scientist is, but in the end most people are there because they like technology and want a well paying job. Some just won't be hardcore enough for some people's liking.
Because that's doubling the amount you have to learn in early CS courses. Most students already struggle with wrapping their heads around basic programming, algorithms, and data structures. Adding the challenges of pointers, memory allocation, etc. into the mix can only drive up dropout rates.
I don't think anybody should graduate with a CS degree if they don't know C, but I also don't see any value in forcing them to learn it first.
The other modules I took covered OOP using C++; Smalltalk, SQL, db design (JNF and all that jazz). We're going back more than a decade here so no doubt there was a whole heap more too.
Personally I liked starting with the hardware, jumping in with C and then linking the two together. Gave a good basis to understanding computers, I think that first module might have been called something like "understanding microprocessor based computing".
I've never understood this.
That is, I understand completely how people choose to shift into lower gear to lessen their speed gains due to descent down the hill ... I get that.
But what I don't understand is why ? Your brakes are replaceable consumables. Your transmission is an integral mechanical part of the car, the failure of which (in most cases) is synonymous with the failure of the entire car.
So why, given the choice, would you choose to divert wear from the brakes (where it belongs) to the transmission (where you don't want it) ?
Is it just assumed that you won't have decent brakes ?
By the way, I grew up in the mountains in Colorado, and live there part time now, so it's not like I "don't get" the scenario. I get it. This just never made sense to me, unless you assume you've got bad (or poorly maintained) brakes...
In case of emergency it makes a huge difference. You've got to control your brakes and turn the steering wheel. If you release your foot from the brakes the car will surge forward as it is not limited by the transmission.
Plus water, snow or ice downhill with brakes = danger, very easy to lose control of the car. Again if you're limited by the transmission it's a non- issue.
Plus it makes your brakes hot, which makes them less efficient in case of emergency (I think).
Your transmission should always be the limiting factor of your speed, period. That's it's job, to determine the speed of the car. The brakes are only here for when you couldn't downshift fast enough and you're going to hit something if you don't stop the car. That's what my instructor taught me, and repeated again and again.
When driving I sometimes see people braking all over the place, most of the time they also have strange trajectories, and are not easily predictable. I pass them as soon as I can.
And yes I use a stick, never used an automatic in my whole life in fact. I don't think it teaches you anything about the mechanics of the car as i know mostly nothing about that. But I'm pretty sure you "feel" the car much more. I'm nearly certain the aforementioned brakers are mostly automatic drivers (pretty rare in my country as most people despise automatics.)
On a long descent, excessive use of the brakes can cause them to overheat and "fade", meaning that you will not be able to stop the vehicle at the bottom of the hill. This can be caused by loss of friction from overheated brake pad material and rotors, or by the actual brake fluid itself boiling out of the master cylinder and becoming compressible.
This is not nearly as prevalent as it used to be, now that vented disc brakes are standard on most modern vehicles.
See http://en.wikipedia.org/wiki/Brake_fade
Another reason is that fuel injection is cut completely in modern fuel-injected vehicles under compression braking, meaning you will get better gas mileage if you engine brake than if you idle down the hill.
I'm pointing this out because I have strong opinions about how to teach beginners backed up by a non-trivial amount of success.
I disagree with Evan's conclusions, although I agree with the spirit of most of it. I don't know how much experience Evan has teaching or what opinions he has about how teaching/learning works, but where and how he misses the mark are what I'd expect from someone who is a very experienced engineer but has limited experience teaching a wide range of people how to program.
Here's my summary of Evan's article.
To be a good programmer it's necessary to have a mental model of how computers actually do their work. Languages like Python, Ruby, JavaScript, etc. make it difficult (if not impossible) for beginners to develop an accurate model. C, however, forces the programmer to build an accurate mental model of what their computer is actually doing. What's more, it isn't too difficult for ("smart") beginners to learn as their first language. Therefore, we ought to teach more beginners C.
I think his claim is actually a little stronger than that — he's almost saying that teaching something other than C is a disservice to students — but I don't want to be unfair.
I agree that students need a mental model of how computers actually do their work in order to be a good programmer. I also agree that among all the languages we have out there, C is the one that most closely approximate's the computer's internal model of how it does its work. On Quora, someone asked me what are the 5 language one should learn. At the top of the list was C. I said: "Learn C. C is the best language to learn if you want to get a visceral feeling for computation from a computer's perspective. " ()
I also recommended:
- Ruby or Python
- Scheme or Clojure
- Haskell
- Erlang or another "oddball" language like Forth, APL, or Prolog.
Link: https://www.quora.com/Knowing-what-you-know-now-what-are-the...More generally, you want students to have an accurate mental model of _computation_, not just how computers work. There are many perspectives and one doesn't really "see" the overall picture until one has adopted, compared, and related many different perspectives.
While I'll agree that Python or Ruby might not give a student an accurate mental model of how a computer works on the inside, you won't be able to convince me that they don't give students _one_ accurate mental model of _computation_.
For a beginner, we're going from a place of no model of computation to some model of computation. C's perspective on the matter is one perspective. Haskell's is another. Smalltalk's is yet a third.
As a teacher, these are my goals:
- Give a student _some_ accurate model of computation
- Give them the tools to refine that model over time
- Develop a sense of consistent forward momentum
- Set them up to do all of the above without being in my presence
IMO, whatever language we teach first should be chosen with these goals in mind. A beginner's first language is a beachhead. If we do our jobs correctly, students will synthesize other perspectives as they develop as programmers.I think Evan's rebuttal to this is encapsulated in this quote:
> So Python programmers, as a rule, never get around to learning C.
Well, maybe that's the problem! Maybe we should be teaching students in a way where the _need_ to learn C is obvious to the student, where the student sees a C-shaped hole in their programming knowledge (so to speak).
At CodeUnion, we're absolutely not shy about diving into the Ruby interpreter's internals, for example. Want to know what a Ruby method does? Let's look at the source code! It won't bite.
The problem isn't that students aren't eating their spinach. The problem is that students aren't being exposed to situations where they see the lack of spinach is the problem.
The same is true of more abstract concepts like data structures, by the way. We can teach binary trees all day long, but it's not going to "click" for a student until they have a binary-tree-shaped hole in their life.
Focus on getting them to that place and they'll be _begging_ you to teach them C, data structures, functional programming, or whatever else you think is the really important stuff.
Eventually I decided to try to write an IRC bot in Python. During this effort, I realized I knew nothing about networking, concurrency (how does gevent work?), operating systems, and how to do non-trivial IO. Similarly, I also ran into performance and memory problems with some numpy code, and I started to wonder how does this stuff work and why is numpy so much faster than anything I can write in pure Python? These were my "C shaped holes". Around the time I discovered I had these "C shaped holes" is when I started college, which was a good place for me to take low-level programming classes and fill the holes.
Besides getting a student motivated, maybe the hard part of education is having students realize that they have these holes.
You make some interesting points, but in my (limited) anecdotal experience, C is a pretty awful language.
Of all the languages I half-way know, I think I'm still least comfortable with C. Why not start out by teaching assembler? Especially post 32bit x86, assembler (intel syntax) is rather pleasant. It allows highlighting lots of things that are commonly hidden in C: calling conventions, different types of strings (eg: C vs Pascal) etc.
C is obviously very useful as a "real-world" language. If you need to add a driver you need to Linux, or patch a bug in your shell (or shell utility) etc -- knowing at least enough C to be dangerous can be very helpful. You'll likely be able to put C to good use re-implementing critical sections of python/ruby/whatever code. But IMNHO C still remains a rather awful language.
For some reason, I feel that pointers are easy in Pascal (and assembler) and hard to grasp in C -- as far as I can tell this is entirely a syntactical issue for me.
In the future (post rust 1.0), perhaps I'd recommend assembler, rust and some other high-level language (or two...). I find it hard to recommend Pascal -- while compilers exist, it is a little too much of a "teaching language" -- and a little too far on the outside of everything. Ada might be an interesting option.
Perhaps assembler > C > python. Or assembler > SBCL (Steel bank common lisp) -- because it integrates well with assembler, allowing the user to easily "peek under the covers".
C is awfully low level -- and yet also surprisingly high-level. There's no sane, canonical way to handle text (that is utf8/unicode -- not merely ascii). I'm not entirely sure C++ is much better in this regard, but I still think Storstroup's contrast between "beginner C" and "beginner C++" from 1998 is interesting[1]. I'd be curious what kind of production level code a beginning C programmer produces, and for what kind of problems.
Fundamentally though, I'm not sure there is a good "one way fits all". A lot of people will be served fine with only Python or Ruby (or Julia). Thinking too much about how the computer works can also be a hindrance, I think. Getting actual work done, does seem to hinge on joining together proven, useful tools. Be that tool Berkley DB, SQlite, Postgres, levledb, Riak, redis -- or text files (for example). Or protobuf or json or xml. Etc.
At any rate, I fail to see, from the point of "understanding how the computer works" -- it helps very much to learn (just) C. Without assembler, I think most of the useful lessons are lost -- and I'm not convinced the number of lesson most obvious in C -- but not covered by assembler on one end, and a high level language on the other -- are that many?
It might seem trivial to you, but the idea that you have a bunch of 1s and 0s in memory that the computer somehow "knows" is an integer or a character or whatever else is a HUGE intellectual hurdle for people who haven't considered it before. For that reason, I think it's a mistake to put a student in a situation where they're grappling with both memory and things like calling conventions simultaneously and for the first time.
I can't comment on Pascal, but I don't think C++ is an accurate reflection of the computer's own perspective on how work is happening. The main thing C does is make the way your program interacts with memory much more visceral.
There's the added benefit that the most popular interpreters for Ruby and Python (MRI and CPython, respectively) are written in C. This gives the teacher an opportunity to connect concepts in C _directly_ to already-understood concepts from another context.
All in all, I think the debate about which language(s) to learn is overstated, albeit one that programmers love to have for tribal reasons. The same concepts are embedded in all of them in different ways. The important thing is extracting, isolating, and abstracting those concepts.
Wow, such a bunch of self righteous bullshit! Overdrive is either a specialized gear or just the highest gear. It is in all cases the worst possible gear to 'pass' anybody, or really do anything besides maximize efficiency while cruising at a constant speed.
People that drive stick aren't better drivers or better at driving, evidence of which is the pile of prematurely worn clutches behind every lube shop.
But doesn't that completely ignore the rest of what makes good software, like understanding the problem space and creating a good design? It seems to me that your choice of language only matters if the language muddles your clarity of thought and expression, and in this regard the merit of C over Python is less clear.
Good to know now at least C is allowed. We're going somewhere :)
As to losing interest in programming because Python is too easy - BASIC on 8bit computers was gateway drug to programming for me and most of my friends, I think kids like programming being too easy (instant feedback etc).
Really? "Smart and curious" people will just see this new language and go "well fuck, I dunno what to do"? They won't, for instance, just LEARN THE NEW LANGUAGE?
Arguing that beginners should start in C because they need to "understand how computers work" is like arguing that first-graders should be taught the construction of the reals so they "know how arithmetic works".
The true assembler equivalent candidate for gearboxes is the infamous "Zwischengas" or double clutch on unsynchronised manual transmissions.
Stick shift??
I learned to code on a PIC micro in assembly.
And I learned to drive in a column shift Kingswood with one break pad, no handbrake and a missing accelerator peddle.
Kids these days.
Counterexample: I started with Python, learned C, and switched to C for most things. (Python is far too slow. PyPy is better, but still...)
You might be optimizing for the ability to write the most righteously perfect software, down to the number of registers and getting every ounce out of the bare metal.
Or you might optimize for getting more people involved in programming as it is a truly transformative skill that empowers and enlightens. I know which one I would prioritize and which one is probably premature.