Skip to content

Commit

Permalink
Added support for keyword arguments in traitfn
Browse files Browse the repository at this point in the history
Closes #27
  • Loading branch information
mauro3 committed Mar 14, 2017
1 parent 3fd42ae commit 149262d
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 17 deletions.
14 changes: 13 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,19 @@ SimpleTraits now supports this. Above function `f` can be written as:
Note that the parenthesis are needed with negated traits, otherwise a
parser error is thrown.

## Method overwritten warnings
## Vararg and keyword argument trait functions

Vararg and keyword argument trait functions work. However, with
keyword functions the trait function and negated trait function
either need to both have keywords or not. Example:

```julia
@traitfn kwfn(x::::Tr1, y...; kws...) = x+y[1]+length(kws)
@traitfn kwfn(x::::(!Tr1), y...; kws...) = x+y[1]+length(kws)
```


## Method overwritten warnings in Julia 0.5

As of Julia 0.5 warnings are issued when methods are overwritten. Due
to Tim's trick the `@traitfn` needs to create two functions the first
Expand Down
58 changes: 42 additions & 16 deletions src/SimpleTraits.jl
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ end
# @traitfn f{X,Y; Tr1{X,Y}}(x::X,y::Y) = ...
# @traitfn f{X,Y; !Tr1{X,Y}}(x::X,y::Y) = ... # which is just sugar for:
# @traitfn f{X,Y; Not{Tr1{X,Y}}}(x::X,y::Y) = ...
let dispatch_cache = Set() # to ensure that the trait-dispatch function is defined only once per pair
let dispatch_cache = Dict() # to ensure that the trait-dispatch function is defined only once per pair
global traitfn
function traitfn(tfn)
# Need
Expand All @@ -213,8 +213,9 @@ let dispatch_cache = Set() # to ensure that the trait-dispatch function is defi
# Dissect AST into:
# fbody: body
# fname: symbol of name
# args0: vector of all arguments (without any gensym'ed symbols but stripped of Traitor-traits)
# args1: like args0 but with gensym'ed symbols were necessary
# args0: vector of all arguments except wags (without any gensym'ed symbols but stripped of Traitor-traits)
# args1: like args0 but with gensym'ed symbols where necessary
# kwargs: all kwargs
# typs0: vector of all function parameters (without the trait-ones, no gensym'ed)
# typs: vector of all function parameters (without the trait-ones, with gensym'ed for Traitor)
# trait: expression of the trait
Expand All @@ -225,10 +226,13 @@ let dispatch_cache = Set() # to ensure that the trait-dispatch function is defi
fhead = tfn.args[1]
fbody = tfn.args[2]

fname, paras, args0 = @match fhead begin
f_{paras__}(args0__) => (f,paras,args0)
f_(args__) => (f,[],args)
fname, paras, args0, kwargs = @match fhead begin
f_{paras__}(args0__;kwargs__) => (f,paras,args0,kwargs)
f_(args0__; kwargs__) => (f,[],args0,kwargs)
f_{paras__}(args0__) => (f,paras,args0,[])
f_(args0__) => (f,[],args0,[])
end
haskwargs = length(kwargs)>0
if length(paras)>0 && isa(paras[1],Expr) && paras[1].head==:parameters
# this is a Traits.jl style function
trait = paras[1].args[1]
Expand All @@ -242,7 +246,7 @@ let dispatch_cache = Set() # to ensure that the trait-dispatch function is defi
typs0 = deepcopy(paras) # without gensym'ed types
typs = paras
out = nothing
i = 0
i = 0 # index of function argument with Traitor trait
vararg = false
for (i,a) in enumerate(args0)
vararg = a.head==:...
Expand Down Expand Up @@ -277,7 +281,6 @@ let dispatch_cache = Set() # to ensure that the trait-dispatch function is defi
args1[i] = arg==nothing ? :(::$typ) : :($arg::$typ)
end
args1 = insertdummy(args1)

end

