Call a method on parent thats also defined on the child from a different method

I can’t seem to figure out a way to call a parent class’s implementation of a method from a different method and when that parent method is also redefined in the child. For example:

abstract class Foo
  def one
    "One"
  end
 
  def two
    "Two"
  end
end
 
class Bar < Foo
  def three
    one
  end
 
  def one
    "1"
  end
end
 
b = Bar.new
 
# This should print One
pp b.three

https://play.crystal-lang.org/#/r/bfrz

You can’t just use self.one as that uses the child’s implementation. ::one doesn’t work as it looks for it on the top level. super.one errors with undefined method 'three' for Object (although this does work in Java). In PHP you could do parent::one(); , which is basically the same as Java with a different keyword.

We probably should allow calling a different method on super versus just having it assume the method you want is the one you’re calling it from?

You can’t do this either in Ruby. The way to do it is to make one call a helper method, and then call that helper method from the child class.

This feels kinds hacky, but if you make Foo to be not abstract and if you don’t mind ‘escaping’ the ‘instance’ level of your Bar object, then you can do {{@type.superclass}}.new.one as noted in Carcin . You could maybe do something like {{@type.superclass}}.new(self.param_xyz).one if you want to keep some instance data, but that still seems a bit hacky.

1 Like

Is there a reason this isn’t supported? Or just something that hasn’t been implemented yet?

This is definitely hacky ha. Fortunately in my case the parent method was pretty trivial, so i just copied the code from there into the child directly.

The reason is Ruby: you can’t do it in Ruby. In my mind whenever you need to do this is a small. Could you expand more on the real use case?

The gist of it is I was porting some PHP code and that’s how they had things setup. Specifically symfony/ConsoleSectionOutput.php at 5.4 · symfony/symfony · GitHub.

The parent abstract type defines write and writeln akin to our print and puts methods as well as an abstract doWrite that defers how to do the writing depending on the child. In this case the direct parent type is for stream (IO) based output which will write the data to the stream.

The type from the link above extends the stream based type overriding doWrite as it has some additional logic within it. In some cases it calls parent::doWrite("some string", false); to simply write the data, avoiding the extra logic in the child type. It also doesn’t use write or writeln as those do some additional formatting to the data and would still invoke the child’s implementation. Because the content is already in its final form it can just utilize the parent’s implementation.

Granted in this case it’s not a big deal as the parent do_write in Crystal land is just new_line ? @io.puts(message) : @io.print(message). But I don’t see why it shouldn’t be a feature just because it’s not in Ruby. We already have the super keyword. Continue to have super ... refer to the method it’s called in but also allow calling methods on it like super.do_write "foo", false.

1 Like

If there are any positional parameters in the defs, a different hack is to give them different names:

abstract class Foo
  def one(x)
    "One"
  end
end
 
class Bar < Foo
  def three
    one(x: ...)
  end
 
  def one(y)
    "1"
  end
end

Bar.new.three # => "One"

It turns out you can with some reflection:

class Foo
  def one
    "One"
  end
 
  def two
    "Two"
  end
end
 
class Bar < Foo
  def three
    self.class.superclass.instance_method(:one).bind(self).call
  end
 
  def one
    "1"
  end
end
 
b = Bar.new
 
p b.three # => "One"

you can’t invoke super.one even you define three in parent class.

be cause super in ruby (so should in crystal too) just invoke same name method in parent class with same args, it not a parent class object?

I’m not sure I agree with the argument that something in Crystal should behave in a particular way just because that’s how it works in Ruby. Crystal and Ruby are separate languages, so we should look at the other points of view when considering if a feature should or should not be added.

Other languages support it, so it’s not out of the question to want this feature in Crystal too. Granted it’s not super critical/often needed, but still could be a nice to have just to avoid the extra boilerplate of needing another helper method.

2 Likes

I like the idea having this in crystal too. I had the need one time.

I wonder what would be the syntax?

The most intuitive is super.one() but super alone is already taken. But maybe we could allow it and force using (super).foo to disambiguate ? (this would be a breaking change though…, but for 2.0?)

Probably could do something like seeing the method call has a receiver of super and parse that differently than when its by itself/a simple call?

2 Likes

Please check Inheritance - Crystal

Sure it need following the design from ruby, because it involves others things in ruby

Dart use super.one() hack, when i start o write flutter the first time, i even misunderstood the means of super.

anyway, i don’t think crystal need/will change this in the future, it just work like this.

That totally make user confusing … :rofl: