Skip to content

Commit

Permalink
Merge pull request #20 from cstjean/destruct
Browse files Browse the repository at this point in the history
Destructuring assignment
  • Loading branch information
cstjean authored Nov 3, 2020
2 parents 3d8acf8 + 2e80e80 commit b79c324
Show file tree
Hide file tree
Showing 6 changed files with 180 additions and 11 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ os:
- osx
julia:
- '1.3'
- '1.4'
- '1.5'
- nightly
#matrix:
# allow_failures:
Expand Down
4 changes: 2 additions & 2 deletions Manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ version = "1.0.0"

[[MacroTools]]
deps = ["Markdown", "Random"]
git-tree-sha1 = "f7d2e3f654af75f01ec49be82c231c382214223a"
git-tree-sha1 = "6a8a2a625ab0dea913aba95c11370589e0239ff0"
uuid = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"
version = "0.5.5"
version = "0.5.6"

[[Markdown]]
deps = ["Base64"]
Expand Down
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ ConstructionBase = "187b0558-2788-49d3-abe0-74a17ed4e7c9"
MacroTools = "1914dd2f-81c6-5fcd-8719-6d5c9610ff09"

[compat]
ConstructionBase = "1"
MacroTools = "0.5"
julia = "1.3"
ConstructionBase = "1"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Expand Down
35 changes: 32 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,6 @@ end
Adder(10)(20)
```

Similar to [Parameters.jl](https://github.com/mauro3/Parameters.jl), `@qstruct Foo(x)` defines
an `@unpack_Foo Foo(10)` which sets `x = 10`

### More options

```julia
Expand All @@ -59,3 +56,35 @@ Group([1,1+1])
```

See also [Parameters.jl](https://github.com/mauro3/Parameters.jl).

## Destructuring over objects

`@destruct` can be used to destructure objects.

```julia
struct House
owner
n_windows
end

@destruct function energy_cost(House(o; n_windows))
return o == "Bob" ? 10000 : n_windows * 5
end
```

becomes

```julia
@destruct function energy_cost(temp_var::House)
o = getfield(temp_var, 1)
n_windows = temp_var.n_windows

return o == "Bob" ? 10000 : n_windows * 5
end
```

This enables syntax like `@destruct mean_price(DataFrame(; price)) = mean(price)`. Destructuring
can also be applied to assignments with `@destruct Ref(x) := ...` and `for` loops. It can be nested:
`@destruct energy_cost(House(Landlord(name, age))) = ...`

`@d ...` is a synonym for `@destruct`. Import it with `using QuickTypes: @d`.
111 changes: 109 additions & 2 deletions src/QuickTypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ __precompile__()

module QuickTypes

using MacroTools: @capture, prewalk, @match, splitarg, @q, splitdef, combinedef
using MacroTools: @capture, prewalk, @match, splitarg, @q, splitdef, combinedef, isdef
import ConstructionBase

export @qmutable, @qstruct
export @qmutable_fp, @qstruct_fp
export @qstruct_np, @qmutable_np
export @qfunctor
export @destruct

const special_kwargs = [:_define_show, :_concise_show]

Expand Down Expand Up @@ -367,7 +368,7 @@ macro qmutable_np(def)
end

################################################################################
# Functors
# @qfunctor

"""
```julia
Expand Down Expand Up @@ -417,4 +418,110 @@ macro qfunctor(fdef0)
end)
end

################################################################################
# @destruct

macro destruct_assignment(ass)
if @capture(ass, typ_(args__; kwargs__) = rhs_)
nothing
elseif @capture(ass, typ_(args__) = rhs_)
kwargs = []
elseif @capture(ass, (args__,) = rhs_)
kwargs = []
typ = Tuple
else
@assert @capture(ass, lhs_ = _) # regular assignment
@assert lhs isa Symbol
return esc(ass)
end
obj = rhs isa Symbol ? rhs : gensym(:obj) # to avoid too many gensyms
body = []
for (i, a) in enumerate(args)
push!(body, :($QuickTypes.@destruct_assignment $a = $Base.getfield($obj, $i)))
end
for x in kwargs
local_var, prop = @capture(x, a_ = b_) ? (a, b) : (x, x)
prop::Symbol
push!(body, :($local_var = $obj.$prop))
end
esc(quote
$obj = $rhs::$typ
$(body...)
end)
end

