Last active
February 26, 2021 03:28
-
-
Save asterite/d11edd5ee9fd8990fe139dafe2c855b5 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# One can write a class that wraps a Proc and calls it: | |
class Handler | |
def initialize(&@proc : -> String) | |
end | |
def call | |
@proc.call | |
end | |
end | |
handler = Handler.new { "hello" } | |
handler.call # => "hello" | |
# What if we want to pass any object that responds to `call` to Handler, | |
# as long as it returns a String? | |
# | |
# ... | |
# | |
# How can we do it? | |
# | |
# ... | |
# | |
# We can wrap the object in a Proc! | |
class Handler | |
def initialize(obj) | |
@proc = Proc(String).new { obj.call } | |
end | |
def call | |
@proc.call | |
end | |
end | |
class AnyObj | |
def call | |
"hi!" | |
end | |
end | |
handler = Handler.new(AnyObj.new) | |
handler.call # => "hello" | |
# With that idea, we can actually store any object that responds to any | |
# number of methods we want! | |
# This is the interface that we want to represent | |
module Interface | |
abstract def double(x : Int32) : Int32 | |
abstract def string(x : Int32) : String | |
end | |
class Handler | |
def initialize(obj) | |
@double = Proc(Int32, Int32).new { |x| obj.double(x) } | |
@string = Proc(Int32, String).new { |x| obj.string(x) } | |
end | |
def double(x) | |
@double.call(x) | |
end | |
def string(x) | |
@string.call(x) | |
end | |
end | |
class ObjOne | |
def double(x) | |
x * 2 | |
end | |
def string(x) | |
"ObjOne: #{x}" | |
end | |
end | |
class ObjTwo | |
def double(x) | |
x * 2 * 2 | |
end | |
def string(x) | |
"ObjTwo: #{x}" | |
end | |
end | |
handler = Handler.new(ObjOne.new) | |
handler.double(10) # => 20 | |
handler.string(20) # => "ObjOne: 20" | |
handler = Handler.new(ObjTwo.new) | |
handler.double(10) # => 40 | |
handler.string(20) # => "ObjTwo: 20" | |
# Using macros, we can abstract the concept of an interface, or at least | |
# something that wraps any object that responds to a series of methods. | |
# Then we can use that name to refer generally to any object that responds | |
# to that interface. | |
# This is the interface that we want to represent | |
module Interface | |
abstract def double(x : Int32) : Int32 | |
abstract def string(x : Int32) : String | |
end | |
macro def_interface(name, *methods) | |
class {{name}} | |
def initialize(obj) | |
{% for method in methods %} | |
@{{method.name}} = | |
Proc({{(method.args.map(&.restriction) + [method.return_type]).join(", ").id}}) | |
.new do |{{*method.args.map(&.name)}}| | |
obj.{{method.name}}({{*method.args.map(&.name)}}) | |
end | |
{% end %} | |
end | |
{% for method in methods %} | |
def {{method.name}}({{*method.args}}) : {{method.return_type}} | |
@{{method.name}}.call({{*method.args.map(&.name)}}) | |
end | |
{% end %} | |
end | |
end | |
def_interface(Handler, | |
abstract def double(x : Int32) : Int32, | |
abstract def string(x : Int32) : String) | |
class ObjOne | |
def double(x) | |
x * 2 | |
end | |
def string(x) | |
"ObjOne: #{x}" | |
end | |
end | |
class ObjTwo | |
def double(x) | |
x * 2 * 2 | |
end | |
def string(x) | |
"ObjTwo: #{x}" | |
end | |
end | |
handler = Handler.new(ObjOne.new) | |
handler.double(10) | |
handler.string(20) | |
handler = Handler.new(ObjTwo.new) | |
handler.double(10) | |
handler.string(20) | |
# For example, let's say we want to capture any object that | |
# responds to `greet(String) : String`. | |
def_interface(Greeter, | |
abstract def greet(name : String) : String) | |
class GreeterHolder | |
def initialize(greeter) | |
@greeter = Greeter.new(greeter) | |
end | |
def greet(name : String) : String | |
"I'm a holder and I see: #{@greeter.greet(name)}" | |
end | |
end | |
class PoliteGreeter | |
def greet(name : String) : String | |
"Hello, #{name}" | |
end | |
end | |
class FunkyGreeter | |
def greet(name : String) : String | |
"Yo, #{name}!" | |
end | |
end | |
puts GreeterHolder.new(PoliteGreeter.new).greet("Juan") | |
puts GreeterHolder.new(FunkyGreeter.new).greet("Brian") | |
holder = GreeterHolder.new(PoliteGreeter.new) | |
# It can really support any object that responds to `greet(String) : String`! | |
puts GreeterHolder.new(holder).greet("Ary") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment