Programming is not algebra
andy.wordpress.com
andy.wordpress.com
Abstract mathematics aside, this article is a perfect case for using Scheme as an introductory language. Scheme does not have false cognates in its syntax--it practically doesn't have syntax at all! C-style assignment syntax is needlessly confusing; the only reason people still use and teach it is because they're acclimated to it.
Scheme has some other big advantages as well. It's exceptionally simple, so there is much less incidental complexity to trip up the student. Additionally, it has far less magic than other languages. Assignment is built into the language, but the syntax isn't: changing a variable looks much like calling a normal function. The same is true for OOP. I found this a big advantage--it's much easier to see that OOP is not the one true way, and that there is no one true OOP way, when things like classes and inheritance are not built into the language. Scheme shows OOP for what it really is: a design pattern that can be useful in certain cases. Compared to a dogmatic language like Java, this is a breath of fresh air!
Scheme is also very multi-paradigm. It supports functional programming far better than any mainstream languages but also supports imperative and OO programming well as well. You can even reuse the same syntax for nondeterministic or logic programming. The best bit is that it supports all these paradigms in very similar ways--it lets them stand on their own merits rather than pushing you towards or away from any of them.
The most important idea in computer science is abstraction, and Scheme is extremely well-suited for teaching students about it. Beyond this, Scheme is also just a fairly pleasant language to use for normal tasks.
It's writing a recipe to a very, very dumb line cook. Except he's really fast, but really dumb.
Unless you're writing a pure stateless turing machine, programming is closer to sending a very detail recipe than defining the universe.
Like anything though, the computer is a tool, you can use it as an algebra machine or as a line cook. It' a bit like you can use a hammer to blow open a wall, or carefully insert a nail in a wall.
... which the compiler spends a lot of effort turning into something more algebraic. Imperative programming is a really a pain in the ass for the compiler, but thankfully after 50 years of research in to the subject compilers are smart enough to get rid of most of the imperative parts.
Of course, ultimately, the program will be translated into a form that is a recipe for the processor or byte code interpreter. But what happens in between would be somewhat easier if programming languages were more about algebra and less about statements.
Many compilers use an SSA intermediate representation which is much closer to a functional language than an imperative one primarily because all data becomes immutable.
Compilers will also often try to add annotations that say whether a function is pure or not, because calls to pure functions can often be lifted out of loops or optimized away entirely.
Yes. In the very end of the compilation the compiler emits machine code or some kind of byte code, which is imperative by nature.
Before that, the compiler must do a lot of analysis and optimization. All of which happens in some intermediate representation, which is as little imperative as possible. In order to get there, the compiler does different control flow and data flow analyses such as live variable analysis, the goal of which is to turn mutable variables into immutable values. It has a resemblance to functional programming in some sense (you can read research papers about SSA, single static assignment and it's equivalence to CPS, continuation passing style, if you're interested).
So in the end, we get assembly or some kind of byte code, but a lot of magic happens to get there. Back in the 1970's, C was like portable assembler. These days the compiler does so much magic that it's not true any more.
We could use any language as the source language for compilation but yet we stick to an ill-fitted C-based languages that are really painful for the compiler and the programmer.
The fact that compilers use s-expressions as an intermediate form says more about the recursive nature of most syntaxes than it does about the imperativeness or not of the program being compiled, and the program being output.
Also, as an aside, if you take some machine code that was compiled from C, even these days it is still ridiculously easy to take that code and turn it back into C. I have quite a bit of experience at doing precisely this. The only thing that has changed is that the compiler has got better at identifying code that is recalculating the same value over and over, and gets rid of that code.
[1] http://en.wikipedia.org/wiki/Static_single_assignment_form
It's loading data into registers, and then performing operations on the registers. It feels like a turning machine changing state over time.
The only thing that has changed is that the compiler has got better at identifying code that is recalculating the same value over and over, and gets rid of that code.
i.e. the compiler identifies pure functions and tries to move them out of any loops. On the other hand, you can't really restructure impure code that much. So basically, you are confirming what your parent said - the compiler has to first analyze the AST and hack its way around any IO and side effects before turning the optimized intermediate program representation into machine code.Btw, the actual hardware could as well be a lambda calculus reducer instead of a von Neumann machine - in that case a compiler wouldn't emit anything imperative at all.
Compilers most definitely try to work with as "functional" (ie. immutable values, not mutable variables) program representations as possible. Such as GCC's GIMPLE intermediate language and it's SSA subset or LLVM's IR "assembly". These languages operate mostly on values and try to avoid mutable variables as far as possible (like LLVM's mem2reg pass which does just this).
Getting to this intermediate representation from a C-like language is really painful (lots of iterative graph algorithms). Dealing with source level optimization in C-like languages is next to impossible and most compilers don't do this. Instead they transform the program to the intermediate representation with simpler semantics, which is most definitely less "imperative" than the source language.
A good compiler can use several source languages, which are translated to a common intermediate representation, where machine independent optimization takes place.
The final compilation phase from the intermediate representation to assembler re-introduces the "imperative" style to the program because the CPU operates that way.
> Also, as an aside, if you take some machine code that was compiled from C, even these days it is still ridiculously easy to take that code and turn it back into C.
If you crank up the optimizer to -O3 and give the compiler opportunities to optimize (inline functions or link time optimization), you cannot recognize the original program structure from the emitted assembly code except in the simplest of cases.
Also, I've only begun looking into abstract algebra. While I have a good idea of the very basics, I don't know much besides.
The first thing that got me interested in the subject was rather domain-specific. I was writing a simple Prolog interpreter. In Prolog, there is the concept of a "most general unifier" (MGU). Given two expressions like f(X, 1) and f(2, Z) it's something like (X=2, Z=1). If the two expressions can be unified without any substitutions, you have an empty unifier. The match can also fail (you can't unify f(1) with f(2), for example).
During resolution, you have to combine these MGUs together. This combination behaves algebraically. I'm pretty sure it's associative and commutative. The empty match is the identity and the failed match acts like zero. I think there is also another reasonable operation--rather than combining them, we alternate on different possibilities. This then acts like addition: the failed match becomes the identity element. (That is, given a choice between any match and a failed match, you can only choose the other match.)
If I had known more about the subject, I suspect writing some of the code would have been much easier.
Since then, I've also looked into more general uses of ideas from abstract algebra. The Haskell standard library has some types from abstract algebra like Monoid, and there are some other packages based on those ideas as well. And, of course, abstract algebra ties into category theory naturally, which is going to be useful in both haskell and OCaml.
So the first patterns I noticed were inherent in the problem, but using Haskell and OCaml is convincing me that they are common to programming in general.
Yeah, I was going to say that the most common algebra in programming seems to be monoids. Being aware of them would probably make some patterns people apply ritualistically more clear and obvious but I think that most experienced people have the notion implicitly.
Category theory formulations are not as common in OCaml and I am hard pressed to find any practical use for them. No that is not quite true. Knowing concepts like fusion, natural polymorphisms, initial and final algebras, ana/cata/hylo morphisms make many fuzzy patterns concrete. And motivate many algorithms. But I have never found a use where a theorem was useful in deriving some part of a program.
People keep saying that. That Scheme is a perfect introductory language. I doubt though that there is _any_ evidence to suggest that students who start with Scheme end up being better programmers.
I love functional programming, but I don't see any correlation between one's first programming language and their programming abilities.
I don't see tikhonj saying that at all. I read it as an assertion that scheme is a superior language for bootstrapping people into the programmer mindset.
And I am saying that there is no evidence to support that assertion.
1. Code looks less like algebra than many other languages. 2. The algebraic symbols Lisp dialects use tend to carry their algebraic meanings, e.g. "(" and "*". 3. Fewer side effects make the language read more similarly to algebra, in so far as it is centered around determine values.
That's not to say that Lisp dialects are the best starting point for learning programming. Only that they appear to address the issue of syntax raised in the article.
FWIW, http://www.ccs.neu.edu/home/matthias/Thoughts/coop.html
Seeing someone like that start with Haskell would be pretty cool. There, at the very least, the equations are actually just that.
My impression was that those with very little practice programming found Haskell no harder to pick up, if not a bit easier, than those who'd played with imperative languages beforehand -- everyone had a good grasp of high-school algebra (being most of the entrance exam), and that seemed to help more than any programming skills.
I noticed the something similar: people with no previous experience found Scheme much easier to pick up than Java. (Java was, unfortunately, the language in the second CS class.) In fact, as odd as it may seem to people well acquainted with C-like languages, people even had problems with the syntax. Some students just didn't know where to put the curly braces in if statements, for example.
I think starting from Haskell or ML would be similar to starting from Scheme. While I actually like Haskell more for programming, I think Scheme has some slight advantages for learning: there's no syntax, it's very simple and it supports a ton of different paradigms. I would personally like to cover a whole bunch of different paradigms in Scheme before specializing with languages like Haskell or Prolog.
For instance: once the students have a handle on pure code, show them State for, well, state; Maybe/Either for exception handling; Writer for output; lists for prolog-esque non-determinism; ST for a richer model of mutable state; and finally IO for side-effects.
Also, I guess I like "getting them while they're young" when it comes to static typing -- it encourages a certain rigour of thought that I feel serves me well even in dynamically typed langauges.
Nevertheless, what's really wrong with starting with basic assembler, like on MIPS or something. You don't have any of these kinds of expressions like "a = b + c" to confuse people!
Knuth is without parallel but I think for the beginner that example you cited might be too abstract for many newcomers, they may have trouble seeing that associated with how the "blinky light box" really works. :) But sure, using any language which uses different semantics for settings variables than simply algebraic looking statements would probably solve that problem.
false.
Her intuition checks out in my REPL.
Seriously though, I'm surprised no-one has mentioned Prolog yet. It's the closest popular language to honest-to-goodness mathematics, to the point that some Prologs can solve linear equations that are written exactly as a system of equations.
'cut', of course, destroys the purely algebraic nature of the thing, and forces you to understand the whole search-tree and backtracking part of the language, but the basics work.
Jumping to this response really bothers me. If you can put your finger on the exact contradiction shouldn't that allow you to seek an explanation rather than throwing your arms up and assuming it's not for you?
The best way to teach someone who knows algebra but is new to programming how to not excessively repeat themselves and to use functions is to tell them to factor out the commonalities. This analogy to factoring in algebra really helps someone who understands the concept but hasn't grokked its application to programming yet.
(Well, it still is, but its an algebra of state).
Some programming languages use the same symbols and notation to mean something different that algebra. Chances are you will get confused if you come from an algebraic background. I Reached to this conclusion based on a single data point: a friend of mine.
Problems like this was what Niklaus Wirth was trying to solve when creating Pascal. The assignment symbol in pascal is :=
Or maybe she would have been better begging with assembly (if I remember my 6502 correctly):
.macro a $300
.macro b $302
LDA a
STA b
The assignment clearly looks different than algebra.Er, BASIC line numbers kind of made it easier to get my head around the concept.