macro destruct_function(fdef)
di = splitdef(fdef)
prologue = []
function proc_arg(a)
if @capture(a, f_(__))
@gensym g
push!(prologue, :($QuickTypes.@destruct_assignment $a = $g))
return :($g::$f)
else
return a
end
end
di[:args] = map(proc_arg, di[:args])
di[:kwargs] = map(proc_arg, get(di, :kwargs, []))
di[:body] = quote
$(prologue...)
$(di[:body])
end
return esc(combinedef(di))
end

""" Destructuring for objects.
```julia
struct House
owner
n_windows
end
@destruct function energy_cost(House(o; n_windows))
return o == "Bob" ? 10000 : n_windows * 5
end
```
becomes
```julia
@destruct function energy_cost(temp_var::House)
o = getfield(temp_var, 1)
n_windows = temp_var.n_windows
return o == "Bob" ? 10000 : n_windows * 5
end
```
This enables syntax like `@destruct mean_price(DataFrame(; price)) = mean(price)`. Destructuring
can also be applied to assignments with `@destruct Ref(x) := ...` and `for` loops. It can be nested:
`@destruct energy_cost(House(Landlord(name, age))) = ...`
`@d ...` is a synonym for `@destruct`. Import it with `using QuickTypes: @d`.
"""
macro destruct(expr::Expr)
if @capture(expr, lhs_ := rhs_)
esc(:($QuickTypes.@destruct_assignment $lhs = $rhs))
elseif @capture(expr, for x_ in seq_ body__ end)
@gensym g
esc(quote
for $g in $seq
$QuickTypes.@destruct_assignment $x = $g
$(body...)
end
end)
elseif isdef(expr)
esc(:($QuickTypes.@destruct_function $expr))
else
error("@destruct does not handle expressions like $expr")
end
end

""" Short-hand for `@destruct` """
macro d(expr)
esc(:($QuickTypes.@destruct $expr))
end

end # module
37 changes: 35 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using QuickTypes
using QuickTypes: construct, roottypeof, fieldsof, type_parameters, roottype,
tuple_parameters
tuple_parameters, @d
using Test
using ConstructionBase: setproperties

Expand Down Expand Up @@ -121,10 +121,43 @@ convert_f(foo) = convert(foo.a, 10)
@test_throws UndefKeywordError Issue11()

################################################################################
# Functors
# @qfunctor

@qfunctor function Action(a; kw=100)(x)
return a + x + kw
end

@test Action(2)(10) == 112

################################################################################
# @destruct

@destruct foo(Ref(x)) = x+2
@destruct foo(Ref{Float64}(x)) = x+10
@test foo(Ref(10)) == 12
@test foo(Ref(10.0)) == 20
@destruct foo(a, (Ref{T} where T)(x)) = a + x

struct LongerStruct{X}
a
b
c::X
end

@destruct function kwfun(LongerStruct{X}(u,v; c, bof=b)) where X
return u, v, c, bof
end
@test kwfun(LongerStruct(4,5,6)) == (4,5,6,5)

@destruct nested(LongerStruct(Ref(Ref(a)))) = a
@test nested(LongerStruct(Ref(Ref(44)), 3, 4)) == 44

@destruct tup_destruct(Ref((a,Ref(b)))) = (a, b)
@test tup_destruct(Ref((1,Ref(2)))) == (1,2)

@d Ref(x) := Ref(111)
@test x == 111

@destruct for (LongerStruct(Ref(xx)), Ref(yy)) in [(LongerStruct(Ref(55), 10, 20), Ref(66))]
@test (xx, yy) == (55, 66)
end

2 comments on commit b79c324

@cstjean
Copy link
Owner Author

@cstjean cstjean commented on b79c324 Nov 3, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/24084

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.6.0 -m "<description of version>" b79c324000d6a4c82ca960b76a87707de0137a86
git push origin v1.6.0

Please sign in to comment.