class Algebrick::ProductVariant

Representation of Product and Variant types. The class behaves differently
based on #kind.

noinspection RubyTooManyMethodsInspection

Attributes

fields[R]
variants[R]

Public Class Methods

new(name, &definition) click to toggle source
Calls superclass method
# File lib/algebrick/product_variant.rb, line 26
def initialize(name, &definition)
  super(name, &definition)
  @to_be_kind_of  = []
  @final_variants = false
end

Public Instance Methods

==(other) click to toggle source
# File lib/algebrick/product_variant.rb, line 95
def ==(other)
  other.kind_of? ProductVariant and
      variants == other.variants and fields == other.fields
end
[](*fields)
Alias for: new
apply_be_kind_of() click to toggle source
# File lib/algebrick/product_variant.rb, line 105
def apply_be_kind_of
  @to_be_kind_of.each do |type|
    @constructor.send :include, type if @constructor
    variants.each { |v| v.be_kind_of type if v != self && v.respond_to?(:be_kind_of) } if @variants
  end
end
assigned_types() click to toggle source
# File lib/algebrick/product_variant.rb, line 165
def assigned_types
  @assigned_types or raise TypeError, "#{self} does not have assigned types"
end
assigned_types=(assigned_types) click to toggle source
# File lib/algebrick/product_variant.rb, line 169
def assigned_types=(assigned_types)
  raise TypeError, "#{self} assigned types already set" if @assigned_types
  @assigned_types = assigned_types
end
be_kind_of(type) click to toggle source
# File lib/algebrick/product_variant.rb, line 100
def be_kind_of(type)
  @to_be_kind_of << type
  apply_be_kind_of
end
call(*field_matchers) click to toggle source
# File lib/algebrick/product_variant.rb, line 112
def call(*field_matchers)
  raise TypeError, "#{self} does not have any fields" unless @fields
  Matchers::Product.new self, *field_matchers
end
field(name) click to toggle source
# File lib/algebrick/product_variant.rb, line 62
def field(name)
  fields[field_indexes[name]]
end
field_indexes() click to toggle source
# File lib/algebrick/product_variant.rb, line 58
def field_indexes
  @field_indexes or raise TypeError, "field names not defined on #{self}"
end
final!() click to toggle source
# File lib/algebrick/product_variant.rb, line 66
def final!
  @final_variants = true
  self
end
kind() click to toggle source

noinspection RubyCaseWithoutElseBlockInspection

# File lib/algebrick/product_variant.rb, line 152
def kind
  @kind ||= case
            when @fields && !@variants
              :product
            when @fields && @variants
              :product_variant
            when !@fields && @variants
              :variant
            when !@fields && !@variants
              raise TypeError, 'fields or variants have to be set'
            end
end
new(*fields) click to toggle source
# File lib/algebrick/product_variant.rb, line 88
def new(*fields)
  raise TypeError, "#{self} does not have fields" unless @constructor
  @constructor.new *fields
end
Also aliased as: []
set_fields(fields_or_hash) click to toggle source
# File lib/algebrick/product_variant.rb, line 32
def set_fields(fields_or_hash)
  raise TypeError, 'can be set only once' if @fields
  @kind        = nil
  fields, keys = case fields_or_hash
                 when Hash
                   [fields_or_hash.values, fields_or_hash.keys]
                 when Array
                   [fields_or_hash, nil]
                 else
                   raise ArgumentError
                 end

  add_field_names keys if keys

  fields.all? { |f| Type! f, Type, Class, Module }
  raise TypeError, 'there is no product with zero fields' unless fields.size > 0
  define_method(:value) { @fields.first } if fields.size == 1
  @fields      = fields
  @constructor = Class.new(
      field_names? ? ProductConstructors::Named : ProductConstructors::Basic).
      tap { |c| c.type = self }
  const_set :Constructor, @constructor
  apply_be_kind_of
  self
end
set_variants(*variants) click to toggle source
# File lib/algebrick/product_variant.rb, line 71
def set_variants(*variants)
  raise TypeError, 'can be set only once' if @variants && @final_variants
  @kind = nil
  variants.all? { |v| Type! v, Type, Class }
  @variants ||= []
  @variants += variants
  apply_be_kind_of
  variants.each do |v|
    if v.respond_to? :be_kind_of
      v.be_kind_of self
    else
      v.send :include, self
    end
  end
  self
end
to_m() click to toggle source
# File lib/algebrick/product_variant.rb, line 117
def to_m
  case kind
  when :product
    Matchers::Product.new self
  when :product_variant
    Matchers::Variant.new self
  when :variant
    Matchers::Variant.new self
  else
    raise
  end
end
to_s() click to toggle source
# File lib/algebrick/product_variant.rb, line 130
def to_s
  case kind
  when :product
    product_to_s
  when :product_variant
    name + '(' +
        variants.map do |variant|
          if variant == self
            product_to_s
          else
            sub_type variant
          end
        end.join(' | ') +
        ')'
  when :variant
    "#{name}(#{variants.map { |v| sub_type v }.join ' | '})"
  else
    raise
  end
end

Private Instance Methods

add_field_names(names) click to toggle source
# File lib/algebrick/product_variant.rb, line 191
def add_field_names(names)
  @field_names = names
  names.all? { |k| Type! k, Symbol }
  dict = @field_indexes =
      Hash.new { |_, k| raise ArgumentError, "unknown field #{k.inspect} in #{self}" }.
          update names.each_with_index.inject({}) { |h, (k, i)| h.update k => i }
  define_method(:[]) { |key| @fields[dict[key]] }
  # TODO use attr_readers to speed things up
end
product_to_s() click to toggle source
# File lib/algebrick/product_variant.rb, line 176
def product_to_s
  fields_str = if field_names?
                 field_names.zip(fields).map { |name, field| "#{name}: #{field.name}" }
               else
                 fields.map(&:name)
               end
  "#{name}(#{fields_str.join ', '})"
end
sub_type(type) click to toggle source
# File lib/algebrick/product_variant.rb, line 185
def sub_type(type)
  return type.name unless type.name.nil?
  return '(recursive)' if type == self # FIXME: will not catch deeper recursions
  return type.to_s
end