# Process dissected AST
Expand All @@ -297,22 +300,41 @@ let dispatch_cache = Set() # to ensure that the trait-dispatch function is defi
else
pushloc = poploc = nothing
end
# create the function containing the logic
retsym = gensym()
if hasmac
fn = :(@dummy $fname{$(typs...)}($val, $(args1...)) = ($pushloc; $retsym = $fbody; $poploc; $retsym))
fn = :(@dummy $fname{$(typs...)}($val, $(args1...); $(kwargs...)) = ($pushloc; $retsym = $fbody; $poploc; $retsym))
fn.args[1] = mac # replace @dummy
else
fn = :($fname{$(typs...)}($val, $(args1...)) = ($pushloc; $retsym = $fbody; $poploc; $retsym))
fn = :($fname{$(typs...)}($val, $(args1...); $(kwargs...)) = ($pushloc; $retsym = $fbody; $poploc; $retsym))
end
# Create the trait dispatch function
ex = fn
key = (current_module(), fname, typs0, args0, trait0_opposite)
if !(key dispatch_cache)
ex = quote
$fname{$(typs...)}($(args1...)) = (Base.@_inline_meta(); $fname($curmod.trait($trait), $(strip_tpara(args1)...)))
$ex
if !(key keys(dispatch_cache)) # define trait dispatch function
if !haskwargs
ex = quote
$fname{$(typs...)}($(args1...)) = (Base.@_inline_meta(); $fname($curmod.trait($trait),
$(strip_tpara(args1)...)
)
)
$ex
end
else
ex = quote
$fname{$(typs...)}($(args1...);kwargs...) = (Base.@_inline_meta(); $fname($curmod.trait($trait),
$(strip_tpara(args1)...);
kwargs...
)
)
$ex
end
end
dispatch_cache[key] = haskwargs
else # trait dispatch function already defined
if dispatch_cache[key]!=haskwargs
ex = :(error("Trait-functions can have keyword arguments. But if so, add them to both `Tr` and `!Tr`."))
end
push!(dispatch_cache, key)
else
delete!(dispatch_cache, key) # permits function redefinition if that's what we want
end
ex
Expand Down Expand Up @@ -353,13 +375,17 @@ function strip_tpara(a::Expr)
return a.args[1]
elseif a.head==:...
return Expr(:..., strip_tpara(a.args[1]))
elseif a.head==:kw
@assert length(a.args)==2
return Expr(:kw, strip_tpara(a.args[1]), a.args[2])
else
error("Cannot parse argument: $a")
end
end

# insert dummy: ::X -> gensym()::X
# also takes care of :...
# not needed for kwargs
insertdummy(args::Vector) = Any[insertdummy(a) for a in args]
insertdummy(a::Symbol) = a
function insertdummy(a::Expr)
Expand Down
29 changes: 29 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,35 @@ end
@test vara3t(5, 7, 8)==Int
@test_throws MethodError vara3t(5, 7, 8.0)

# kwargs (issue 27)
@traitfn kwfn1(x::::Tr1; k=1) = x+k
@traitfn kwfn1(x::::(!Tr1); k=2) = x-k
@test kwfn1(5)==6
@test kwfn1(5.0)==3.0
@test kwfn1(5,k=2)==7
@test kwfn1(5.0,k=3)==2.0

@traitfn kwfn2(x::::Tr1, y...; k=1) = x+y[1]+k
@traitfn kwfn2(x::::(!Tr1), y...; k::Int=2) = x+y[1]-k
@test kwfn2(5,5)==11
@test kwfn2(5.0,5)==8.0
@test kwfn2(5,5,k=2)==12
@test kwfn2(5.0,5,k=3)==7.0
@test_throws TypeError kwfn2(5.0,5,k="sadf")

@traitfn kwfn3(x::::Tr1, y...; kws...) = x+y[1]+length(kws)
@traitfn kwfn3(x::::(!Tr1), y...; kws...) = x+y[1]-length(kws)
@test kwfn3(5,5)==10
@test kwfn3(5.0,5)==10
@test kwfn3(5,5,k=2)==11
@test kwfn3(5.0,5,k=3)==9
@test kwfn3(5.0,5,k=3,kk=9)==8

@traitfn kwfn4(x::::Tr1, y...; kws...) = x+y[1]+length(kws)
@test_throws ErrorException @traitfn kwfn4(x::::(!Tr1), y...) = x+y[1]-length(kws)
@traitfn kwfn5(x::::Tr1, y...) = x+y[1]+length(kws)
@test_throws ErrorException @traitfn kwfn5(x::::(!Tr1), y...; k=1) = x+y[1]-length(kws)


# with macro
@traitfn @inbounds gg{X; Tr1{X}}(x::X) = x
Expand Down

0 comments on commit 149262d

Please sign in to comment.