I cooked up a little macro that allows you to define a function conditionally. This condition can be totally arbitrary, the sky’s the limit. The only requirement is that the condition must return True
or False
. Unless I’m mistaken, this allows for much more flexibility than traits, but requires no changes to base. Anyway, let me know what you think. I could turn it into a fully fledged package or submit it as a PR to Base depending on what people think.
module DefineWhen
using MacroTools
import Base: !, &, |
abstract type TypedBool end
"""
struct True
Typed `true`
"""
struct True <: TypedBool end
export True
"""
struct False
Typed `false`
"""
struct False <: TypedBool end
export False
@inline !(::False) = True()
@inline !(::True) = False()
@inline (&)(::False, ::False) = False()
@inline (&)(::False, ::True) = False()
@inline (&)(::True, ::False) = False()
@inline (&)(::True, ::True) = True()
@inline (|)(::False, ::False) = False()
@inline (|)(::False, ::True) = True()
@inline (|)(::True, ::False) = True()
@inline (|)(::True, ::True) = True()
function define(source, if_code)
if !@capture if_code (if condition_
function afunction_(arguments__)
lines__
end
else
fallback_
end)
throw(ArgumentError("Requires an if statement and a function call"))
end
new_function_name = gensym(afunction)
condition_variable = gensym("condition")
Expr(:block,
source,
Expr(:function,
Expr(:call, afunction, arguments...),
Expr(:block, source,
Expr(:call, new_function_name, condition,
QuoteNode(condition), arguments...
)
)
),
source,
Expr(:function,
Expr(:call, new_function_name, :(::$True), condition_variable,
arguments...
),
Expr(:block, Expr(:meta, :inline), bodylines...)
),
source,
Expr(:function,
Expr(:call, new_function_name, :(::$False), condition_variable,
arguments...
),
Expr(:block,
source,
fallback
)
)
)
end
"""
@define if condition_
function afunction_(arguments__)
lines__
end
else
fallback_
end
Only define `afunction` as `lines` when `condition` is [`True`](@ref); if
`condition` is [`False`], `fallback`. `condition` should be inferrable based
only on the types of the `arguments`.
\```jldoctest
julia> using DefineWhen; using Test: @inferred;
julia> can(::typeof(getindex), ::AbstractArray, ::Vararg{Int}) = True();
julia> can(::typeof(getindex), arguments...) = False();
julia> @define if can(getindex, something, index...)
function my_getindex(something, index...)
getindex(something, index...)
end
else
error("Can't getindex \$something")
end;
julia> @inferred my_getindex(1:2, 1)
1
julia> my_getindex(nothing, 1)
ERROR: ErrorException: Can't getindex nothing
[...]
\```
"""
macro define(if_code)
esc(define(__source__, if_code))
end
export @define
end