Skip to content

Commit

Permalink
added inheritance
Browse files Browse the repository at this point in the history
  • Loading branch information
nsiccha committed Apr 3, 2023
1 parent b6b95bd commit e1ed31e
Show file tree
Hide file tree
Showing 4 changed files with 99 additions and 81 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "DynamicObjects"
uuid = "23d02862-63fe-4c6e-8fdb-1d52cbbd39d5"
authors = ["Nikolas Siccha <[email protected]> and contributors"]
version = "0.1.2"
version = "0.1.3"

[deps]
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Implements a dynamic object type which can be redefined with convenient/confusing `.`-syntax and caching.

See [https://nsiccha.github.io/examples/DynamicObjects.jl/](https://nsiccha.github.io/examples/DynamicObjects.jl/) for examples.

```julia
using DynamicObjects

Expand All @@ -21,5 +23,4 @@ A Rectangle(height = 10, width = 20) has an area of 200.

## To add/fix:

* Inheritance (e.g. `@dynamic_object Rectangle <: Polygon height width`)
* Defining a single argument type without a type on that argument (e.g. `@dynamic_object Circle radius`)
27 changes: 0 additions & 27 deletions examples/geometry.qmd

This file was deleted.

148 changes: 96 additions & 52 deletions src/DynamicObjects.jl
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
module DynamicObjects
export DynamicObject, @dynamic_object#, update, cached,
export AbstractDynamicObject, DynamicObject, @dynamic_object, @dynamic_type#, update, cached,
import Serialization

"""
DynamicObject{T}

A `DynamicObject` is a thin named wrapper around a generic NamedTuple which enables function overloading.
The type is inefficient computationally, but can enable more efficient prototyping/development.
"""
struct DynamicObject{T}
nt::NamedTuple
DynamicObject{T}(nt::NamedTuple) where T = new(nt)
end
esc_arg(arg::Symbol) = esc(arg)
function esc_arg(arg::Expr)
if arg.head == :(=)
Expand Down Expand Up @@ -43,75 +34,128 @@ This dynamic type can be used in function definitions as
area(what::Rectangle) = what.height * what.width
```
"""
get_sname(name::Symbol) = QuoteNode(name)
get_sname(name::Expr) = QuoteNode(name.args[1])
ename_and_ebase(name::Symbol, default) = esc(name), esc(default)
ename_and_ebase(name::Expr, default) = esc.(name.args)
macro dynamic_object(name, args...)
sname = QuoteNode(name)
ename = esc(name)
sname = get_sname(name)
ename, ebase = ename_and_ebase(name, :DynamicObject)
# ename = esc(name)
eargs = esc_arg.(args)
kwargs = esc(:kwargs)
argnames = get_arg_symbol.(args)
aargs = [esc(:($(arg)=$(arg))) for arg in argnames]
quote
Base.@__doc__ $ename = DynamicObject{$sname}
$DynamicObject{$sname}($(eargs...); $kwargs...) = DynamicObject{$sname}((
Base.@__doc__ $ename = $ebase{$sname}
$ebase{$sname}($(eargs...); $kwargs...) = $ebase{$sname}((
$(aargs...), $kwargs...
))
end
end
Base.propertynames(value::DynamicObject) = propertynames(value.nt)
function Base.getproperty(value::DynamicObject, name::Symbol)
if name == :nt
getfield(value, name)
else
if hasproperty(value.nt, name)
getproperty(value.nt, name)
else
getfield(Main, name)(value)

abstract type AbstractDynamicObject end

macro dynamic_type(name)
ename, ebase = ename_and_ebase(name, :AbstractDynamicObject)
NT = esc(:NamedTuple)
Base = esc(:Base)
quote
Base.@__doc__ struct $ename{T} <: $ebase
nt::$NT
# $ename{T}(nt::$NT) where T = new(nt)
end
$Base.propertynames(what::$ename) = propertynames(what.nt)
function $Base.getproperty(what::$ename, name::Symbol)
if name == :nt
getfield(what, name)
else
if hasproperty(what.nt, name)
getproperty(what.nt, name)
else
getfield(Main, name)(what)
end
end
end
$ename{T}(what::$ename) where T = $ename{T}(what.nt)
$ename{T}(;kwargs...) where T = $ename{T}((;kwargs...))
$Base.show(io::IO, what::$ename{T}) where T = print(io, T, what.nt)
$Base.merge(what::$ename, args...) = typeof(what)(merge(what.nt, args...))
update(what::$ename; kwargs...) = merge(what, (;kwargs...))
update(what::$ename, args...) = merge(what, (;zip(args, getproperty.([what], args))...))
$Base.hash(what::$ename{T}, h::Int=0) where T = hash((what.nt, T, h))
end
end
# DynamicObject{T}(nt::NamedTuple) where T = DynamicObject{T}(nt)
DynamicObject{T}(what::DynamicObject) where T = DynamicObject{T}(what.nt)
DynamicObject{T}(;kwargs...) where T = DynamicObject{T}((;kwargs...))

"""
DynamicObject{T}
A `DynamicObject` is a thin named wrapper around a generic NamedTuple which enables function overloading.
The type is inefficient computationally, but can enable more efficient prototyping/development.
"""
@dynamic_type DynamicObject
# struct DynamicObject{T}
# nt::NamedTuple
# DynamicObject{T}(nt::NamedTuple) where T = new(nt)
# end

# Base.propertynames(value::DynamicObject) = propertynames(value.nt)
# function Base.getproperty(value::DynamicObject, name::Symbol)
# if name == :nt
# getfield(value, name)
# else
# if hasproperty(value.nt, name)
# getproperty(value.nt, name)
# else
# getfield(Main, name)(value)
# end
# end
# end
# # DynamicObject{T}(nt::NamedTuple) where T = DynamicObject{T}(nt)
# DynamicObject{T}(what::DynamicObject) where T = DynamicObject{T}(what.nt)
# DynamicObject{T}(;kwargs...) where T = DynamicObject{T}((;kwargs...))
# Base.show(io::IO, what::DynamicObject{T}) where T = print(io, T, what.nt)
# Base.merge(what::DynamicObject, args...) = typeof(what)(merge(what.nt, args...))
# update(what; kwargs...) = merge(what, (;kwargs...))
# update(what, args...) = merge(what, (;zip(args, getproperty.([what], args))...))
# Base.hash(what::DynamicObject{T}, h::Int=0) where T = Base.hash((what.nt, T, h))


# function cached(what::DynamicObject, key)
# if hasproperty(what, key)
# # println("LOADING FROM DynamicObject")
# getproperty(what, key)
# else
# if !isdir("cache")
# mkdir("cache")
# end
# file_name = "cache/$(key)_$(what.hash)"
# if isfile(file_name)
# # println("LOADING FROM FILE!")
# Serialization.deserialize(file_name)
# else
# # println("COMPUTING")
# rv = getproperty(what, key)
# Serialization.serialize(file_name, rv)
# rv
# end
# end
# end

# DynamicObject{T}() where T = DynamicObject{T}(NamedTuple())
# default(what, name) = missing
# getprop(what, name, def=default(DynamicObject, name)) = hasproperty(what, name) ? getproperty(what, name) : def
Base.show(io::IO, what::DynamicObject{T}) where T = print(io, T, what.nt)
Base.merge(what::DynamicObject, args...) = typeof(what)(merge(what.nt, args...))

# Base.length(what::DynamicObject) = 1
# Base.size(what::DynamicObject) = ()
# Base.getindex(what::DynamicObject, i) = what
# Base.iterate(what::DynamicObject) = iterate([what])
# Base.merge(what::DynamicObject, arg1::DynamicObject, args...) = typeof(what)(merge(what.nt, arg1.nt, args...))
# igetproperty(obj, sym) = getproperty(obj, sym)
# igetproperty(obj, sym, args...) = igetproperty(getproperty(obj, sym), args...)
update(what; kwargs...) = merge(what, (;kwargs...))
update(what, args...) = merge(what, (;zip(args, getproperty.([what], args))...))
# update_default(what, args...) = update(wha)

# what = DynamicObject
Base.hash(what::DynamicObject{T}, h::Int=0) where T = Base.hash((what.nt, T, h))

function cached(what::DynamicObject, key)
if hasproperty(what, key)
# println("LOADING FROM DynamicObject")
getproperty(what, key)
else
if !isdir("cache")
mkdir("cache")
end
file_name = "cache/$(key)_$(what.hash)"
if isfile(file_name)
# println("LOADING FROM FILE!")
Serialization.deserialize(file_name)
else
# println("COMPUTING")
rv = getproperty(what, key)
Serialization.serialize(file_name, rv)
rv
end
end
end
# update_cached(what, args...) = merge(what, (;zip(args, cached.([what], args))...))
# Plots.plot!(p, what) = Plots.plot()
# Plots.plot(what::DynamicObject{T}) where T = Plots.plot!(Plots.plot(), what)
Expand Down

2 comments on commit e1ed31e

@nsiccha
Copy link
Owner Author

@nsiccha nsiccha commented on e1ed31e Apr 3, 2023

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/80878

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 v0.1.3 -m "<description of version>" e1ed31e675d0c584edd1a2fac5c3490968aad37d
git push origin v0.1.3

Please sign in to comment.