You could write a macro to do something a bit like this, although it hits a bunch of problems with figuring out which symbols in the expression could be fields in the struct: a macro canât know that information, so youâd need purely syntactic rules to determine whether somethingâs a field. In particular, how do you handle the case in which the struct contains a function in it?
with x do
field3 = field4(field1, exp(field2))
end
I think PatameterisedModule.jl by @thautwarm may do what you want. It may be a bit too fancy for your uses though. I certainly donât really âgetâ it as I never used Ocaml.
Edit: Nevermind, it appears to only support immutable types if I understand correctly.
Iâm giving the user more options, so they can think about how they want to write their program. The functor looks pretty close to the x = compositetype(1,2) line in the OP (and so does an outer constructor!).
I came up with something here: Unpack named tuples (does anyone know how to do it?) - #13 by Mason but I donât think thereâs any good way to turn a setfield! statement like the one you show: field3 = field1 + field2 into x.field3 = x.field1 + x.field2 so I posted it in the other thread rather than this one.
Not directly. As others have suggested, various approximations exist, but fundamentally this kind of a construct does not fit well into the way Julia works.
In a nutshell, the meaning of field3 etc in a with block would need to depend on the type of x, which may or may not be known at compile time. Consider
function f!(x)
with x do # **** NOTE: hypothetical syntax ****
field3 = field1 + field2
end
x
end
mutable struct Foo
field1
field2
end
mutable struct Bar
field1
field2
field3
end
f!(Foo(1, 2))
f!(Bar(1, 2, 4))
What should f!(Foo(1, 2)) do? Should it treat field3 as a local variable, which is then optimized out as unused? This could lead to very insidious bugs.
If youâre willing to explicitly mark which symbols should be treated as fields, then this is easier to implement. Hereâs a macro which will replace all expressions of the form &a with container.a:
julia> using MacroTools: postwalk
julia> macro with(container, expr)
esc(postwalk(expr) do ex
if @capture(ex, &field_)
:(($container).$field)
else
ex
end
end)
end
@with (macro with 1 method)
Usage:
julia> mutable struct Bar
field1
field2
field3
end
julia> b = Bar(1, 2, 0)
Bar(1, 2, 0)
julia> @with b begin
&field3 = &field1 + &field2
end
3
julia> b
Bar(1, 2, 3)
This implementation doesnât have any problem with local variables which happen to match the names of fields, since the user is responsible (by adding &) for explicitly marking which symbols should be treated as fields:
julia> field1 = Ď
Ď = 3.1415926535897...
julia> @with b begin
# The left-hand side is unambiguously `b.field1`
# and the right-hand side is unambiguously
# the local variable `field1`.
&field1 = field1
end
Ď = 3.1415926535897...
julia> b
Bar(Ď, 2, 3)
functor in C++ is just a callable, thatâs quite a simple thing and not suggestive enough to abstraction tasks in programming.
The category theory has a concept called functor, and specifically, something called endofunctor will always take the place of a general âfunctorâ, in programming fields, because usually only âendofunctorâ is useful for programming.
What I meant by functor is close to the second meaning mentioned above:
When we have a module A(like a category), and a functor F that maps it to another module F(A), where some types (like A.t) in module A will be mechanically mapped to F(A.t), as well as values and functions.
A function f defined in the module A, transforms a value typed A.t1 to a value typed A.t2, it will be mapped correspondingly into module F(A), as a function F(f).
F(f) transforms a value typed F(A.t1) to a value typed F(A.t2).
original
mapped
module
A
F(A)
type
t
F(t)
a functionâs âeffectâ
from t1 to t2
from F(t1) to F(t2)
See Catlab.jl, theyâre professional and they should have shown use cases of this functor.
This one is already available⌠with GeneralizedGenerated.jl, but I hope users do not write code like thisâŚ
Locally and implicitly binding variables from data structures is not a new thing, in some other languages they widely use this.
with! treats the fields as-is(visiting these variables means accessing fields), with unpacks and binds the fields to local variables. This makes sense to me.
Is there a good tutorial on how to do this? In particular, is there a way to evaluate field1 instead of using its variable name as a literal? i.e. x = :field1 and &x still get field1?
No, &x unambiguously means container.x not container.(whatever the value of the variable `x` currently is). This is a purely syntactic transformation, so the values of variables cannot affect the code produced by the macro.
If you want to set a struct field using a name contained in a variable, then you need setproperty! instead.
Thanks! One other question, sorry to bother you. Do you know how to @capture a symbol literal? Like, not any particular symbol literal, but do something if an expression is just a symbol literal.
Hmmm still doesnât work. (Iâm playing around with implementing some DataFramesMeta features to try and learn about it).
If the mods want to break this into a new thread I would appreciate it.
using DataFrames
using MacroTools
using MacroTools: postwalk
macro with(container, expr)
esc(postwalk(expr) do ex
if @capture(ex, &field_)
quote
$field isa Union{<:Integer, <:AbstractString, Symbol} ?
getindex($container, !, $field) :
Tables.columntable(getindex($container, !, $field))
end
elseif @capture(ex, x_Symbol)
:(getindex($container, !, $x))
else
ex
end
end)
end
df = DataFrame(rand(2,2))
@with df begin
:x1
end