I feel an excellent feature to Crystal would be the ability to create overloadable implicit type casts for any class. This would reduce the amount of boilerplate overloaded methods with slightly different parameter types simply to support extra types.
Consider this example:
class Foo
def initialize(@bar : Bar)
end
def implicit : Bar
@bar
end
end
class Bar
def initialize(@message : String)
end
def say
puts @message
end
end
def baz(bar : Bar)
puts bar.say
end
bar = Bar.new "Hello, world"
baz(bar) #=> Hello, world
# Passing an object of type `Bar` to `baz` yields expected functionality
foo = Foo.new bar
baz(foo) #=> Hello, world
# Passing an object of type `Foo` (which shares no common inherited ancestor
# with `Bar`) internally calls the `#implicit` method which determines how
# the type should mutate with the expected type.
# Thus, `baz(foo)` is identical to `baz(foo.implicit)`
In this example, there exists two types, Foo
and Bar
. The top-level method baz
is responsible for invoking Bar#say
. It is clear that baz
will not accept Foo
any time, as Foo
does not share any inherited ancestors with Bar
, and no overloads exist for baz
that accept a Foo
directly.
However, with the proposition of overloadable implicit type casts, Foo
can define a sort of “implicit” method that explains to the compiler how the object should act as if it were used as another type. In this case, Foo
has an instance variable, bar
, that defines a clear instance of type Bar
. Additionally, Foo
defines a type-annotated implicit
method that returns its instance variable of bar
, so that when it is used in an implicit type cast, it pulls this value from its defined implicit type cast method.
This proposition would reduce the amount of redundant checks to responds_to?
, and reduce the amount of overloaded methods with slightly different parameter types.
This feature is found in several other languages with well-defined approaches to OOP. For example, C# allows you to overload implicit type casts as an operator:
public static implicit operator Bar(Foo foo) {
return foo.Bar;
}
Additionally, C# also allows you to specify explicit
over implicit
to provide behavior when explicitly casting. (Bar) foo
would yield this functionality.