UnderscoreOh.jl

Call graphs as recompilation-free capture-by-value closures
Author tkf
Popularity
8 Stars
Updated Last
2 Years Ago
Started In
June 2020

UnderscoreOh exports _o for building anonymous "functions"

WARNING This package is all about syntax pun and interactive convenience. It is highly recommended to avoid using it inside any serious packages and scripts.

What is it?

UnderscoreOh.jl is a tool for building anonymous callables. There are several differences to the native anonymous functions and closures created with syntax like x -> ... and function (x) ... end:

  • The callables created with UnderscoreOh.jl are identical when generated with the same expression and the same context. It allows julia to re-use JIT-compiled anonymous callables.

  • Simple callables are easier to build with UnderscoreOh.jl API.

  • Variables are captured by value (i.e., no infamous Boxing).

Property and index access

UnderscoreOh.jl exports _o. Accessing properties and keys generate a function that does that:

julia> using UnderscoreOh

julia> f = _o.key
_o -> _o.key (generic function with 1 method)

julia> f((key = 1, value = 2))
1

julia> map(_o.key, [(key = 1,), (key = 2,), (key = 3,)])
3-element Array{Int64,1}:
 1
 2
 3

julia> f = _o.x[1]
_o -> _o.x[1] (generic function with 1 method)

julia> f((x = (:a,), y = (:b, :c)))
:a

A chained dot-calls as an anonymous callable

Broadcasting expression around this object becomes a unary function:

julia> f = _o.x .== _o.y
_o -> _o.x == _o.y (generic function with 1 method)

julia> f((x = 2, y = 2))
true

julia> f = _o .* 2 .+ 1
_o -> (_o * 2) + 1 (generic function with 1 method)

julia> f(1)
3

julia> g(args...; kwargs...) = (args, (; kwargs...))
g (generic function with 1 method)

julia> f = g.(_o; x = _o .+ 1)
_o -> g(_o; x=_o + 1) (generic function with 1 method)

julia> f(0)
((0,), (x = 1,))

julia> filter(_o.k .== 1, [(k = 1, v = :a), (k = 2, v = :b), (k = 1, v = :c)])
2-element Array{NamedTuple{(:k, :v),Tuple{Int64,Symbol}},1}:
 (k = 1, v = :a)
 (k = 1, v = :c)

Standard broadcasting

The type of the function generated by _o overloads the ~ operator (in such a way that it is nothing to do with bitwise not):

julia> ~_o.x
_o -> ~(_o.x) (generic function with 1 method)

julia> _o ~ _o.x  # equivalent
_o -> ~(_o.x) (generic function with 1 method)

If f is a function generated by o_, ~(f) is also a function. When invoked, this function acts like the one wrapped in ~.

julia> _o.x((x = 1,))
1

julia> (~_o.x)((x = 1,))
1

However, it has the normal broadcasting rule so that it can be used to mix dot-_o syntax with actual broadcasting

julia> sum.(~_o.x, [[(x=1,), (x=2,)], [(x=3,), (x=4,), (x=5,)]])
2-element Array{Int64,1}:
  3
 12

Note that ~ _o .* 2 does not work since it is parsed as (~ _o) .* 2. It can be worked around by using the binary version _o ~ _o .* 2 where _o on the left side of ~ is just a marker for declaring the anonymous function boundary.

(At this point, ordinary anonymous function x -> 2x is much simpler! However, this form is included so that the expression can be used without re-typing it.)

Named tuples

UnderscoreOh.jl also exports _nt. This function simply converts keyword arguments to a named tuple for normal arguments:

julia> _nt(X = 1)
(X = 1,)

When the input contains _o, it creates a function that returns a named tuple:

julia> _nt(a = _o.x .+ 1, b = 2)
_o -> _nt(; a=_o.x + 1, b=2) (generic function with 1 method)

julia> _nt(a = _o.x .+ 1, b = 2)((x = 1,))
(a = 2, b = 2)

Similar packages