Skip to content

Latest commit

 

History

History
161 lines (129 loc) · 3.49 KB

README.md

File metadata and controls

161 lines (129 loc) · 3.49 KB

FGenerators: foldl for humans™

Dev GitHub Actions

FGenerators.jl is a package for defining Transducers.jl-compatible extended foldl with a simple @yield-based syntax. Here are a few examples for creating ad-hoc "generators":

julia> using FGenerators

julia> @fgenerator function generate123()
           @yield 1
           @yield 2
           @yield 3
       end;

julia> collect(generate123())
3-element Array{Int64,1}:
 1
 2
 3

julia> sum(generate123())
6

julia> @fgenerator function organpipe(n::Integer)
           i = 0
           while i != n
               i += 1
               @yield i
           end
           while true
               i -= 1
               i == 0 && return
               @yield i
           end
       end;

julia> collect(organpipe(3))
5-element Array{Int64,1}:
 1
 2
 3
 2
 1

julia> @fgenerator function organpipe2(n)
           @yieldfrom 1:n
           @yieldfrom n-1:-1:1
       end;

julia> collect(organpipe2(2))
3-element Array{Int64,1}:
 1
 2
 1

FGenerators.jl is a spin-off of GeneratorsX.jl.

Use FLoops.jl to iterate over the items yielded from the generator:

julia> using FLoops

julia> @floop for x in generate123()
           @show x
       end
x = 1
x = 2
x = 3

Adding fold protocol to existing type

The foldl protocol can be implemented for an existing type T, by using the syntax @fgenerator(foldable::T) do .. end:

julia> struct OrganPipe <: Foldable
           n::Int
       end

julia> @fgenerator(foldable::OrganPipe) do
           n = foldable.n
           @yieldfrom 1:n
           @yieldfrom n-1:-1:1
       end;

julia> collect(OrganPipe(2))
3-element Array{Int64,1}:
 1
 2
 1

Note that inheriting Foldable is necessary only if using Base API such as collect. It is not necessary when using just Transducers.jl API (including FLoops.@floop).

Defining parallelizable collection

@fgenerator alone is not enough for using parallel loops on the collection. However it can be easily supported by defining SplittablesBase.halve and length (or SplittablesBase.amount if length is hard to define). Since halve and length has to be implemented on the same existing type, @fgenerator(...) do notation as above should be used. Extending OrganPipe example above:

julia> using SplittablesBase

julia> function SplittablesBase.halve(foldable::OrganPipe)
           n = foldable.n
           return (1:n, n-1:-1:1)
       end;

julia> Base.length(foldable::OrganPipe) = 2 * foldable.n - 1;

julia> @floop for x in OrganPipe(2)
           @reduce(s += x)
       end
       s
4

Using @floop in @fgenerator

@floop can be used inside @fgenerator

julia> @fgenerator function ffilter(f, xs)
           @floop for x in xs
               if f(x)
                   @yield x
               end
           end
       end;

julia> collect(ffilter(isodd, generate123()))
2-element Array{Int64,1}:
 1
 3

julia> collect(ffilter(isodd, organpipe(3)))
3-element Array{Int64,1}:
 1
 3
 1

julia> collect(ffilter(isodd, 1:5))  # fallback to `Base.iterate`
3-element Array{Int64,1}:
 1
 3
 5