# FunctionWranglers.jl

This package allows fast, inlined execution of functions provided in an array. It can be used to speed up dispatch-heavy calculations directly, or as a building block for developing high-performance programming primitives.

The following operations are supported currently:

`smap!`

maps a single set of arguments using all the functions into a preallocated array.`sfindfirst`

looks for the first function which returns`true`

for the given arguments, and returns its index.`sindex`

selects and calls a function from the wrangler. This is not constant time, but for small indexes faster than indexing to the original array directly.`sreduce`

transforms a single value with the composite of the functions, and also allows providing extra "context" arguments to the functions

Please note that merging the method bodies at the first call have some compilation overhead, which may be significant if the array contains more than a few dozen functions. Tests run with 200 functions.

## Reference

#### smap!

```
smap!(outputs, wrangler::FunctionWrangler, args...)
```

Map a single set of arguments using all the functions into a preallocated array.

```
| | |_| | | | (_| | | Version 1.5.2 (2020-09-23)
_/ |\__'_|_|_|\__'_| | Official https://julialang.org/ release
|__/
julia> using FunctionWranglers
julia> create_adder(value) = (x) -> x + value
create_adder (generic function with 1 method)
julia> adders = Function[create_adder(i) for i = 1:5]
5-element Array{Function,1}:
#3 (generic function with 1 method)
#3 (generic function with 1 method)
#3 (generic function with 1 method)
#3 (generic function with 1 method)
#3 (generic function with 1 method)
julia> w = FunctionWrangler(adders)
FunctionWrangler with 5 items: #3, #3, #3, #3, #3,
julia> result = zeros(Float64, length(adders))
5-element Array{Float64,1}:
0.0
0.0
0.0
0.0
0.0
julia> smap!(result, w, 10.0) # If your functions accept more arguments, you can also provide them here
julia> result
5-element Array{Float64,1}:
11.0
12.0
13.0
14.0
15.0
julia> @btime smap!($result, $w, d) setup = (d = rand())
3.934 ns (0 allocations: 0 bytes)
```

#### sfindfirst

```
sfindfirst(wrangler::FunctionWrangler, args...)
```

Look for the first function which returns `true`

for the given arguments, and return its index. Return `nothing`

if no function resulted in `true`

.

```
julia> using FunctionWranglers, BenchmarkTools
julia> predicates = Function[(x) -> x < i for i=1:5]
5-element Array{Function,1}:
#1 (generic function with 1 method)
#1 (generic function with 1 method)
#1 (generic function with 1 method)
#1 (generic function with 1 method)
#1 (generic function with 1 method)
julia> fw = FunctionWrangler(predicates)
FunctionWrangler with 5 items: #1, #1, #1, #1, #1,
julia> sfindfirst(fw, 4)
5
julia> sfindfirst(fw, 5)
julia> @btime sfindfirst($fw, 4)
1.699 ns (0 allocations: 0 bytes)
5
```

#### sindex

```
sindex(wrangler::FunctionWrangler, idx, args...)
```

Call the `idx`

-th function with args.

```
julia> adders = Function[create_adder(i) for i = 1:50]
50-element Vector{Function}:
#1 (generic function with 1 method)
⋮
#1 (generic function with 1 method)
julia> w = FunctionWrangler(adders)
FunctionWrangler with 50 items: #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1, #1,
julia> sindex(w, 3, 10)
13
julia> sindex(w, 30, 10)
40
```

Note that this call iterates the wrangler from 1 to `idx`

. Try to
put the frequently called functions at the beginning to minimize overhead:

```
julia> @btime sindex($w, i, i) setup=(i=rand(1:10))
2.228 ns (0 allocations: 0 bytes)
8
julia> @btime sindex($w, i, i) setup=(i=rand(20:30))
3.454 ns (0 allocations: 0 bytes)
60
julia> @btime sindex($w, i, i) setup=(i=rand(40:50))
3.695 ns (0 allocations: 0 bytes)
88
```

#### sreduce

```
sreduce(wrangler::FunctionWrangler, args...; init = nothing)
```

Transform a single `init`

value with the composite of the functions in `wrangler`

.

The functions will be applied in their order in `wrangler`

. If you provide extra args, they will be passed to the functions after the current value of the computation:

```
julia> using FunctionWranglers, BenchmarkTools
julia> pluses = Function[(+) for i = 1:5]
5-element Array{Function,1}:
+ (generic function with 184 methods)
+ (generic function with 184 methods)
+ (generic function with 184 methods)
+ (generic function with 184 methods)
+ (generic function with 184 methods)
julia> fw = FunctionWrangler(pluses)
FunctionWrangler with 5 items: +, +, +, +, +,
julia> sreduce(fw, 1; init = 10)
15
julia> @btime sreduce($fw, 1; init = 10)
1.099 ns (0 allocations: 0 bytes)
15
```