-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
RFC: Macro for delegating methods to the fields of a composite type #3292
Conversation
@kmsquire: This one will be in RFC for a long time I suspect. It would be good to hear your opinion. |
For the patch itself, it's easy to understand and very clean--nice job! I'll try to get an example mocked up of a place where this would be useful-- |
+1 |
@JeffBezanson, any thoughts on this? I'm somehow not entirely sold on this, but I can't put my finger on why. |
To be clear, I think this is a good implementation of the delegation approach we discussed. I'm just not entirely certain this is how we should handle delegation in the language. It feels kind of ad hoc and as though we should provide something more powerful in the language. |
I totally agree. This is way too special-cased. It only looks clean for functions with exactly this argument structure: otherwise it's totally broken. I just wanted to stir up discussion with something definite. |
I've been thinking about this a little lately. It seems like it would be more convenient to be able to delegate from within a type definition. I have no idea if this is feasible or potentially destructive, but it'd be convenient to be able to note than a particular field should be "promoted" per se to be used automatically by methods calling the composite type. In the original case here, when |
It would be cool if delegation supported a use-case like
It could be done with zero overhead, since we could already do
and would be convenient over giving a new definition of getindex for each integer index wrapper type. |
Couldn't the macro use |
We could do that. It would certainly make this macro more useful. |
+1 for the @karbarcca I brought up basically that idea with @StefanKarpinski once and he expressed some skepticism, as well as pointing out that this has some implications with respect to type inference b/c calls that you could previously infer to be no method errors now sometimes will succeed. |
I confused as to how type inference is affected. The proposed macro is equivalent to just declaring a bunch of methods manually, after all. |
@stevengj sorry, I was talking about two different things above, The macro involving The other thing I was talking about was something similar to @karbarcca's idea, which (as I understand it) is to change the language so you can declare some field of a type to be it's delegate, a la: type MyMatrixWrapper
delegate data::Matrix
info::Dict
# whatever other fields you need here . . .
end and then whenever a call like: f(A::MyMatrixWrapper, x::Int, y::Int) would throw a no method error, this: f(A.data, x, y) would be tried instead, which as I understand it would affect type inference because things that used to be inferred to throw no method errors now sometimes will succeed. |
I also like @stevengj idea: as long as the macro simply expands to a tedious bit of existing code, it can't have any bad effects that weren't already possible. And it will make the simplest kind of delegation pattern, wrapping a single type in a new immutable, a lot easier. |
The only potential downside to the module A: module A
type MatrixWrapper
data::Matrix
# other fields
end
@autodelegate MatrixWrapper :data
# functions specific to MatrixWrapper
end module B: module B
function f(x::Matrix)
# some B-specific Matrix operations
end
end Here calling using A
using B
A = MatrixWrapper(...)
f(A) #throws no method
Whereas here it works: using B
using A
A = MatrixWrapper(...)
f(A) #works as you would expect
This is a pretty small issue I think, but it is potentially confusing. |
I guess that we are talking about On Sat, Feb 8, 2014 at 7:15 PM, James J Porter notifications@github.comwrote:
|
It would be useful for some abstractions (e.g. JuliaData/DataFrames.jl#571) if the following could be made to work: type Wrapper{T}
X::T
end
@delegate Wrapper.X
sum(Wrapper([1, 2, 3])) == 6 As far as I can tell, this is different from the above in that we don't know the types that we'll need to delegate until a A better approach might be to have some kind of catch-all staged method that gets called for a specific method signature regardless of the method name. I'm not sure if that can be done, but it's also another possible approach to #1974 that would admit both Julia-style dispatch and dispatch with the |
I've been thinking about this a little more lately. I wonder if the concept of typealiases could have a "stronger" brother ( typepeer MyIntArray Array{Int,1}
a = [1:10]
b = MyIntArray(a)
sum(b) == 55 The idea being that there's an official way to wrap another type and automatically get all it's behavior. It's stronger than a Though I pause to wonder if this is actually needed that much and if the subtyping of concrete types would be too heretical. |
@karbarcca the problem I see with that approach is that I often find when I write wrappers I want to add an extra field (a prominent example is DataArrays adding the missing data bitmask). |
@StefanKarpinski has stated pretty unambiguously in the past that subtyping concrete types will never be allowed since it prevents the compiler from inferring the size of something once it's concrete type is known, which is a good argument. I proposed the approach of declaring one field of a type to be it's delegate, and then having any method lookup that would be fail be retryed using a type's delegate in it's place if the type has one, to which Stefan responded it would be pretty hard to convince him/Jeff it was a good idea, at which point I stopped pushing the issue :) |
@porterjamesj, if you're wanting to add a field, then it isn't really a case of the kind of "pure wrapping" that I was aiming at (though I realize this issue is probably more about your case); the case I'm talking about is a pure wrapper, i.e. a |
If I am understanding correctly, the key to this proposal is that |
@karbarcca there was some discussion about the |
Ha, @mauro3, thanks for the link. Crazy that we suggested almost the exact same thing. Thanks for the link @kmsquire as well. It seems like there may be some wiggle room in between a I guess the other problem is I don't really understand the implementation details of the type hierarchy and dispatch. It seems the plumbing would be too messed up to flag a concrete type as a parent of another, even if the children were guaranteed the same memory layout/fields/etc. |
This branch is very badly out-of-date, but I suspect we still are interested in eventually coming up with an effective API for doing delegation. Not sure whether to close or keep open. |
Sounds like, even if not immediately, this will be taken care of with #1470. |
@quinnj, how's that? |
Yup, thanks Jacob! |
I vote we close this. |
One vote's enough for me. |
Sorry to revisit a closed issue, but can anyone say what the status of this is? As far as I can tell, the language features that were envisioned to support delegation (namely the ability to subtype functions and overload |
@quinnj could we reopen this PR? (the original patch works with only a minor change) Or I can make a new one and add docs and tests. Now that macros are generic, we can cover other corner cases more easily. |
Something like this I mean: using Base.Meta: quot
macro delegate(source::Expr, targets::Expr)
source.head ≠ :. && error("syntax: expected `Foo.bar` form")
targets.head ≠ :vect && error("syntax: expected vector of function names")
type_name = source.args[1]
field_name = source.args[2].value
func_names = targets.args
n = length(func_names)
block = Expr(:block)
for func_name in func_names
expr = quote
function $func_name(a::$type_name, args...)
$func_name(a.$field_name, args...)
end
end
push!(block.args, expr)
end
return esc(block)
end
macro delegate(source::Expr, target::Symbol)
source.head ≠ :. && error("syntax: expected `Foo.bar` form")
type_name = source.args[1]
field_name = source.args[2].value
:($target(a::$type_name, args...) = $target(a.$field_name, args...)) |> esc
end I'd just need the syntax you guys expect in all corner cases, to implement it. Julia 1.0 is on the horizon and I really think we should make composition via delegation, easier, more julian. |
Feature freeze for 1.0 is December 15th, so we're not adding any more features to the release at this point without very significant justification. However, new features can be added in 1.x versions as long as they don't break older code. |
As discussed long ago on the mailing list (https://groups.google.com/d/msg/julia-dev/MV7lYRgAcB0/s6_RMeHLpEYJ), I've created a simple
@delegate
macro that allows one to delegate methods to the fields of a composite types. An example is shown below in which a new container type can delegate methods likesize
andlength
to an array contained inside of it:Right now, this only supports functions for which the first argument is the field which delegated methods need. Before we merge this, I think we'll want to find a clean generalization.