Skip to content

Instantly share code, notes, and snippets.

@asterite
Last active February 26, 2021 03:28
Show Gist options
  • Save asterite/d11edd5ee9fd8990fe139dafe2c855b5 to your computer and use it in GitHub Desktop.
Save asterite/d11edd5ee9fd8990fe139dafe2c855b5 to your computer and use it in GitHub Desktop.
# 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