scripts/product_generators.rb (162 lines of code) (raw):
#!/usr/bin/env ruby
# @author Edwin Chen (@echen)
# Automatically write product semigroup, monoid, product group, and product ring
# classes for products up to size 22.
#
# Run it like this:
#
# ruby scripts/product_generators.rb > algebird-core/src/main/scala/com/twitter/algebird/GeneratedProductAlgebra.scala
#
# TODO: Remove in favour of script/product_generators.rb in the next breaking release
PACKAGE_NAME = "com.twitter.algebird"
# The product sizes we want.
PRODUCT_SIZES = (2..22).to_a
# Each element in a product is of a certain type.
# This provides an alphabet to draw types from.
TYPE_SYMBOLS = ("A".."Z").to_a
INDENT = " "
# This returns the comment for each product monoid/group/ring definition.
# n is the size of the product.
# algebraic_structure is "monoid", "group", "ring", etc.
#
# Example return:
# "/**
# * Combine two monoids into a product monoid
# */"
def get_comment(n, algebraic_structure)
ret = <<EOS
/**
* Combine #{n} #{algebraic_structure}s into a product #{algebraic_structure}
*/
EOS
ret.strip
end
# This returns the class definition for each product monoid/group/ring.
# n is the size of the product.
# algebraic_structure is "monoid", "group", "ring", etc.
#
# Example return:
# "class Product2Monoid[T,U](implicit tmonoid: Monoid[T], umonoid: Monoid[U]) extends Monoid[(T,U)]"
def get_class_definition(n, algebraic_structure)
# Example: "T,U"
type_values_commaed = TYPE_SYMBOLS.first(n).join(", ")
params = "(apply: (#{type_values_commaed}) => X, unapply: X => Option[(#{type_values_commaed})])"
extends = ["#{algebraic_structure.capitalize}[X]"]
parent = case algebraic_structure
when "monoid"
"Semigroup"
when "group"
"Monoid"
when "ring"
"Group"
end
if parent
extends.unshift("Product#{n}#{parent}[X, #{type_values_commaed}]#{params}")
end
"class Product#{n}#{algebraic_structure.capitalize}[X, #{type_values_commaed}]#{params}(implicit #{get_type_parameters(n, algebraic_structure)}) extends #{extends.join(" with ")}"
end
# This returns the parameters for each product monoid/group/ring class.
# n is the size of the product.
# algebraic_structure is "monoid", "group", "ring", etc.
#
# Example return:
# "tmonoid: Monoid[T], umonoid: Monoid[U]"
def get_type_parameters(n, algebraic_structure)
params = TYPE_SYMBOLS.first(n).map{ |t| "#{t.downcase}#{algebraic_structure}: #{algebraic_structure.capitalize}[#{t.upcase}]"}
params.join(", ")
end
# This returns the method definition for constants in the algebraic structure.
# n is the size of the product.
# algebraic_structure is "monoid", "group", "ring", etc.
# constant is "zero", "one", etc.
#
# Example return:
# "override def zero = (tgroup.zero, ugroup.zero)"
def get_constant(n, algebraic_structure, constant)
# Example: "tgroup.zero, ugroup.zero"
constants_commaed = TYPE_SYMBOLS.first(n).map{ |t| "#{t.downcase}#{algebraic_structure}.#{constant}" }.join(", ")
"override def #{constant} = apply(#{constants_commaed})"
end
# This returns the method definition for negation in the algebraic structure
# (assuming the structure has an additive inverse).
# n is the size of the product.
# algebraic_structure is "group", "ring", etc.
#
# Example return:
# "override def negate(v: (T,U)) = (tgroup.negate(v._1), ugroup.negate(v._2))"
def get_negate(n, algebraic_structure)
negates_commaed = TYPE_SYMBOLS.first(n).each_with_index.map{ |t, i| "#{t.downcase}#{algebraic_structure}.negate(tuple._#{i+1})" }.join(", ")
"override def negate(v: X) = { val tuple = unapply(v).get; apply(#{negates_commaed}) }"
end
# This returns the method definition for associative binary operations in
# the algebraic structure.
# n is the size of the product.
# algebraic_structure is "monoid", "group", "ring", etc.
# operation is "plus", "minus", "times", etc.
#
# Example return:
# "override def plus(l: (T,U), r: (T,U)) = (tmonoid.plus(l._1,r._1), umonoid.plus(l._2, r._2))"
def get_operation(n, algebraic_structure, operation)
# Example: "(T, U)"
individual_element_type = "X"
# Example: "l: (T, U), r: (T, U)"
method_params = "l: #{individual_element_type}, r: #{individual_element_type}" # (1..n).to_a.map{ |i| "x#{i}" }.map{ |p| "#{p}: #{individual_element_type}" }.join(", ")
# Example: "(tmonoid.plus(l._1,r._1), umonoid.plus(l._2, r._2))"
values_commaed = TYPE_SYMBOLS.first(n).each_with_index.map do |t, i|
"#{t.downcase}#{algebraic_structure}.#{operation}(lTuple._#{i+1}, rTuple._#{i+1})"
end.join(", ")
values_commaed = "apply(#{values_commaed})"
"override def #{operation}(#{method_params}) = { val lTuple = unapply(l).get; val rTuple = unapply(r).get; #{values_commaed} }"
end
def get_sumoption(n, bufferSize)
# Example: "tsemigroup.sumOption(items.iterator.map(_._1), "
buffers = TYPE_SYMBOLS.first(n).map do |t|
" val buf#{t} = fromSumOption[#{t}](#{bufferSize})"
end.join("\n")
put_statements = TYPE_SYMBOLS.first(n).each_with_index.map do |t, i|
"buf#{t}.put(tuple._#{i+1})"
end.join("; ")
gets_commaed = TYPE_SYMBOLS.first(n).map do |t|
"buf#{t}.flush.get"
end.join(", ")
"override def sumOption(to: TraversableOnce[X]) = {
if (to.isEmpty) None
else {
#{buffers}
to.foreach { x =>
val tuple = unapply(x).get
#{put_statements}
}
Some(apply(#{gets_commaed}))
}
}"
end
# Example return:
# "implicit def pairMonoid[T,U](implicit tg: Monoid[T], ug: Monoid[U]): Monoid[(T,U)] = {
# new Product2Monoid[T,U]()(tg,ug)
# }"
def get_implicit_definition(n, algebraic_structure)
type_params_commaed = get_type_parameters(n, algebraic_structure)
# Example: "T,U"
product_type_commaed = TYPE_SYMBOLS.first(n).join(", ")
# Example: "Monoid[(T,U)]"
return_type = "#{algebraic_structure.capitalize}[(#{product_type_commaed})]"
ret = %Q|#{INDENT}implicit def #{algebraic_structure}#{n}[#{product_type_commaed}](implicit #{type_params_commaed}): #{return_type} = {
#{INDENT} new Product#{n}#{algebraic_structure.capitalize}[Tuple#{n}[#{product_type_commaed}], #{product_type_commaed}](Tuple#{n}.apply, Tuple#{n}.unapply)(#{TYPE_SYMBOLS.first(n).map{ |t| t.downcase + algebraic_structure.downcase }.join(", ")})
#{INDENT}}|
ret
end
def get_product_definition(n, algebraic_structure)
type_params_commaed = get_type_parameters(n, algebraic_structure)
# Example: "T,U"
product_type_commaed = TYPE_SYMBOLS.first(n).join(", ")
ret = %Q|#{INDENT}def apply[X, #{product_type_commaed}](applyX: (#{ product_type_commaed}) => X, unapplyX: X => Option[(#{ product_type_commaed})])(implicit #{type_params_commaed}): #{algebraic_structure.capitalize}[X] = {
#{INDENT} new Product#{n}#{algebraic_structure.capitalize}[X, #{product_type_commaed}](applyX, unapplyX)(#{TYPE_SYMBOLS.first(n).map{ |t| t.downcase + algebraic_structure.downcase }.join(", ")})
#{INDENT}}|
ret
end
def print_class_definitions
PRODUCT_SIZES.each do |product_size|
code = <<EOS
#{get_comment(product_size, "semigroup")}
#{get_class_definition(product_size, "semigroup")} {
#{get_operation(product_size, "semigroup", "plus")}
#{get_sumoption(product_size, 1000)}
}
#{get_comment(product_size, "monoid")}
#{get_class_definition(product_size, "monoid")} {
#{get_constant(product_size, "monoid", "zero")}
}
#{get_comment(product_size, "group")}
#{get_class_definition(product_size, "group")} {
#{get_negate(product_size, "group")}
#{get_operation(product_size, "group", "minus")}
}
#{get_comment(product_size, "ring")}
#{get_class_definition(product_size, "ring")} {
#{get_constant(product_size, "ring", "one")}
#{get_operation(product_size, "ring", "times")}
}
EOS
puts code
end
end
def print_custom_definitions
puts "trait ProductSemigroups {"
PRODUCT_SIZES.each do |n|
puts get_product_definition(n, "semigroup")
puts
end
puts "}"
puts
puts "trait ProductMonoids {"
PRODUCT_SIZES.each do |n|
puts get_product_definition(n, "monoid")
puts
end
puts "}"
puts
puts "trait ProductGroups {"
PRODUCT_SIZES.each do |n|
puts get_product_definition(n, "group")
puts
end
puts "}"
puts
puts "trait ProductRings {"
PRODUCT_SIZES.each do |n|
puts get_product_definition(n, "ring")
puts
end
puts "}"
end
def print_implicit_definitions
puts "trait GeneratedSemigroupImplicits {"
PRODUCT_SIZES.each do |n|
puts get_implicit_definition(n, "semigroup")
puts
end
puts "}"
puts
puts "trait GeneratedMonoidImplicits {"
PRODUCT_SIZES.each do |n|
puts get_implicit_definition(n, "monoid")
puts
end
puts "}"
puts
puts "trait GeneratedGroupImplicits {"
PRODUCT_SIZES.each do |n|
puts get_implicit_definition(n, "group")
puts
end
puts "}"
puts
puts "trait GeneratedRingImplicits {"
PRODUCT_SIZES.each do |n|
puts get_implicit_definition(n, "ring")
puts
end
puts "}"
end
puts "// following were autogenerated by #{__FILE__} at #{Time.now} do not edit"
puts "package #{PACKAGE_NAME}"
puts
puts "import ArrayBufferedOperation.fromSumOption"
puts
print_class_definitions
puts
print_custom_definitions