Arc Forumnew | comments | leaders | submitlogin
Function composition syntax, and existing conventions
3 points by etal 6171 days ago | 6 comments
I'm looking at the syntax for function composition -- (f:g x) -- and I have two quibbles, both regarding conventions in other languages.

First, the notation for this in math looks more like a * or . in ascii. Haskell uses a standalone . character, but the convention of using f.g for attribute or method access in other languages is so strong that (f.g x) would probably be unnecessarily counterintuitive for most of us. Which is the second quibble: The : character shows up in other places in math (e.g. defining sets) and some object-oriented languages. It makes me think of namespaces, actually, like in XML. The main strike against * I see is that it's also an existing operator, so how would you compose another function with multiplication?

And this opens up two more cans of worms:

1. Consider the left-to-right flow of a normal Lisp function: You start reading at the left, see the symbol you're defining, its inputs, a flow-control operator, and then maybe a calculation -- at which point you scan for the calculation's arguments, and trace backwards until you're back at the top of the calculation. With the compose operator the nesting of parentheses is less distracting, but your eye still jumps to the last function in the chain and works right-to-left.

The pipe operator in the shell is handy and intuitive -- it's concise, and it reads left-to-right, so the pipeline is easy to follow visually. How about using a pipe for reverse function composition?

Here's an example function (taking a list of numbers) -- see what I mean by left-to-right reading:

  ;; Scheme
  (define (dist-from-orig pt)
    (sqrt (apply + (map (lambda (n) (expt n 2)) pt))))

  ;; Arc + pipes
  (def sum (arr) (apply + arr))
  (def dist-from-orig (pt)
    (map|sum|sqrt [expt _ 2] pt))
Now, the same pipe character could also mean "or" or "such that"; there might be a really effective way to use those concepts on list-processing functions, too, that would be worth spending an infix operator on. However, I don't know of any other language that already uses a pipe character that way (anyone here know APL or K?); "or" is already handled by the language and "such that" would probably appear in a much different context, like pattern matching. And as for the existing compose operator, it already breaks the usual assumption in Lisp that the order of function application is completely defined by parentheses. For stream-of-consciousness hacking, I think this adaptation of the shell's condition is a better use of syntax.

[NB: Most of the code in SICP has to be rearranged a bit to make any use of the compose or reverse-compose operators, which makes me think maybe it's not such a good idea to go too crazy with function composition.]

2. Namespaces: good or bad? They work pretty well for me in Python. If they're left out, I can picture some folks doing it manually, similar to the way it's sometimes done in C:

  ;; In mylib.arc, profanities:
  (def mylib:read (fd) ...
  (def mylib:write (fd data) ...

  ;; In order to do this elsewhere:
  (import 'mylib)
  (mylib:write myfd "User kludge")
Versus:

    ;; mylib.arc:
    (export '(read write))
    (def read (fd) ...
    (def write (fd data) ...

    ;; elsewhere.arc:
    (import 'mylib)
    (mylib:write mydf "Erlang has it right")

    ;; or:
    (import 'mylib '(read write))
    (write mydf "Possibly clobbering read and write")

    ;; or even:
    (import 'mylib '*)
    (write mydf "Python has a nice shortcut")
Better?


3 points by nex3 6170 days ago | link

What I would really like to see is user-programmable in-symbol operators. Then you could trivially implement the pipe:

  (def-inop | (f1 f2) f2:f1)
You could probably even implement a simple module system without any core code using that.

-----

1 point by etal 6170 days ago | link

Sounds good to me. But we'd have to keep a complete list of which characters can be used that way, or else the parser wouldn't know where to slice the tokens, right? Or can you think of a general way to do it?

This topic really makes me wish I knew a function-level programming language like K. It's all about composing a certain set of basic functions, and it's amazingly concise, so I feel like someone with that background would be able to suggest a Right Way of handling functions at this level.

-----

1 point by cpfr 6170 days ago | link

You could just set aside a handful of symbols. Every possible combination of them is a possible infix operator.

-----

1 point by ryantmulligan 6170 days ago | link

Is the way to implement def-inop a reader macro?

-----

1 point by greatness 6170 days ago | link

I believe the ordering of the colon operater was based off of the car/cdr functions work, ie:

  (def caddr (v) (car (cdr (cdr v))))
or using colon notation:

  (= caddr car:cdr:cdr)

-----

1 point by cooldude127 6170 days ago | link

i like the pipe character. it seems to make more sense than the colon.

-----