Last active
January 10, 2018 14:27
-
-
Save dminuoso/35bcb3efac3526e8e8d48149cafc6707 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
Const = Struct.new(:value) do | |
def fmap(*args) | |
-> (func) { self }.curry[*args] | |
end | |
def self.make(*args) | |
-> (o) { new(o) }.curry[*args] | |
end | |
end | |
Const.implements Functor |
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
module Functor | |
extend Protocol | |
requires_instance_method :fmap | |
end |
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
module ArityRange | |
def arity_range | |
args = parameters.map(&:first) | |
req = args.count :req | |
keyreq = args.count :keyreq | |
opt = args.include?(:rest) ? Float::INFINITY : args.count(:opt) | |
keyopt = args.include?(:keyrest) ? Float::INFINITY : args.count(:key) | |
{ arguments: req..req + opt, keywords: keyreq..keyreq + keyopt } | |
end | |
refine UnboundMethod do | |
include ArityRange | |
end | |
refine Method do | |
include ArityRange | |
end | |
end | |
module Protocol | |
using ArityRange | |
class MethodRequirement | |
Exception = Class.new(StandardError) | |
def initialize(method, arguments: nil) | |
@method = method | |
@arguments = case arguments | |
when NilClass then nil | |
when Integer then arguments..arguments | |
when Range then arguments | |
else raise ArgumentError | |
end | |
end | |
def ===(other) | |
other.method_defined?(@method) && | |
(!@arguments || @arguments == other.instance_method(@method).arity_range[:arguments]) | |
end | |
def to_s | |
"method ##{@method}#{" with arity #{@arguments}" if @arguments}" | |
end | |
end | |
class ClassMethodRequirement < MethodRequirement | |
Exception = Class.new(StandardError) | |
def ===(other) | |
other.respond_to?(@method) && | |
(!@arguments || @arguments == other.method(@method).arity_range[:arguments]) | |
end | |
def to_s | |
"class #{super}" | |
end | |
end | |
class AncestorRequirement | |
Exception = Class.new(StandardError) | |
def initialize(ancestor) | |
@ancestor = ancestor | |
end | |
def ===(other) | |
other < @ancestor | |
end | |
def to_s | |
"ancestor #{@ancestor}" | |
end | |
end | |
def self.extended(mod) | |
mod.instance_eval do | |
@requirements = [] | |
end | |
end | |
def requires_ancestor(mod) | |
@requirements << AncestorRequirement.new(mod) | |
end | |
def requires_instance_method(method, **kw) | |
@requirements << MethodRequirement.new(method, **kw) | |
end | |
def requires_class_method(method, **kw) | |
@requirements << ClassMethodRequirement.new(method, **kw) | |
end | |
def included(target) | |
@requirements.each do |requirement| | |
unless requirement === target | |
raise requirement.class::Exception, "protocol #{self} requires #{requirement}" | |
end | |
end | |
end | |
end | |
class Module | |
alias_method :implements, :include | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment