Skip to content

Instantly share code, notes, and snippets.

@itarato
Last active November 5, 2020 03:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save itarato/ffb6ef0fd1c849420a6027b05b1d2bd7 to your computer and use it in GitHub Desktop.
Save itarato/ffb6ef0fd1c849420a6027b05b1d2bd7 to your computer and use it in GitHub Desktop.
Playing with (almost) algebraic data definitions in Ruby
class Def
class DataContainer
def initialize(name)
@name = name
@members = []
@last_constructor = nil
Object.const_set(name, self)
end
def to_s
@name
end
def inspect
@name
end
def |(name)
@last_constructor = DataConstructor.new(name, self)
self
end
alias_method :>>, :|
def ^(arg_defs)
raise "Constructor must be defined first" unless @last_constructor
@last_constructor.set_arg_defs(arg_defs)
self
end
end
class DataConstructor
attr_accessor :vals
def initialize(name, data_container)
@name = name
@data_container = data_container
@args = nil
@vals = {}
Object.const_set(name, self)
end
def to_s
"<#{@name}#{@vals.map { |k, v| " #{k}:#{v}"}.join}>"
end
alias_method :inspect, :to_s
def type
@data_container
end
def is_a?(a_type)
a_type === type
end
def set_arg_defs(arg_defs)
@arg_defs = arg_defs
end
def new(*args, **kwargs)
self_clone = clone
self_clone.vals = {}
case @arg_defs
when Hash
@arg_defs.each do |k, _|
self_clone.vals[k] = kwargs[k]
end
when Array
@arg_defs.zip(args).each do |arg_def, arg|
self_clone.vals[arg_def] = arg
end
else
self_clone.vals[@arg_defs] = args[0]
end
self_clone
end
def value
@vals.values[0]
end
def defined?(name)
return true if @vals.key?(name)
super(name)
end
def method_missing(name, *args)
return super(name, *args) unless @vals.key?(name)
@vals[name]
end
end
def self.data(name)
DataContainer.new(name)
end
class << self
def [](name)
DataContainer.new(name)
end
end
end
Def['Color'] >> 'Black' | 'Yellow' | 'White'
Def['Bool'] >> 'Yes' | 'No'
Def['Address'] >> 'MakeAddress' ^ [String, Integer]
Def['BetterAddress'] >> 'MakeBetterAddress' ^ { street: String, number: Integer }
Def['Either'] >> 'Left' ^ String | 'Right' ^ Integer
Def['Optional'] >> 'Nothing' | 'Just' ^ BasicObject
p Black
p Black.type
p White
p White.type
p White.is_a?(Color)
p White.is_a?(Bool)
p Yes.is_a?(Bool)
a = MakeAddress.new("Rene Levesque", 1330)
p a
b = MakeBetterAddress.new(street: "Rene Levesque", number: 1330)
p b
p b.street
p b.number
err = Left.new("missing")
p err
p Left
p err.value
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment