FixedArguments

Build Status Coverage Code Style: Blue

This package serves as a straightforward and lightweight extension of Julia's Base.Fix1 and Base.Fix2 functionalities. The key distinction of this package is its simplicity and lightweight design. Its single purpose is to generate a function with fixed values for specific arguments programmatically, incurring no additional computational overhead. Notably, it is not using macro-based sugar-syntax in favor of an ultra-minimalistic API. Additionally, the package offers the ability to modify fixed values using a custom transform function.

Simple example

Consider a basic function where you wish to set specific arguments:

foo(x, y, z) = x * y + z

This package supports two fixing methods: position-based fixing and sequential-based fixing.

Position-based Fixing

Suppose you want to fix the first and third arguments of this function. Using position-based fixing, it would look like this:

import FixedArguments: fix, FixedArgument

# Fixes the argument at position `1` with the value `1.0`
#       the argument at position `3` with the value `3.0`
fixed_foo = fix(foo, (FixedArgument(1, 1.0), FixedArgument(3, 3.0)))

The resulting function can be called as

fixed_foo(2.0) # 5.0

Note: While not strictly necessary, for optimal performance and to avoid dynamic dispatch, it is recommended to have positions known at compile-time. Also, ensure that positions are specified in ascending order.

Sequential based fixing

For sequential-based fixing, the same example would be written as:

# Fixes the argument at position `1` with the value `1.0`
#       the argument at position `2` is not being fixed
#       the argument at position `3` with the value `3.0`
fixed_foo = fix(foo, (FixedArgument(1.0), NotFixed(), FixedArgument(3.0)))

Note: Both scenarios are valid, but to prevent ambiguity, position-based and sequential-based fixing methods are not compatible. Attempting to combine them will result in an error:

fix(foo, (FixedArgument(1.0), FixedArgument(2, 1.0))) # throws an error

The NoFixed() argument can be employed in both regimes, but in the position-based regime, it is simply disregarded.

Custom transformation of the fixed values

The fix function also accepts a custom transformation function as its second argument, which gets called each time the fixed arguments are placed in their respective slots. The default transformation function doesn't alter anything and simply returns the fixed value (aka identity), but in more complex scenarious it provides the ability to dynamically change the fixed values based on their position. For instance:

import FixedArguments: FixedPosition

function unpack_from_ref(::FixedPosition{P}, ref::Ref) where {P}
    return ref[]
end

var1 = Ref(1.0)
var2 = Ref(2.0)
var3 = Ref(3.0)

cached_foo = fix(foo, unpack_from_ref, (FixedArgument(var1), FixedArgument(var2), FixedArgument(var3)))

cached_foo() # 5.0

var1[] = 3.0
var2[] = 2.0
var3[] = 1.0

cached_foo() # 7.0

Similarly, we could create a cached argument at a certain fixed positions:

function unpack_from_cache(::FixedPosition{P}, cache) where {P}
    return cache[P]
end

some_global_cache = Dict()

some_global_cache[1] = 1.0
some_global_cache[2] = 2.0
some_global_cache[3] = 3.0

cached_foo = fix(foo, unpack_from_cache, (FixedArgument(some_global_cache), FixedArgument(some_global_cache), FixedArgument(some_global_cache)))

@test cached_foo() == 5.0

some_global_cache[1] = 3.0
some_global_cache[2] = 2.0
some_global_cache[3] = 1.0

@test cached_foo() == 7.0

Note The object that is returned from the fix function is not a subtype of Function.

Projects with similar functionality: