Reputation: 1209
If I have a function which accepts an argument of multiple types, how can I enforce that the return must match the value of the input?
This comes up particularly when I want a method to work with any children of a parent type. For demonstration, consider something that is "barlike":
abstract struct Barlike
property bar: Int32
def initialize(@bar); end
end
abstract def make_clang_sound
abstract def serve_drinks
end
Now any struct can implement those two methods, and store that value
struct Bar1 < Barlike
def make_clang_sound
puts "bing bang bong"
end
def serve_drinks
puts "your drink sir"
end
end
struct Bar2 < Barlike
def make_clang_sound
puts "kling klang"
end
def serve_drinks
puts "here are your drinks"
end
end
Now what if I have a method that wants to use the bar and return a new one with an updated value (these are structs afterall):
def foo(arg : Barlike)
new_bar = arg.bar + 2
arg.class.new(new_bar)
end
this will return a Bar1
if a Bar1
is passed in and a Bar2
if that is passed in but it's not guaranteed:
def foo(arg : Barlike)
"howdy"
end
I'm going to be putting my foo
into an abstract structure as well, so I need to guarantee that implementers of foo
return the same type of Barlike
that was given.
I tried
def foo(arg : Barlike) : arg.class
end
But that's a compile time error (arg cannot be used there like that)
I also tried
def foo(arg : Barlike) : typeof(arg)
end
which passes, but typeof here is just Barlike whereas I really need it to be only the thing that was passed in, only Bar1 or Bar2 and so on.
Can macros help?
Upvotes: 1
Views: 86
Reputation: 188
Here is something that works:
{% for sub in Barlike.subclasses %}
struct {{sub}}
def foo() : {{sub}}
{{sub}}.new(@bar+1)
end
end
{% end %}
But it feels like this is trying to solve the wrong problem. It uses the #subclasses
macro to generate a foo for all of the child structs.
You could also declare these as self methods inside the abstract class: example.
Upvotes: 1
Reputation: 5661
The tool for this are free variables. That's essentially generics scoped to a single method.
# This method returns the same type as its argument
def foo(arg : T) : T forall T
arg
end
This would already solve the main part of your question.
However, it is currently not possible to apply type restrictions to free variables, for example restricting T
to Barlike
.
There are workarounds, though:
def foo(arg : T) : T forall T
{% raise "arg must implement Barlike" unless T < Barlike %}
arg
end
def foo(arg : T) : T forall T
foo_impl(arg)
end
private def foo_impl(arg : Barlike)
arg
end
Both workarounds affect the implementation of the method. There is no way to specify such a type restriction for a abstract def
. Number 2 might be feasible, if you make foo_impl
abstract and require inheriting classes to implement this one, instead of foo
.
But it's probably also fine to just go with the initial example using free variables, without the Barlike
restriction. In practice, you probably don't gain much.
Upvotes: 3