We’ve been dancing around this language feature so far, but it’s such a natural way to code in Rye that it’s used a lot and we couldn’t escape it when showing how to use for
loop, so we already did a little crash course.
But we also didn’t know op-words then, so we could use just left leaning set-words. Now we will finally be able to use full Rye.
All code in Rye lives inside blocks, and Rye functions and assignment can take vales from the left, so it makes sense that when we start evaluating a block, that we can sometimes already inject a value that Rye code can pick up and use.
msg: "Hey"
; do evaluates a block of code. It doesn't inject any value.
do { print msg }
; with takes another argument and it injects that value into block when it evaluates it
with msg { .print }
Let’s return back to loop function with knowing about injected blocks and op/pipe words. Now this example from before:
loop 3 { :i , prns i }
; prints: 1 2 3
becomes:
loop 3 { .prns }
; prints: 1 2 3
Similar for for function. We had this before:
names: { "Jim" "Jane" "Anne" }
for names { :name , print "Hi " + name }
; prints:
; Hi Jim
; Hi Jane
; Ji Anne
for range 1 5 { :i , prns i }
; prints: 1 2 3 4 5
and now we have:
names: { "Jim" "Jane" "Anne" }
for names { .printv "Hi {}" }
; prints:
; Hi Jim
; Hi Jane
; Ji Anne
for range 1 5 { .prns }
; prints: 1 2 3 4 5
More functions use injected blocks mechanism. Some, like pass
, keep
and wrap
are called flow combinators. They aren’t used that much, but they
come handy here and there.
; pass evaluates the block, and then returns/passes the first argument forward
101 .pass { * 3 |print } |* 5 |print
; prints:
; 303
; 505
; keep evaluates first block, then second, but passes result of the first forward
4 .keep { * 11 } { + 11 |print }
; prints: 15
; returns: 44
Rye supports a great number of functions for patterns that are usually called higher order functions (filter, map, reduce).
In Rye these functions can just take blocks of code (like if
, loop
or for
functions) for what you would need special forms in most languages.
Rye really has a lot of these, see the reference for them. And here both injected blocks and op/pipe words come very handy.
nums: { 1 2 3 4 5 }
map nums { + 10 }
; returns: { 11 12 13 14 15 }
filter nums { > 2 }
; returns: { 3 4 5 }
reduce nums 'acc { + acc }
; returns: 15
seek nums 'acc { .is-even }
; returns 2
partition nums { > 2 }
; returns { { 1 2 } { 3 4 5 } }
; example from before, so you won't think HOF-s are
; useful just for swaping numbers
text: "Did snake eat the apple?"
words: { "apple" "snake" }
fold words 'tx text { .replace* tx "****" }
; returns: "Did **** eat the ****?"
As explained before, we have more than one function for constructing functions. fn1
is a function that takes one anonymouse argument, and it’s
injected into function block. Either way, the first argument is always injected into the function block.
add10: fn { a } { a + 10 }
; is the same as
add10: fn1 { + 10 }
; this also works
add: fn { a b } { + b }
Comma inside a block is an optional expression guard. You don’t need to use it, but sometimes it’s better to be certain and visually cue the border of expressions.
x: 42
do { print x x + 1 }
prints: 42
returns: 43
; expression guard is entirely optional but sometimes beneficial
do { print x , x + 1 }
prints: 42
returns: 43
But comma also has another effect in injected block, since we explicitly determined the border of previous expression, we re-inject the injected value after it. This little behaviour gives us a lot of additional flexibility in combination with functions that work with injected blocks.
33 .with {
* 10 |print ,
+ 10 |print
}
; prints:
; 330
; 43
{ 7 4 12 21 } .with {
.max .printv "Max: {}" ,
.min .printv "Min: {}" ,
.avg .printv "Avg: {}" ,
print "---------" ,
.sum .printv "Sum: {}"
}
; prints:
; Max: 21
; Min: 4
; Avg: 11.0
; ---------
; Sum: 44