-
-
Notifications
You must be signed in to change notification settings - Fork 5.6k
Description
Fix1
and Fix2
are, as far as I can tell, intended to be used internally. I think these types can be extremely useful for some APIs and I think they should be generalized and given elevated status. In addition to being useful in the same places as Fix1
and Fix2
are used in Base
, by binding some of the arguments, it allows encoding some symbolic manipulation rules in terms of other operations by binding all the arguments.
Here's a possible implementation:
using Base: tail
interleave(bind::Tuple{}, args::Tuple{}) = ()
interleave(bind::Tuple{}, args::Tuple) = error("more args than positions")
interleave(bind, args) = _interleave(first(bind), tail(bind), args)
# `nothing` indicates a position to be bound
_interleave(firstbind::Nothing, tailbind::Tuple, args::Tuple) = (
first(args), interleave(tailbind, tail(args))...)
# allow escaping of e.g. `nothing`
_interleave(firstbind::Some{T}, tailbind::Tuple, args::Tuple) where T = (
something(firstbind), interleave(tailbind, args)...)
_interleave(firstbind::T, tailbind::Tuple, args::Tuple) where T = (
firstbind, interleave(tailbind, args)...)
struct Bind{F, A}
f::F
a::A
end
function (c::Bind)(args...)
c.f(interleave(c.a, args)...)
end
# for backwards compatibility, and succinctness
const Fix1{F, X} = Bind{F, Tuple{Some{X}, Nothing}}
const Fix2{F, X} = Bind{F, Tuple{Nothing, Some{X}}}
Fix1(f, x) = Bind(f, (Some(x), nothing))
Fix2(f, x) = Bind(f, (nothing, Some(x)))
getx(f::Fix1) = something(f.a[1])
getx(f::Fix2) = something(f.a[2])
# should probably be a deprecated:
getproperty(f::Fix1, s::Symbol) = s === :x ? getx(f) : getfield(f, s)
getproperty(f::Fix2, s::Symbol) = s === :x ? getx(f) : getfield(f, s)
e.g.
is3 = Bind(==, (3, nothing))
isnothing2 = Bind(===, (Some(nothing), nothing)) # demonstrate how to escape `nothing`
seems to generate efficient code:
julia> @code_llvm is3(4)
; @ /Users/goretkin/projects/julia_scraps/curry2.jl:22 within `Bind'
define i8 @julia_Bind_19023({ { i64 } } addrspace(11)* nocapture nonnull readonly dereferenceable(8), i64) {
top:
; ┌ @ /Users/goretkin/projects/julia_scraps/curry2.jl:3 within `interleave'
; │┌ @ tuple.jl:96 within `first'
; ││┌ @ tuple.jl:24 within `getindex'
%2 = getelementptr inbounds { { i64 } }, { { i64 } } addrspace(11)* %0, i64 0, i32 0, i32 0
; └└└
; ┌ @ promotion.jl:398 within `=='
%3 = load i64, i64 addrspace(11)* %2, align 8
%4 = icmp eq i64 %3, %1
%5 = zext i1 %4 to i8
; └
ret i
I made a gist trying to demonstrate the benefits I perceive of this proposal. The first example is about replacing Rational{A, B}
with Bind{typeof(/), A, B}
. This probably shouldn't actually be done, and it at least serves as an illustration:
https://gist.github.com/goretkin/0d86957dd3279ce9d55993467f872794#file-curry-jl-L47-L58
The second example is about deferring the evaluation of union(1:3, 5:7)
and carrying a symbolic representation of it to allow more efficient operations downstream.
https://gist.github.com/goretkin/0d86957dd3279ce9d55993467f872794#file-curry-jl-L65-L79
I tried to see if this change would wreak havoc in Base, but I was not able to test it out: #36180
Related:
https://www.cplusplus.com/reference/functional/bind/
PR #36094 Document Fix1 and Fix2
https://github.com/c42f/Underscores.jl