General musings on programming languages, and Java.

Monday, January 07, 2008

In Defence of (0/:l)(_+_) in Scala

A few days ago, Doug posted a rather angry-sounding exit note about Scala, based on two and a half months of 'an in-depth look at the Scala language'. He pointed out some code as an example of how write-only Scala is:

(0/:l)(_+_)

I had no clue what this meant (the first part anyway) at first. Zero Slash Colon One? Is that a new band? I typed it into my Scala interpreter, and while I was typing it, I realised that the One was an Ell. Let's change the name now. l is a bad name. Call it x, xs, or kiwiFruit, but not l:

(0/:list)(_+_)

I then had a bit more of a clue. From the Scala book, I remember reading that operator names that end with a colon are right-associative. In other words, a+b is shorthand for a.+(b), but a+:b is shorthand for b.+:(a). So the above code can be rewritten as:

(list./:(0))(_+_)

This doesn't look a lot clearer, but we can now look up the /: method in the Scaladocs. It's not on List, it's on one of List's traits, Iterable. It's a fold. Here's a longer way of writing the code (and in a way I'd have understood immediately):

list.foldLeft(0)(_+_)

If you're still left bewildered by this version, let's go a bit further.

(_+_) is an anonymous function that takes two values and adds them with the + operator. Let's make up two names and roll them into place. x gets rolled into the first _, y into the second:

list.foldLeft(0)(x, y => x+y)

And if anyone's still not following, what this does is to sum all the elements of list. If the list is List(2,3,4) it looks like 0+2+3+4. Some languages/tools call it reduce (notably MapReduce/Hadoop), some call it inject (Ruby. Any others?). Some languages make you use a for loop, which I assume must be to stop you from getting too cocky about how good your code looks.

So, 1 minute after not understanding the original code, I understood it. The blog in question got mentioned on the Scala mailing list, and Martin Odersky, Scala's inventor, apologised, kind of:

That was my fault. I included it because I liked it, and that for two reasons:

1. (z /: xs) (op) looks like an abbreviation of a left leaning tree
with a `z' on the lower left end (at least to me). I.e. something like

      op
    /    \
   op    x1
  /  \
 z   x0

That's the tree I always draw when I explain fold left.

2. (z /: xs) has the operands in the ``right'' order.
I can see both points, and I'll still use foldLeft. I would not balk at someone else's code using /:. It took me 1 minute to understand, and now I can happily read folds written that way.

In discussions with other Scala programmers, I tried to say that the time taken to learn things isn't as important as how useful they are once learned, but I couldn't find a good way to say it. David MacIver, in an unrelated post to the mailing list, said what I intended to, but much better:

"Optimising your notation to not confuse people in the first 10 minutes of seeing it but to hinder readability ever after is a really bad mistake."

If only I could get him to stop arguing with Ian Clarke and write stuff I want to read!

If I used folds a lot, and perhaps I will someday, I would quite happily use the /: operator. Once I've internalised its meaning I can just get on with reading the names that I chose for things, rather than reading what the language forces me to have. Of course, to most of us, myself included, I'm not that used to folds, but if I considered folding to be just as primitive as +, I'd much rather write /: than foldLeft, just as I'd rather write + than plus.

The rest of Doug's blog really surprised me; I don't know how two people can spend the same amount of time in a programming language's community and get such different results.

My only explanation is that I am comfortable with Haskell, and a long-time Java programmer, so I probably have an advantage when it comes to Scala, which is largely Java minus some bad things, plus some good things from Haskell and many other places.

And while it's in my text buffer:

Don't assume that Scala is only useful for writing web apps, desktop apps, object orientated programming, functional programming, scripting, encoding mathematical properties and concurrency just because that's all that's been discussed on the mailing list this week.

33 comments:

Doug said...

It certainly wasn't my intention for my weblog posting to sound "angry". It was simply meant to be a summary of my personal observations, experiences, and conclusions.

I have no reason to be angry with anyone.

Ricky Clarkson said...

I don't think there is null tone in these quotes:

"I haven’t read any of it [the Scala book], but I don’t have much hope because the primary author is Martin Odersky (the driving force behind Scala) and most of the existing documentation was also written by Odersky."

Most of the documentation that has been around thus far exists for the programming language community. It's spread about in papers, etc. The book you don't hold much hope for is fairly good.

"Almost all of them are programming language afficionados who wouldn’t know how to approach writing an actual application."

It's hard to respond to this without being childish.

"Some of them are openly hostile to the idea of debasing of an FP language by writing actual applications in it, and also hostile to anyone using Scala who isn’t an FP guru."

I can think of one person you could say the latter part about, and he means well, he just doesn't communicate in the best way, in my opinion.

"A really good Scala-knowledgeable IDE could deal with most of these problems, but I don’t see that ever happening."

A few IDE plugins are being developed. I just wish people still wrote those for emacs.

"This code might be darned near illegible to normal people, but it is the pinnacle of purity and virtue in the FP world."

Allow me to rephrase: "You might learn something"

"Pretty much 100% of all actual, working, useful programs have been written with mutable state. So FP programmers, just get over your aversion to it, okay?"

In case you haven't noticed, Scala doesn't enforce immutability, it just prefers it. Haskell is a pure language that, in Simon Peyton-Jones' The Awkward Squad, is described as the world's finest imperative language and with good reason. Functional programming does more than making the computer hot, paraphrasing Simon.

The part about for comprehensions is another part that needs rephrasing to "you might learn something here". Only a couple of days ago in #scala I wrote a single line of code using a for comprehension to compute prime numbers, that was 20 times faster and 20 times more readable than someone else's 10 lines of mutation-ful code.. and I've hardly ever used them before. That's not because I'm a great programmer or anything, it's because I don't look at things like for-comprehensions and think "how can that be in a language intended for humans?", but "ooh, what new things can I do with this?".

"The Scala language is intriguing, quite powerful, but tends toward being illegible when written “properly”."

The only illegible Scala code I've seen thus far, bearing in mind that legibility is pretty subjective, was by someone whose Java code and Haskell code I find quite illegible too.

I didn't intend this to be a tit-for-tat blog post, I probably shouldn't have said the word 'angry'. I hope you can see from some of the above why one might think you were angry, even if you disagree with my explanations.

Anonymous said...

I didn't find the explanation particularly convincing. You didn't explain why, after you're a Scala programmer for a year or two, you'd find this more readable:

(0/:l)(_+_)

than this:

list.foldLeft(0)(x, y => x+y)

To me, the first puts the emphasis on the wrong thing, the 0. Fundamentally this is an operation on the list. The 0 is just a starter value.

In my opinion, readability a year from now and readability in the first hour are not THAT often in competition. This is especially true when you consider how often (good) programmers switch languages.

In any case, for a language targeted at Java programmers, ease of reading in the first hour is really, really important.

To be a bit harsh, this post came off as the sort of thing a Perl programmer would writes to defend a Perl 5-ism until Perl 6 was invented to solve the problem.

tflynch said...

Anonymous, as Ricky said: "I can see both points, and I'll still use foldLeft. I would not balk at someone else's code using /:. It took me 1 minute to understand, and now I can happily read folds written that way."

The language provides a clearer alternative, so it's really just a matter of style.

Jim Menard said...

In your post, you ask if any other languages call it "inject" besides Ruby. Smalltalk calls it "inject:into:", as in

list inject: 0 into: [ :subTotal :next | subTotal + next ].

Ricky Clarkson said...

Anonymous,

One point of difficulty in using folds in general (not particular to Scala) is that people forget which is left-folding and which is right-folding. One big difference is where the starting value goes in the expansion. 0/:list expresses more clearly than foldLeft that the starting value is 'on the left'.

"In any case, for a language targeted at Java programmers, ease of reading in the first hour is really, really important."

I have no objection to Java programmers being encouraged to learn things. While Java programmers can pick up Scala and run with it, they would be better off learning its FP side, it would improve their code no end.

I should note that I didn't come across /: in my first hour, my first day or even my first month. The only time I've seen it is on Doug's blog. My point was that considering Scala unreadable because of /: is an overreaction.

Villane said...

No-one pointed out the symmetry between the shorter versions of foldLeft and foldRight yet:

list.foldLeft(init) = (init /: list)
list.foldRight(init) = (list :\ init)

I think it's fairly understandable after you use it a few times.

sanity said...

You and I must have different standards if you think it is acceptable to have to spend 1 minute to understand such a tiny piece of code, and frankly, I have doubts whether most people would figure it out that quickly. The need to remember the whole thing about methods ending in ':' being right-associative is downright awful.

In the real world, code is read more frequently than it is written. Why is it acceptable to save less than a second in typing "/:" rather than "foldLeft", when it soaks up (at least) a minute of the reader's time to understand it?

I find it telling that most of those defending syntax like "/:" seem to be either academics, or have graduated quite recently. I suspect this means that you have limited exposure to having to work as part of teams, and maintain other people's code.

I know this sounds horribly condescending (and I'd probably have punched anyone who said this to me early in my programming career), but I strongly suspect you would feel differently after you've had more real world experience.

Reinier Zwitserloot said...

This is a fine example of the bruhaha about operator overloading.

A preamble to this comment: I'm aware that scala is in the 'let's try stuff out' phase, and that it is taking the path of 'monkey house featuritis' (throw as much shit on the wall and see what sticks) *NOW*, while scala is still in the experimental phase, before the need to be backwards compatible makes this tactic infeasible. However, as Martin himself said in java posse #155, now that there's a book, it's already become more difficult to take things out again.

On one hand, scala's operator overloading has at least two big advantages. For one, you can write some pretty nifty DSLs in it, and the advanced operator overloading (advanced in that you can select associativity, for example) is a crucial enabler of scala's DSL friendliness. For two, all the usual operators are no longer defined in the scala language spec the way they are in the java language spec; they are defined in libraries. Which means they can be changed and experimented with, and which means that those places where operator overloading makes sense get the right treatment quickly.

On the other hand, there's /:, which is, and let me put this delicately: Completely, utterly, ridiculous.

That should never be in a core library. If you have a bunch of code that uses foldl so much that an operator would make a a lot more sense, by all means, create it. That's one of scala's strengths, in fact. You can patch that sort of thing right in for your project.

you say that one advantage of a right-associative fold operator is that its blindingly obvious that the 0 is the starting value. However, doesn't "fold left" give this away? Folds are fairly simple constructions but there's no common real world use case to naturally grok them the way many human beings implicitly know approximately what's going on in the expression "5 + a". Someone or something needs to quickly explain it to you, and once you've heard the term, 'foldLeft' is just as blindingly obvious. Except this time it has the term 'fold' in it, so we all know what's going on. If having to intern colon + slashy thingie as left/right fold is the only thing scala suggests you do, that'd be allright, but this isn't the only cartoon swearing that scala defines.

In other words, "0 /: list" is not MORE readable than "list foldLeft 0". Not even if you know exactly what fold does, and not even if you know that /: and :\ are fold macros. The operators are STILL in the wrong order, because even though arithmetically it makes more sense to list the 0 first, scala is still OO in nature, and folding is an operation done to lists. Having dual-associative namespacing is a giant pain in the behind. The mental effort of reordering the operators in your mind to understand what's going on is less than having to look both ways just to see which argument 'controls' the function. Taking the expression out of context, having the right ordering seems important. In the middle of a sea of statements, identifying what's going on in the first place is far more important, and only having to look at 'operator/method + thingie on the left' for almost every line is a great boon to reading code.

Having said all that, scala clearly does this much, much better than ruby or python.

In python, there is no right associativity, so when your benevolent dictator gets obsessed with things being 'in the right order', you end up with gross obscenities such as a 'join' method on the String class so that you can type ",".join(list) instead of list.join(","), with the definition of join now being in the wrong place, as well as the arguments being in the wrong order for programmers who expect the defining argument to be on the left.

In ruby, in the ill-considered goal of being 'more english-like', the numbers have date methods on them just so you can write 20.minutes.ago. This might look like english, but when I read dutch, I think dutch, when I read english, I think english, and when I readprogramming languages, particularly programming languages with OO tendencies, I think programming. And in programming, 20.minutes.ago is as grammatically akward as yoda-speak. minutes(20).ago would make more sense. Even new Interval(20 * MINUTES).ago would make more sense. At least the definitions are in the right place (IIRC the date stuff adds .minutes and the like to the numbers namespace).

(_+_) by the way is far less of a problem. _ is scala-speak for 'wildcard' and it's just something you have to learn to use scala. Given the various places it shows up I can live with that, there's a good reason for it. Once you have interned _ as wildcard, _+_ reads like .* + .* in a regexp, and is just as readable as (x, y => x+y). It's more obvious about its nature (the plus symbol is the key feature here), and it's slightly less readable as to what's going on (that you're supplying a closure to fold). In this case, advanced users who understand either form perfectly well really can say that (_+_) is more readable because it highlights the meat of it (the +) more. I haven't seen a convincing argument yet that /: does the same for foldl.

Ricky Clarkson said...

sanity,

It would take quite some inventiveness to come up with a 10,000-line codebase in which each line took a minute to understand. The reality is, it took me 1 minute to understand \:, and that was only the first time I'd seen a right-associative operator used in Scala. The next time I see it, or another right-associative operator, I'll be more savvy.

This really really is not about saving keypresses. I always forget in posts about code that someone somewhere will mistake shorter code for "less keypresses". I forget to qualify it every time, but really, we all know code is read more than written, and /: is about reading code, just as + is.

I assume the teamwork part of your comment refers to having to keep to the lowest common denominator in order to get work done. I recommend Excel for teams like that. The real world? That's somewhere between the water cooler and the coffee machine, as I recall.

I personally have worked as part of teams enough to know the difficulties, though I'll happily admit I need to do more of that.

mightybyte said...

sanity,

Your comment about Scala's use of the colon confuses your unfamiliarity with (and dislike for) the operator with a lack of genuine usefulness. You find it awful because it's not what you're used to. It's no more inherently awful than remembering that square brackets tacked on to the end of a variable name mean that you're referring to a particular element in the array. Or that < insert any syntax artifact here> does what it does. It's all about what you're used to.

If you're trying to read Scala code, it seems reasonable to assume that your a Scala programmer. If your point was that it's hard for the average Java programmer to understand this Scala example, then the root cause of your criticism boils down to the fact that Scala is not commonly used--not that Scala is inherently unreadable. Or, put another way, your use of the term "most people" means "most people in the world today", while ricky's use of it means "most people familiar with Scala".

Oliver said...

Reinier Zwitserloot > please don't confuse ruby and rails (date methods on numbers are from rails, not ruby)

Anonymous said...

How in the @#$^&^# did all of these cry-baby developers ever manage to program in C, with its >, >>, +, ++, &, and && operators?

Utterly ridiculous. If you can't learn something new, find an easier occupation.

hexten said...

To be a bit harsh, this post came off as the sort of thing a Perl programmer would writes to defend a Perl 5-ism until Perl 6 was invented to solve the problem.

Hey, don't go dragging Perl into the argument :)

As with human languages readability in programming languages is generally a function of concision, good structure and listener / reader familiarity with the idioms being used.

The thing that always muddies the debate is that people can get accustomed to pretty much anything and tend to prefer familiarity. So there's a lot of hysteresis in random programmer X's perception of the readability of random idiom Y.

-- Andy reads-forth-for-fun-so-perl's-no-problem Armstrong

Anonymous said...

(Let's just pretend the expression was written "(0/:list)(_+_)" in the first place -- using "l" to name something is just asking for trouble, IMHO.)

I'm brand new to Scala -- like an hour's worth of reading new -- and I found this blog entry very illuminating. Thanks!

I think I'll be able to wrap my mind around most of the syntax in that tight little expression, but: I do think using the associativity of "/:" to indicate the order it processes the list is too cute. Worse, it separates the "initial value" of 0 and the "accumulation operation" of "+", which seems to me like a step backwards. But the terseness of "(_+_)" sure looks to me like a feature, even if it takes some getting used to.

~jb

sanity said...

mightybyte,

The "you will like it when you get used to it" argument misses the point.

We aren't talking about a piece of Scala's core syntax, we are talking about calling a library method by a name that gives no suggestion whatsoever of its meaning to a user that hasn't seen it before. This increases the difficulty in interpreting the language, and it does so for the rather lame reason of saving a few characters of screen real-estate. Its a very bad trade-off.

Ricky Clarkson said...

sanity,

I agree that you probably can't guess what /: does (perhaps those more used to folds than I would disagree).

I disagree that you should be able to. It's perfectly reasonable to have to look up a method in the documentation.

If using /: makes certain code more readable, I don't mind its existence. It doesn't harm any code, because there's an alternative.

Jorge Ortiz said...

Out of curiosity, what was the one-liner for primes?

Ricky Clarkson said...

(for (i <- Stream from 2; if !(2 to (Math sqrt i) exists (i%_==0))) yield
i)(10000)
res0: Int = 104743

This computes the 10001st prime. It's one of the Project Euler problems. The guy who had the slow implementation eventually managed to beat mine (though not in readability!). Mine is quite naive; I should have made j iterate only over the known primes, not all numbers. And 'i' should really have gone from 3, not 2, skipping all even numbers.

sanity said...

Ricky,

"If using /: makes certain code more readable, I don't mind its existence. It doesn't harm any code, because there's an alternative."

The whole point is that it doesn't make the code more readable, it makes it *less* readable because it offers no inherent explanation of what it does.

And it harms the reader of the code in which it is used. The alternative is only available to the writer of the code, not the reader.

More concise != more readable (see Perl).

Ricky Clarkson said...

No. It makes the code less approachable, if you don't know the meaning of /: already. Put it in your vocabulary, get used to it and now you can read code that uses it.

"more concise != more readable"

What is that != you put there? Where can I look it up? I can't Google it. Yes, I'm being silly to show a point.

You're correct that more concise!=more readable, but there is a link.

hdh said...

I like sweet syntactic sugar.

> In python, there is no right associativity

But python's classes can define their reaction when placed on the rhs of the operator::

>>> class l(list):
... def __rdiv__(s, o): return lambda f: reduce(f, s, o)
...
>>> (0/l([2,3,6]))(lambda x,y: x+y)
11

I think python try to (0).__div__(l()) first, when that fails it try l().__rdiv__(0)::

>>> (0).__div__(l())
NotImplemented
>>> l().__rdiv__(0)
<function <lambda> at 0x2ac3e4964b18>

Ruby's open classes can be patched at run-time, but I don't know how to do the __rdiv__. I don't know how to work the Ruby REPL either =\

(Off-topic: thanks Guido, I don't have to entity-escape all those prompts)

Reinier Zwitserloot said...

Thanks for the primer on python's right-associative operator expansion system, hdh. I assume such a thing does not exist for identifiers such as 'join', though that brings the logic behind python's String.join even further into question. If it's such a crucial use case, why not toss it an operator the way 'printf' got one (%)?

Avdi said...

Reiner: 20.minutes.ago and its kin are NOT a part of Ruby, they are added by Rails.

hdh said...

There's an entry in the FAQ on this matter. I came to the entry via an historical note in Dive into Python (the link in there is outdated) which writes it off as historical cruft (aka "Guido said so"). But this part from the entry strikes me as reasonable:

> join() is a string method because in using it you are telling the separator string to iterate over a sequence of strings and insert itself between adjacent elements. This method can be used with any argument which obeys the rules for sequence objects, including any new classes you might define yourself.
> Because this is a string method it can work for Unicode strings as well as plain ASCII strings. If join() were a method of the sequence types then the sequence types would have to decide which type of string to return depending on the type of the separator.

So, with the current way, a new class just has to respond to __iter__ (i.e. just *being* a sequence) to be string-joined (responds with only strings though, as int+str doesn't work), vs. having to implement a join method to quacks like built-in sequences.

But that would force __iter__ to return strings, while a separate join(delimiter) can convert members to a suitable representation (e.g., when passed a list instead of a string, it returns a list with the passed-in list's elements interposed between the NewThing's elements (I'm going nut)). My first argument is nil.

My second argument was that the sequence doesn't have to check the type of the joiner, but that doesn't matter because python automatically promotes ASCII strings to Unicode when +'d with a Unicode string, be either one the joiner or from the sequence. So the 2nd quoted paragraph has no weight, and my 2nd argument is nullable.

Back in 2003, Christian Tismer pointed out a particularly unpythonic deal with str.join::

>>> str.join(',', lines) # vs.
>>> string.join(lines, ',')

Later down the thread, Guido said.

Okay, I think I am now in your camp, that python's BDFL said so-and-so, so-and-so be True. Deprecating string.join would be nice.

I'm going to find a second place where BDFL's power weights in, just for kick (or stopping myself from running to a committee-driven language :)

(hates blogger for disabling lots of HTML)

German B. said...

I'm learning scala, and I find no problem with this syntax. The visual intent of the syntax works on me, I'll tell you why although it might sound silly. I'm not experienced with FP, I started learning a few months ago. The fold functions have always sounded ambiguous to me: if you say foldLeft, it's not obvious to me whether it means:
1) it works to the left (therefore starting at the right), or
2) it starts at the left (therefore working to the right)
A little experience should help in knowing immediately that it means #2. But this scala syntax helps too. I can use either fold, knowing what I'm doing, and not even needing to remember whether it's a "foldLeft" or a "foldRight".
I don't know if anyone else ever had this confusion or whether Odersky had that in mind when coming up with the syntax.
Sure, it's not obvious when you see it for the first time, but learning it is harmless (by the way, I guess any newcomer who goes through Scala by Example will understand it pretty well before they ever find the construct in somebody's code)

Manojo said...

Why don't we just appreciate it for what it is, i.e. fun ?

Det said...

One Anonymous said:

I didn't find the explanation particularly convincing. You didn't explain why, after you're a Scala programmer for a year or two, you'd find this more readable:

(0/:l)(_+_)

than this:

list.foldLeft(0)(x, y => x+y)

To me, the first puts the emphasis on the wrong thing, the 0. Fundamentally this is an operation on the list. The 0 is just a starter value.


To me it seems the problem is that developers learning a new language are reluctant to learn new paradigms. Not only concepts, but paradigms to think about code and programming per se.
(I must admit, I much too often run into this trap myself).

The problem with the above statement is that you want it to read as "There is a Thing. A Message/Method is called on that Thing, given a parameter. Then a function is given to that Method too."
The mental imagination of the expression list.foldLeft(0)(x, y => x+y) renders this paradigm.
You imagine a "list" "called" with a "zero". Then you imagine "two parameters put into a function" and you imagine "the addition".


Now: (0/:list)(_+_)

After I first struggled with such an expression myself a bit, I detected a new way to read it for me: Take a step back from the object.method(param) paradigm, and abstract to a mathematical "expression" which you grep at one glance, like words, wordgroups or even sentences being grepped by an experienced reader.

Then 0/:list becomes clear, you then perhaps get a mental image of a list where a zero coming from the left crushes into a list and jams it leaving only one result at the right ;-) .

No, serious:
When you see the two-group expression (0/:list)(_+_) the symmetry should be obvious:

.( 0 /: list )
.   |     |
.   v     v
. ( _ + _ )

The zero to the left flows into the left wildcard , the list to the right flows, item by item, into the right wildcard .

OTOH in (list :\ 0) ( _ + _ )

The zero "crashes" the list from the right, and the "flow" view matches again:

.( list :\ 0 )
.    |     |
.    v     v
.  ( _ + _ )


All, very nicely imaginable and in balance in my opinion.

is colon cleansing safe said...

If you think Scala is bad, just look at J:

+/%#

That's the code to average a list of numbers.
its obviously more readable (Note I know J, but not scala)

On the other hand, scala is letting you specify the initial value to apply the insert to (0). In J, its predefined for built in functions (0 for +, 1 for *), and I believe (IIRC) you can define the singleton result for custom operators. The scala version also (probably) lets you do something more involved inside the injected function, such as say doubling any list item that is odd, the more natural way in any functional language is to pretransform the list and apply sum to the result.

in J: (1 2 3 is list data) +/ ]`+:@.(2&|)("0) 1 2 3

10

... starts to lose readability.

In scala, I'm guessing the (_ + _) part could be rewriten to customize the more involved logic of conditionaly doubling one element, where you want the special case of the leftmost element of the list never gets doubled (could happen), then the scala semantics might come in handy...

Although a readable J version,

sumdoubleifrightisodd=: dyad : 'x + ]`+:@.(2&|) y'

sumdoubleifrightisodd/ 1 2 3

9

sumdoubleifrightisodd/ 3 2 1

7

Overall, J has great syntactic ideas, but they made it too polymorphic to be readable. There's far wordier/error prone conventions than scala though.

Anonymous said...

If /: and :\ seem like so much gobbledygook to you, this bit of intuition might help.

maxant said...

I can cope with the argument that /: is like + and it's just something you learn (although perhaps one uses + a lot more than a fold). But what I find hard, as someone learning Scala, is that there are 4 methods to do the same thing: fold, foldl, foldLeft and /:

It's that which makes Scala harder to read and easy to write, imho.

Arthur Bugorski said...

Shouldn't "but a+:b is shorthand for b.+:(a)" be "but a+:b is shorthand for b.+(a)" (ie remove the last colon)?

Ricky Clarkson said...

Arthur, no, the : is part of the method name.

Blog Archive

About Me

A salsa dancing, DJing programmer from Manchester, England.