Once a young man approached the Prophet Muhammad and asked him for some advice. The Prophet replied, βDo not become angry,β and he repeated this three times
iterate faster ... ποΈ. Write higher-order functions, get its imperative style at the compile time!
Writing a full nested loop is a boring task, std/sequtils creates lots of temporary seqs, iterutils uses closure iterators (which is slow).
iterrr uses the ultimate power of meta-programming to bring you the speed that Nim programmers deserve.
"hello".pairs |>
filter((i, _) => i > 1)
.map((_, ch) => ch)
.strjoin() ## lloblock:
template iterrrFn3(_; ch): untyped {.dirty.} =
ch
template iterrrFn4(i; _): untyped {.dirty.} =
i > 1
var iterrrAcc2 = strjoinInit[typeof(iterrrFn3(
default(typeof("hello".pairs))[0], default(typeof("hello".pairs))[1]))]()
block mainLoop:
for li1 in "hello".pairs:
if iterrrFn4(li1[0], li1[1]):
block:
let li1 = iterrrFn3(li1[0], li1[1])
if not strjoinUpdate(iterrrAcc2, li1):
break mainLoop
strjoinFinalizer(iterrrAcc2)There is 3 type of usage:
# predefined reducer
iterable |> entity1(_).entity2(_)...Reducer()
# custom reducer
iterable |> entity1(_).entity2(_)...reduce(loopIdents, accIdent = initial_value, [Finalizer]):
# update accIdent
# custom code
iterable |> entity1(_).entity2(_)...each(...loopIdents):
# do with loopIdents- map :: similar to
mapItfromstd/sequtils - filter :: similar to
filterItfromstd/sequtils - breakif :: similar to
takeWhilein functional programming languages but negative. - inject :: injects custom code
NOTE: you can chain as many map/filter/... as you want in any order, but there is only one reducer.
There are some predefined reducers in iterrr library:
toSeq:: stores elements into aseqcount:: counts elementssum:: calculates summationmin:: calculates minimummax:: calculates maximumfirst:: returns the first itemlast:: returns the last itemany:: similar toanyfromstd/sequtilsall:: similar toallfromstd/sequtilstoHashSet:: stores elements into aHashSetstrJoin:: similar tojoinfromstd/strutilstoCountTable:: similar totoCountTablefromstd/tables
here's how you can get maximum x, when flatPoints is: [x0, y0, x1, y1, x2, y2, ...]
let xmax = flatPoints.pairs |> filter(it[0] mod 2 == 0).map(it[1]).max()
# or
let xmax = countup(0, flatPoints.high, 2) |> map(flatPoints[it]).max()NOTE: see more examples in tests/test.nim
using just it in mapIt and filterIt is just ... and makes code a little unreadable.
- if there was no custom idents,
itis assumed - if there was only 1 custom ident, the custom ident is replaced with
it - if there was more than 1 custom idents,
itis unpacked
Here's some examples:
(1..10) |> map( _ ) # "it" is available inside the "map"
(1..10) |> map(n => _ )
(1..10) |> map((n) => _ )
(1..10) |> map((a1, a2, ...) => _ )
(1..10) |> reduce((a1, a2, ...), acc = 2)
(1..10) |> each(a1, a2)Custom idents work with both op: and op() style syntax:
(1..10).items.iterrr:
map: n => _
# or map n => _
...
(1..10).items.iterrr:
filter: (n, k) => _
# or filter (n, k) => _
...example:
"hello".pairs |> filter((i, c) => i > 2).map((_, c) => ord c)you have to specify the iterator for seq and other iterable objects [HSlice is an exception]
example:
let s = [1, 2, 3]
echo s |> map($it).toseq() # doesn't work
echo s.items |> map($it).toseq() # works fine
echo s.pairs |> map($it).toseq() # works fineevery reducer have: [let't name our custom reducer zzz]
zzzInit[T](args...): ...:: initializes the value of accumulator(state) :: must be generic.zzzUpdate(var acc, newValue): bool:: updates the accumulator based onnewValue, if returns false, the iteration stops.zzzFinalizer(n): ...:: returns the result of the accumulator.
NOTE: see implementations in src/iterrr/reducers.nim
pattern:
ITER |> ...reduce(idents, acc = initial_value, [finalizer]):
update acc here Notes:
- acc can be any ident like
resultoranswer, ... - Finalizer:
- it's optional
- it's an experssion inside of it you have access to the
accident - the default finalizer is
accident
Example of searching for a number:
let element = (1..10) |> reduce(it, answer = none int, answer.get):
if your_condition(it):
answer = some MyNumber
break mainLoopNote: if the item has not found, raises UnpackDefect error as result of get function in finalizer answer.get.
My view is that a lot of the time in Nim when you're doing filter or map you're just going to operate it on afterwards :: @beef331 AKA beef.
I'm agree with beef. it happens a lot.
you can do it with each(arg1, arg2,...). [arguments semantic is the same as custom idents]
(1..10) |> filter(it in 3..5).each(num):
echo num
if num < 7:
break mainLoopNote: mainLoop is the main loop block
adapters are inspired from implmentation of iterators in Nim. TODO: explain more
Limitations: you have to import the dependencies of adapters in order to use them.
Built-in adapter:
groupwindowcycleflattendroptake
Usage: example:
let matrix = [
[1, 2, 3],
[4, 5, 6],
[7, 8, 9]
]
matrix.items |> flatten().map(-it).cycle(11).group(4).toseq()result:
@[
@[-1, -2, -3, -4],
@[-5, -6, -7, -8],
@[-9, -1, -2]
]see tests/test.nim for more.
Define your custom adapter:
for now the name of loop iterator are limited to it.
TODO;
see src/iterrr/adapters.
don't like |> operator? no problem! use iterrr keyword:
pattern:
iterable.iterator.iterrr:
filter(...)
map(...)
reducer(...)
reduce(...)/each(...):
# code ...example:
let points = @[(1, 2), (-3, 4), (12, 3), (-1, -6), (5, -9)]
points.items.iterrr:
map (x, y) => x # or map((x, y) => x)
filter it > 0 # or filter(it > 0)
each n: # or each(n):
echo nBoth the |> operator and iterrr macro can be nested.
Examples:
matrix.pairs |> map((ia, a) => (
a.pairs |> map((ib, _) => (ia, ib)).toseq()
)).toseq()matrix.pairs.iterrr:
map: (ia, a) => a.pairs.iterrr:
map: (ib, _) => (ia, ib)
toseq()
toseq()use -d:iterrrDebug flag to see generated code.
- using brackets for defining custom idents is no longer supported.
iterrr targets the same problem as zero_functional, while being better at extensibility.
Is it fully "zero cost" like zero_functional though?
well NO, most of it is because of reducer update calls, however the speed difference is soooo tiny and you can't even measure it. I could define all reducer updates as
templateinstead of function but IMO it's better to have call stack when you hit errors ...
writing macro is kind of addicting... :: PMunch