Skip to content

Instantly share code, notes, and snippets.

@tokland
Last active December 24, 2021 03:35
Show Gist options
  • Save tokland/871431 to your computer and use it in GitHub Desktop.
Save tokland/871431 to your computer and use it in GitHub Desktop.
Simple Algebraic Data types for Ruby
#https://gist.github.com/tokland/871431
module Adt
# Build a ADT parent class with its children constructors.
#
# class_names_with_constructors -- Hash of key=class_name, value=constructor_arguments
# class_body -- Body to add to the parent class
#
# Example:
#
# Tree = adt(:Empty => [], :Node => [:value, :left_tree, :right_tree])
#
def adt(class_names_with_constructors, &class_body)
parent_class = Class.new
class_names_with_constructors.each do |class_name, arguments|
child_class = Class.new(parent_class) do
arguments.each { |argument| attr_reader(argument) }
define_method(:initialize) do |*values|
arguments.size == values.size or
raise ArgumentError.new("Expected arguments (#{arguments.join(', ')})")
arguments.zip(values).each do |argument, value|
instance_variable_set("@#{argument}", value)
end
end
end
child_class.class_eval(&class_body) if class_body
parent_class.const_set(class_name, child_class)
end
parent_class
end
end
class Object
include Adt
end
# data Tree a = Empty | Leaf a | Node a (Tree a) (Tree a)
# deriving Show
#
# weight :: Tree a -> Int
# weight Empty = 0
# weight (Leaf x) = 1
# weight (Node x left right) = 1 + (weight left) + (weight right)
Tree = adt(:Empty => [], :Leaf => [:value], :Node => [:value, :left_tree, :right_tree]) do
def to_s
case self
when Tree::Empty then
"Empty"
when Tree::Leaf
"(Leaf #{value})"
when Tree::Node
"(Node #{value} #{left_tree} #{right_tree})"
end
end
def weight
case self
when Tree::Empty
0
when Tree::Leaf
1
when Tree::Node
1 + left_tree.weight + right_tree.weight
end
end
end
### Usage example
tree = Tree::Node.new(1, Tree::Leaf.new(2), Tree::Empty.new)
puts tree
#=> (Node 1 (Leaf 2) Empty)
puts tree.weight
#=> 2
p [tree.is_a?(Tree), tree.is_a?(Tree::Node), tree.is_a?(Tree::Leaf),tree.is_a?(Tree::Empty)]
#=> [true, true, false, false]
@pritibiyani
Copy link

Awesome stuff. Have you published this as a gem?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment