Last active
December 24, 2021 03:35
-
-
Save tokland/871431 to your computer and use it in GitHub Desktop.
Simple Algebraic Data types for Ruby
This file contains 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
#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] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Awesome stuff. Have you published this as a gem?