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
T.new finds #initialize of T's child when T is a parameter #3989
Comments
Right now when you have a generic type, for example class Bar < Foo
end
a = [] of Foo
a << Bar.new
a[0].foo # will look up `foo` in Foo and Bar That's why I don't think we'll change this, but I'll discuss it with the team. |
That certainly makes sense for Just for my own edification, am I correct in thinking that this can only happen with |
@ezrast That's correct, That said, why do you need this? What's your use case behind this? |
tl;dr: It's necessary for any collection type that needs to be responsible for instantiating its own members. Lengthy example: If the instrumentation library I'm implementing were specced out differently, I may have written something a little like this: # metric type that stores count and sum of observed values,
# allowing for a rolling average to be computed
class Summary
getter count = 0
getter sum = 0.0
def observe(value : Float64)
@count += 1
@sum += value
end
end
# example usage:
s1 = Summary.new
[1.0, 2.0, 4.0, 7.0].each { |value| s1.observe value }
s1.count # => 4
s1.sum # => 14.0
# as Summary, but also tracks observation counts on a per-bucket basis
class Histogram < Summary
getter buckets = {} of Float64 => Int32
def initialize(bucket_bounds : Array(Float64))
bucket_bounds.each do |bound|
@buckets[bound] = 0
end
end
def observe(value : Float64)
super
@buckets.each_key do |bound|
@buckets[bound] += 1 if value <= bound
end
end
end
# example usage:
h1 = Histogram.new([1.0, 5.0, 10.0])
[2.0, 4.0, 7.0, 11.0].each { |value| h1.observe value }
h1.buckets # => {1.0 => 0, 5.0 => 2, 10.0 => 3}
h1.count # => 4
h1.sum # => 24.0
# sometimes metrics of the same type need to be grouped
# together, tagged with arbitrary String labels
class LabeledMetric(MetricType)
@metrics = Hash(String, MetricType).new
def initialize(*args, **kwargs)
# MetricType.new has to be called from inside this class,
# because it's a requirement that all metrics in the collection
# be initialized in the same way - i.e. histograms must all
# use the same buckets.
@metrics = Hash(String, MetricType).new do |hh, kk|
hh[kk] = MetricType.new(*args, **kwargs)
end
end
def [](label)
@metrics[label]
end
end
# example usage:
h2 = LabeledMetric(Histogram).new([1.0, 5.0, 10.0])
h2["foo"].observe 10.0
h2["foo"].observe 20.0
h2["bar"].observe 50.0
h2["foo"].count # => 1
h2["bar"].count # => 2
# but this doesn't work:
# s2 = LabeledMetric(Summary).new This isn't actually blocking me in real life, because in the real library |
@ezrast Hey, that's a really nice use case, and it shows how sometimes one can be very expressive in the language. I don't know if other statically typed languages allow something like the above. The good news is that this is fixable. Even though a generic container should allow subtypes, when you write |
This appears related to #2665. Feel free to mark as dupe.
The compiler won't let me instantiate a parameter type whose child defines
#initialize
with different arguments:Compiler output is
wrong number of arguments for 'Derived#initialize' (given 0, expected 1)
even thoughDerived#initialize
should never get called, unless there's a nuance to the type system I'm missing.The text was updated successfully, but these errors were encountered: