Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Programming is not algebra (andy.wordpress.com)
47 points by krogsgard on July 20, 2012 | hide | past | favorite | 44 comments


It's funny--I've just spent several days realizing exactly the opposite. Namely, programming is algebra. Not high school algebra, granted, but abstract algebra. I've seen patterns from abstract algebra come up several times in programs I've worked on recently, but I didn't know enough about the subject to take advantage of them. Abstract algebra has definitely moved up on my list of subjects to learn soon!

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.


Unless you go deeply in functional programming, writing a program is still writing a Recipe.

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.


> Unless you go deeply in functional programming, writing a program is still writing a Recipe.

... 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.


You took the words right out of my mouth.

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.


Compilers generate assembler, which is about as imperative as it gets... There's nothing functional about LDR1 $7a5112B : ADDA


> Compilers generate assembler, which is about as imperative as it gets...

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.


Whoa, I think you're mixing things up there. Your original claim was that compilers get rid of the imperative nature of some programming languages. I pointed out that a compiler outputs something that is about as imperative as it gets, ie the compiler has made code mode imperative, not less.

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.


> Whoa, I think you're mixing things up there. Your original claim was that compilers get rid of the imperative nature of some programming languages.

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.


    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.


He's talking about intermediate representation and how some optimizations, such as SSA [1], are closer to functional style. In the end it's all imperative assembly, but during the optimization phase it can be something else.

[1] http://en.wikipedia.org/wiki/Static_single_assignment_form


I was thinking "Has he programmed in assembly?" Because it defiantly does not feel functional...

It's loading data into registers, and then performing operations on the registers. It feels like a turning machine changing state over time.


> this article is a perfect case for using Scheme as an introductory language

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 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.


> 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.


One might say that a Lisp dialect overcomes the difficulty suggested by the author because:

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.


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.

FWIW, http://www.ccs.neu.edu/home/matthias/Thoughts/coop.html


I disagree. Scheme is a language where you "get it" very quickly, and I think that is key to motivate people to continue. My first language was freaking Pascal, and I spent my teens thinking I either wasn't smart enough or didn't have the skill to be a programmer.


How do you know it would be any better if you started with Scheme? Can you say you didn't get programming until you switched to scheme? Or did you get Scheme "very quickly" because you were already familiar with programming concepts?


I would be really interested in hearing what patterns from abstract algebra you noticed. Also would you say the patterns are more due to the nature of the problem or something common to all programming?


First, I should note that I've been doing a lot of language stuff in Haskell and OCaml lately, so I might not be entirely representative.

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.


I only just saw your reply. I hope it is not too late to reach you.

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.


This is the first time I'm hearing a beginner looking at a program as algebraic equations! I wonder how people with such backgrounds will take to Haskell. I think their intuitions will get them a long way ...


I've certainly seen plenty of beginners get confused by the idea of mutable state and assignments. The unfortunate syntax that looks like an equation but isn't doesn't help. When I was teaching Java to some younger students in high school, I had consistent problems explaining the idea of variables. Then again, I'm not the best of teachers :P.

Seeing someone like that start with Haskell would be pretty cool. There, at the very least, the equations are actually just that.


The Oxford undergraduate CS course starts people on Haskell before touching any other languages, and the prospectus actively discourages people from picking up a language as "prep work" (although, of course, most applicants are avid programming tinkerers already).

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.


As you can guess from my other post, the intro CS class at my school (Berkeley) was based around Scheme and SICP.

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.


Regarding advantages for learning, via introducing concepts incrementally, Haskell has its own strengths over Scheme as well as vice-versa. You can start on both with "the interactive interpreter is a calculator!", and introduce the non-numeric data types -- but then, in Haskell, you can bring in the various monads to introduce other paradigms.

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.


I think your three steps are basically Scientology's "Education Tech." :) But seriously, it's sort of common sense. I think the key is does the subject engage you enough to want to do all that work? If not then either the presentation should change or maybe you're not into it or not ready to be into it.

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.


OP here. You correctly spotted Hubbard's "Study Tech", a modern formalization of common-sense learning habits. It is a good system independent of the rest of his work. I omitted the credit because many readers would hasten to generalize (i.e. commit an association fallacy[1]) and dismiss the system as inherently crazy.

[1] http://en.wikipedia.org/wiki/Association_fallacy


Ah yes, Study Tech, I had a total brain freeze on that one. :)


I had the same thought. Here's Tom Cruise explaining Study Technology: http://www.youtube.com/watch?v=1S800ilR9c4&feature=youtu...


Here is what I believe to be a more appropriate introduction to "study tech" http://www.cs.cmu.edu/~dst/StudyTech/


This is actually one of my favorite little places to read up on Scientology. :) He has great info!


?- A = 10, B = 20, A = B.

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.


I was going to say the same thing.

'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.


Maybe you should suggest some language that don't create such problems? There are many languages that use mathematical symbols similar way to maths. Functional languages should be better, since there variable values are not reassigned too often. There are also languages that use another operators for value assignemnt, like := in Pascal and <- in GNU R.


> Um… 10 and 20… but then how can a be equal to b? Programming is not for me.

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?


It's kind of natural response when you encounter something that doesn't match what you already know. When I approach new subject I almost always go through that stage. "This is just silly." "This is crap." "I hate this." "Not for me." But that dissonance between the fact that I see something as stupid and the fact I also see people doing this makes me want very hard to understand why people are doing this. Often it leads me to better understanding of the subject, sometimes to better understanding of the people.


agree... the reaction talks more towards emotional maturity than differences between algebra and programming


Programming can be algebraic or a basic application of logic depending on how imperative you lean but algebraic thinking does help code.

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.


Imperative programming is not algebra.

(Well, it still is, but its an algebra of state).


Short version:

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.


Instead of a program, she saw three statements of equality and assumed they were a system of simultaneous equations.

Er, BASIC line numbers kind of made it easier to get my head around the concept.


Programming is algebra in some instances, it's calculus in others. When you want to display something to the screen it's geometry and trigonometry and if you're you're doing 3d work its' linear algebra.


drop the "not" from your title and people will think that you're incredibly smart before even reading the article.




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

Search: