I had the pleasure to fix several variables with Base.Fix
. Iterating with Fix{2}(Fix{5}(Fix{1}(...))))
isn’t very …er… ergonomic.
I wrote a slightly different Fix
which can take a tuple of positions as a parameter, so one can do
fun(u,v,w,x,y,z) = “$u $v $w $x $y $z”
f = Fix{(1,4,5,2)}(fun, 1, 4, 5, 2)
f(-3, -6)
g = Fix{2}(f, -6)
g(-3)
Any thoughts? I think it’s backwards compatible with the current Fix
. Should I submit a PR?
Code
const _stable_typeof = Base._stable_typeof # throw in this if running outside of Base
struct Fix{N,F,T} <: Function
f::F
x::T
function Fix{N}(f::F, x...) where {N, F}
if N isa Int
fix = (N,)
elseif N isa NTuple{L, Int} where L
fix = N
else
throw(ArgumentError(LazyString("expected type parameter in Fix to be `Int` or a tuple of `Ints`, but got `", N, "::", typeof(N), "`")))
end
any(<(1), N) && throw(ArgumentError(LazyString("expected type parameter in Fix to be integers greater than 0, but got ", N)))
length(N) == length(x) || throw(ArgumentError(LazyString("type parameter in Fix specifies $(length(N)) fixed arguments $N, but got $(length(x)): ",x)))
new{fix, _stable_typeof(f), _stable_typeof(x)}(f, x)
end
end
@generated function (f::Fix{N,F,T})(args...; kws...) where {N,F,T}
callexpr = :(f.f(; kws...))
allargs = callexpr.args
offset = length(allargs) # for function name and parameters
# make room for all args
resize!(allargs, offset+length(args)+length(N))
for i in offset+1:length(allargs)
allargs[i] = :undef
end
# fill in the fixed args
for n in 1:length(N)
allargs[N[n]+offset] = :(f.x[$n])
end
# and the others from args
nextarg = 1
for i in offset+1:length(allargs)
if allargs[i] === :undef
allargs[i] = :(args[$nextarg])
nextarg += 1
end
end
return callexpr
end