Op and Pipe-words

And we are finally here. First noticeable thing that probably hit you in the face if you looked at various Rye examples.

Calling functions

Calling functions in Rye is simple. You use a word, that function is bound to, and provide the arguments.

When evaluator gets to a word bound to a function, it goes forward collecting necessary number of values for arguments and then evaluates the function. Here are few simple examples:

print "Call me"
; prints: Call me

inc 100
; returns: 101

concat "Fizz" "Buzz"
; returns: "FizzBuzz"

factor-of 15 3
; returns: 1

replace "Hello World" "World" "Mars"
; returns: "Hello Mars"

Calling functions with op-words

Now we will call the same functions with the same arguments, but we’ll use op-words instead of regular words. Op-words begin with a dot. In case of an op-word, first argument is taken from the left.

"Call me" .print
; prints: Call me

100 .inc
; returns: 101

As we see in next examples, only the first argument is taken from the left. The rest follow like with regular words.

"Fizz" .concat "Buzz"
; returns: "FizzBuzz"

15 .factor-of 3
; returns: 1

"Hello World" .replace "World" "Mars"
; returns: "Hello Mars"

Operators

What are operators then. Operators in Rye are nothing special, they are just regular functions usually taking two arguments. The only difference is that one and two letter operator characters are by default recognized as op-words, they don’t need the dot in front.

The regular spelling of the word in this case is with added underscore in front op-word. For op-word +, regular word is _+.

_+ 3 2                  ; _+ regular word
; returns: 5
_* 3 2
; returns: 6

3 + 2                   ; + op-word
; returns: 5
3 * 2
; returns: 6

Pipe-words

Pipe words behave similarly or in simple cases exactly the same. So you will get the same results in these examples if as we got them with op-words:

"Call me" |print
; prints: Call me

100 |inc
; returns: 101

"Fizz" |concat "Buzz"
; returns: "FizzBuzz"

15 |factor-of 3
; returns: 1

"Hello World" |replace "World" "Mars"
; returns: "Hello Mars"

3 |+ 2
; returns: 5
3 |* 2
; returns: 6

The difference

The difference is in how they combine into larger sentences, how they combine with values, regular words and other op/pipe-words.

Op-word applies to the first value it can on the left, and pipe-word lets all expressions on the left evaluate and then takes the result as the first argument.

This simple example shows the difference:

13 + 14 .print |print
; prints:
; 14
; 27

More examples

inc 10 * 10
; returns: 101

inc 10 |* 10
; returns: 110

"hi " .concat to-upper "joe " .concat "doe"
; returns: "Hi JOE DOE"

"hi " .concat to-upper "joe " |concat "doe"
; returns: "Hi JOE doe"

"Fizz" .print .concat "Buzz" .print |print
; prints:
; Fizz
; Buzz
; FizzBuzz

First argument choice

Because first argument comes from previous expressions on the left, functions in Rye use the active/input value as the first argument if possible.

It’s not always strictly determinable, but in general in a function one value is the input, the active value and other values are often parameters / settings / options. This sometimes means the same order as we’re already used to from other languages, but sometimes order is specific.

Let’s look at one such chain:

; file hello.txt contains "Hello World"

msg: "hello"

read to-file msg .concat ".txt"
|replace "World" "Mars"
|split " "
|second |printv "Planet {}"
; prints: Planet Mars
|write %planet.txt
; writes "Mars" to file planet.txt

From example above, in replace we expect active value to be the first argument, but printv and write would probably take template string or filename as the first argument in other languages.

Second argument from the left

In practice it shows it’s sometimes very beneficial if you can take second argument from the left, not the first one. Be it that the value that is coming down the pipe is the second argument, or you want to change the order of arguments to get the desired effect. Or with generic functions, more on that later, they dispatch on the kind of first argument, and sometimes active value is second argument.

So if an op or pipe-word ends with a star “*” at the end it takes the second argument from the left, not the first.

; let's say that the word to replace comes down the pipe, not input string 

word: "apple"

str: "did snake eat the apple?"

word .replace* str "****" |print
; prints: did snake eat the ****?

; or we want a reverse effect

word .concat* "- " |print 
; prints: - apple

Warning: math expressions

We look at it above (title Operators) but let’s do it again. In Rye, operators like + - * / are regular functions in the form of op-words. The only difference is that operators are loaded as op-words by default while regular .words need a dot in front to be op-words. Their non op-word (prefix) form is: _+ _- _* _/ , so:

1 + 2 + 3
; return 6

_+ 1 _+ 2 3
; or
_+ _+ 1 2 3
; return 6

And you can use the same function as pipe words using: |_+ |_- |_* |_/ or shorter |+ |- |* |/. But what does all this means at functions (or operators) that are not associative and comutative like addition is? It means you have to be avare of the fact that operators are still op-words and behave like them, not like math operators.

; add 12 and 3 and divide the result by 3 (prefix)
_/ _+ 12 3 3
; returns 5

; using pipe word for division
12 + 3 |/ 3
; returns 5

; using just op-words
12 + 3 / 3
; returns 13 -- Oops!
; op-words evaluate from right to left, pipe-words left to right 

; substracting 3 numbers also doesn't do what you would expect from math
100 - 12 - 5
; returns 93
; it first substracted 12 - 5 and got 7 and it substracted 7 from 100

; addition and multiplicaton have same operator precedence
; but if this was math expression we would expect result 56
10 * 5 + 2 * 3
; returns 110
; from left to right it does:
; 2 * 3 = 6
; 6 + 5 = 11
; 11 * 10 = 110

Why is that? Rye comes from the family of languages where whole language is uniform and math expressions are not threated any differently. These languages usually see it as a plus, that you don’t have to learn (complex?) operators precedence rules. Yes, we all know precedence rules between + - * / but what about all other operators or functions like ! and or & > < ..., so they might have a point.

In REBOL math expressions are always evaluated left to right, which is more natural than right to left, but you will get shot in the foot just the same if you are not avare of it. Lisp goes a step beyond and doesn’t even have infix operators or functions. In forth the same, all functions (also operators) are postfix.

; in REBOL
10 * 5 + 2 * 3
; returns 156 (left to right)

; in Lisp this would be written, which has it's estetics too
(+ (* 10 5) (* 2 3))

; and in Forth (Factor)
10 5 * 2 3 * +

All 3 languages value internal consistency above conventions and so does Rye. So what can you do in Rye in this case:

; in Rye (and REBOL) we can use parenthesis to explicitly define the order
; of operations we like, and this is IMO the most natural way to do this

( 10 * 5 ) + ( 2 * 3 )
; or just
( 10 * 5 ) + 2 * 3
; return 56

; technically we could use the distinction between op and pipe words
10 * 5 |+ 2 * 3
; returns 56

; and Rye has math dialect, which follows math precedence rules
; it's defined in math context, where you can find plenty of math
; specific functions (do `cc math lc` to see them)

math/calc { 10 * 5 + 2 * 3 }
; returns 56