The ML section of Dan Grossman's Coursera course is now behind us, and Racket is coming up fast. But I'm still musing about ML and syntax.
One thing Professor Grossman mentioned in passing was the
|> operator, which he says is predefined in F# and can easily be defined in ML as:
fun x |> f = f xThat is, it reverses the order of function application: the argument comes first, then the function. This lets you write what feel like Unix-style pipelines:
strs |> (filter (length _ < 3)) |> (map toUpper)This seems somehow easier to read than:
map toUpper (filter (length _ < 3) strs)But why is it easier to read? I suppose it's because deep in our hearts we think of functions as little machines, and we like the picture of feeding an input to the first machine, which then feeds its output to the next machine, and so on in a little assembly line.
One reason why object-oriented programming Refuses to Die, even after repeated declarations of Total Victory by the Functional Programming Army, may be that it naturally chains operations in this backwards order. Compare:
strs |> (filter (length _ < 3)) |> (map toUpper)with:
strs.filter(_.length < 3).map(_.toUpper)There is, of course, more going on in the OO version than a reordering of the functional version; the objects in question are actually dispatching the methods after the periods. But visually, the OO version doesn't do much more than substitute
|>, and it retains the readability advantages over the forwards-facing FP version (assuming you agree that using
|>does, indeed, enhance readability).
If you read my previous post on concise syntax, you might not have cared for the fact that I wrote a pattern-matching expression as:
(pat1, expr1 | pat2, expr2 | ... | patn, expr2) foothat is, putting the value to match at the end. But with the pipeline operator, you can say:
foo |> (pat1, expr1 | pat2, expr2 | ... | patn, expr2)which looks a lot more traditional (maybe only if your traditions are of very recent origin).
But all this brings up the question: Why does function application normally put the function first, anyway? Why isn't the default the other way around?
Aha, you say, Forth! Or at least something like it; Arcane Sentiment has an amusing post speculating on how languages might have evolved in postfix world. And also one on the pipeline operator itself.
Here I have to insert a confession: I sometimes have difficulty keeping track of the ordering in the usual function composition operator. I like to read f ∘ g as “f after g” instead of the officially sanctioned “f composed with g”, because putting the f before the g somehow suggests that the f happens first.
Forth, of course, lays out the functions in the order they are applied.
g f means f ∘ g. In fact, rather than thinking of a program in such a language as being a series of operations on a stack, you can think of it as a composition of functions—curiously, it comes out looking just the same.
You might think that all the backwards-facing languages are untyped or dynamically typed, but if you just imagine looking at all your forwards-facing statically typed code in the mirror, you realize that that doesn't have to be the case (backwards everything parse just). And so you might be interested in Jon Purdy's statically typed concatenative language Kitten, something like the love child of Forth and Haskell. He explains how the type-checking works in this post.
In Forth (or Kitten), the function application operator is still just whitespace, as in ML or Haskell. I wonder, would either language let you define a
<| that applies a function in the usual order? I don't recall enough Forth, or understand enough Kitten, to be sure.
And where does infix fit into all this? If forwards (or "head-on-the-left") languages can accommodate infix operators, surely the stack-based head-on-the-right ones can too. You can also imagine a language where the forwards and backwards function application operators are equally concise (
/? ◁ and ▷?), where you'd have no inherent direction at all, tossed around at the whim of whatever operator you've landed on most recently. Perhaps it would be fun to execute programs in such a language twice, once from each end, swapping the precedences of the forwards and backwards operators.
For all that forwards and backwards function application seem both to be useful (perhaps even equally so), I've never seen anyone suggest that function definition should, in general, be reversible—that is, that you should be able to declare your parameter after the function body:
x + y <= y fn <= x fnAt first glance, this appears to be a case of That's Just Wrong. But it looks a lot more reasonable if you include the bindings for the parameters immediately afterwards, as in a Haskell-style
x + y where x = 1 y = 2And maybe there's an argument somewhere for reading a backwards definition as:
x + y forSome y forSome xbut, if so, I'm not going to be the one to make it.