-
-
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
default field values #10146
Comments
+1000 I've often written macros to automatically give me both a type definition and an all-keywords constructor function with sane default values. |
This certainly is a useful feature to have. |
same thought recently #6122 (comment) If breaking the current semantics of how |
No, I think we should just bite the bullet and change it. Very little code would get broken. This is used once or twice in base and probably very few other places. |
I agree we should just change it. |
Doesn't this complicate the semantics of inner constructors even more? The distinction between inner and outer constructors is a tricky concept to explain to people unfamiliar with the language. How does this interact with evaluating arbitrary expressions for type fields? type Foo
foo::Int=rand(1:10)
baz::String=randstring()
end I feel allowing side-effects here is a bit weird. |
I assume this would only work for default constructors. |
So the following would retain the behavior we have now (expressions would be ignored)? type Foo
foo::Int=rand(1:10)
baz::String=randstring()
Foo() = new()
Foo(f) = new(f)
end |
Given that the inner/outer constructor business is one of the most complicated parts of the (otherwise reasonably simple) language, I wonder if this is worth the increased complexity? Wouldn't documenting the pattern of creating a no-arg inner constructor suffice? |
I'd argue that the no-arg constructor (which shouldn't be the inner constructor, but only one outer constructor) is an anti-pattern. |
Why not use defaults in |
I'm assuming that the use of keywords in |
Keyword arguments are slow currently, but that's not an inherent aspect of that language feature – they could be made faster with time and effort. |
Keyword arguments in a Currently, I'm pretty sure keywords are only expensive for calls that actually use them. |
I was looking into defaults and keyword constructors just now: Parameters.jl. I came up with this approach to default constructors:
Example: immutable MT{R<:Real}
a::R = 5
b::R
MT(a,b) = (@assert a>b; new(a,b))
end would become immutable MT{R<:Real}
a::R
b::R
MT(a,b) = (@assert a > b; new(a,b))
# This is now chained to above constructor:
MT(; a=5,b=error("Field '" * "b" * "' has no default, supply it with keyword.")) = (MT{R})(a,b)
end
MT{R<:Real}(a::R,b::R) = MT{R}(a,b) # default outer positional constructor.
# These two create new instances like pp but with some changed fields:
MT(pp::MT; kws...) = reconstruct(pp,kws)
MT(pp::MT,di::Union(Associative,((Symbol,Any)...,))) = reconstruct(pp,di) |
I'm pretty adamant that if any user-defined constructors are present, no default constructors should be added. Among other issues, if you don't want them, how do you get rid of them? |
+1 to what Jeff said. When creating type, one of the biggest design On Tue, Feb 10, 2015 at 2:08 PM, Jeff Bezanson notifications@github.com
|
Fair enough. But wouldn't the same argument also apply to |
I think the simplest, most conservative design is to allow default values only if the default constructors are used. Specifying both default values and a user-defined constructor would be an error. The next step in complexity is to allow user-defined constructors, and have default values act as positional default arguments to |
I will appeal to my hugely breaking and never going to be implemented desire to do away with inner constructors entirely. I feel that this issue is a manifestation of the current system being a bit magical. Users expect it to work because they cannot figure out the current implementation. This is unsurprising as it is hidden from them. The simplest design would be to have type syntax be purely declarative (describing what something is) and use outer constructors to create instances of these types (describe how to construct it). I feel this would be much simpler to teach to people new to the language as it removes all magic. Users could reclaim the default behavior of auto generated constructors through macros as it is a purely syntactic transformation. This makes the generation of these default constructors opt-in as opposed to opt-out. It also simplifies scoping issues with I admit that declaring a type and not immediately be able to do something with it defeats the interactive feel of the language. It makes code a bit more verbose and repetitive. This proposal also complicates some optimizations that are currently possible with the current system (such as statically removing #undef checks in type construction and field access). One of the arguments in favor of the current inner / outer distinction is that it enables a way to express invariants. It is currently the only way to express that a certain subset of methods with given arguments / argument types cannot be overridden or extended in the system. To me, the ability to "seal" methods is broadly useful. It seems odd we could potentially have two different ways of expressing the same concept if we gain the ability to seal general methods in the future. I don't really know why I wrote this, but I feel that we are moving farther away from this ideal. |
+1 to what Jake said. While I'm sure there are (possibly On Tuesday, February 10, 2015, Jake Bolewski notifications@github.com
|
+1 to jake! #Obviously only for mutables
function Base.call{T}(x::Type{T}; data...)
instance = new(T)
for (fieldname, value) in data
instance.(fieldname) = value
end
instance
end Similarly this can be implemented for immutables with some added overhead. |
I'm glad you wrote it. The inner/outer constructor business is one of my least favorite things. It always confuses people. The fact that the inner constructor |
Silly me, my default keyword constructor obviously doesn't solve the problem of default values for fields and just gives an order independent way of constructing types. |
@SimonDanisch my proposal would be to overload Ex. type Foo
bar::Int=0
baz::String=""
end would be expressed as type Foo
bar::Int
baz::String
end
Foo(bar::Int=0, baz::String="") = new(Foo, bar, baz) Obviously more verbose, but much more explicit. I feel that we could largely reclaim the performance issues if we just mandated that |
Yes, something like this seems reasonable. immutable Field{Sym} end
Field(s::Symbol) = new(Field{s}) # "unused" parameter is now part of the type |
I'm not willing to give up enforced invariants. It also doesn't seem necessary at all.
and "outer" constructors have the form
Please read #8135 for a good discussion. My comment #8135 (comment) shows how to use outer-only constructors but keep enforced invariants. All the syntax mentioned there works already. If people want to switch to explicit |
I mean if it's ready then the solution would be to just PR Parameters.jl into Base IMO. It's a very solid and stable package which is pretty widely used, and solves the problem very well. If you need the functionality right now, there's no reason to avoid it. |
I thought d5ad19a already had 🙂 Lines 776 to 793 in a593118
|
Ahh, seems you are right. Haha, forgot I was working in 0.5.2 |
Can this issue be closed then? Perhaps export julia> Base.@kwdef mutable struct Foo
bar::Int
baz::String=""
end
Foo
julia> Foo()
Foo(0, "") |
I was under the impression that a non-macro based solution was aimed for. If macros are ok, porting parts of Parameters.jl to Base could work. Note that there is quite a bit more to a full implementation than what
|
In my opinion this feature should be supported language wise and not using a macro. Its not intuitive that one has to put |
Yes, this is a feature that people just intuitively expect to work – I've witnessed this dozens of times in person over the years. Requiring a macro for it to work is unnecessarily ugly. |
I was just suggesting that what Parameters.jl already does could be ported to Base, though with the macro essentially being implicit. I agree it shouldn't need a macro and this should be very standard functionality for type definitions. |
The real question is what does it mean for a field to have a default value? At the very minimum, I would argue that calling |
This came up "in the wild" again: https://discourse.julialang.org/t/default-values-for-composite-types-in-julia/4479 @juliohm has an interesting question because he wants type parameters to work. Since Parameters.jl uses keyword arguments, that won't work out all too well with the current setup, though #16580 would solve that issue. But it won't solve the issue that maybe a user may want to just set the "type", i.e. for immutable GaussianVariogram{T<:Real}
sill::T -> one(T)
range::T -> one(T)
nugget::T -> zero(T)
end it feels like
I agree, though would it be an issue that then there is no way to have a constructor with uninitialized fields? To me it sounds like a weird edge case that probably doesn't need special syntax to cover (example: |
It does: julia> using Parameters
julia> @with_kw struct Blag{T}
x::T=one(T)
y::T=one(T)
end
Blag
julia> Blag{Float64}()
Blag{Float64}
x: Float64 1.0
y: Float64 1.0 |
Wow cool, never knew that. Though it does have the lack of inferrability: julia> @with_kw struct Blag{T}
x::T=one(T)
y::T=one(T)
end
Blag
julia> @code_warntype Blag{Float64}()
Variables:
#self#::Type{Blag{Float64}}
Body:
begin
return ((Core.getfield)($(QuoteNode(Core.Box(#call#1))), :contents)::Any)((Base.sitofp)(Float64, 1)::Float64, (Base.sitofp)(Float64, 1)::Float64, #self#::Type{Blag{Float64}})::Any
end::Any |
It doesn't for me: using Parameters
@with_kw immutable Foo{T<:Real}
a::T = one(T)
end
Foo() EDIT: |
@StefanKarpinski: I assumed that this issue would be solved by automatically generating a keyword constructor rather than modifying For below type this would mean struct A
a
b=5
end What would new(a, b) = ...
new(a; b=5) = ...
new(;a=undef, b=5) = ... with the automatically defined inner constructors: A(a,b) = new(a,b)
A(;kws...) = new(;kws...) |
I'm changing the milestone to 1.x since this seems like a feature that we can add post 1.0 as long as we've made the appropriate things errors in advance. |
Is it clear if the appropriate things error in the current state of master, or if something needs to be prepared for this to be simple to do post v1.0? |
It's a syntax error now, so we're all good: julia> struct Foo
a::Int = 0
end
ERROR: syntax: "a::Int = 0" inside type definition is reserved |
Could default field values be part of Julia v1.3? I wonder if we can also have the |
Since the feature freeze is today, no. But if someone implements it in the next four months, it could be in the 1.4 release. |
For v1.6? |
This comes up periodically since people seem to expect it to work:
Currently, this doesn't do what people expect it to do at all, but I suspect we should change that. This would be a useful feature and the fact that so many people expect it seems like a pretty strong argument in its favor.
The text was updated successfully, but these errors were encountered: