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