lib/finitio/type/sub_type.rb
module Finitio
#
# A sub type generator, through specialization by constraints.
#
# A sub type captures a subset of the values of a super type, through a
# constraint. For instance, a Byte type can be defined as a subset of all
# integers, as follows:
#
# Byte := Integer( i | i >= 0 and i <= 255 )
#
# This class allows defining such sub types with multiple named constraints.
# For instance,
#
# Int = BuiltinType.new(Integer)
# Byte = SubType.new(Int, positive: ->(i){ i >= 0 },
# small: ->(i){ i <= 255 })
#
# The concrete representation of the super type is kept as representation
# of the sub type. In other words:
#
# R(Byte) = R(Int) = Integer
#
# Accordingly, the `dress` transformation function has the following signature:
#
# dress :: Alpha -> Byte throws TypeError
# dress :: Object -> Integer throws TypeError
#
class SubType < Type
def initialize(super_type, constraints, name = nil, metadata = nil)
unless super_type.is_a?(Type)
raise ArgumentError, "Finitio::Type expected, got #{super_type}"
end
unless constraints.is_a?(Array) &&
constraints.all?{|v| v.is_a?(Constraint) }
raise ArgumentError, "[Constraint] expected for constraints, got #{constraints}"
end
super(name, metadata)
@super_type, @constraints = super_type, constraints.freeze
end
attr_reader :super_type, :constraints
def representator
super_type.representator
end
def [](name)
constraints.find{|c| c.name == name }
end
def default_name
constraints.first.name.to_s.capitalize
end
def include?(value)
super_type.include?(value) && constraints.all?{|c| c===value }
end
# Check that `value` can be uped through the supertype, then verify all
# constraints. Raise an error if anything goes wrong.
def dress(value, handler = DressHelper.new)
# Check that the supertype is able to dress the value.
# Rewrite and set cause to any encountered TypeError.
uped = handler.try(self, value) do
super_type.dress(value, handler)
end
# Check each constraint in turn
constraints.each do |constraint|
next if constraint===uped
msg = handler.default_error_message(self, value)
if constraint.named? && constraints.size>1
msg << " (not #{constraint.name})"
end
handler.fail!(msg)
end
# seems good, return the uped value
uped
end
def unconstrained
super_type.unconstrained
end
def ==(other)
super || (
other.is_a?(SubType) && (other.super_type == super_type) &&
set_equal?(constraints, other.constraints)
)
end
alias :eql? :==
def hash
self.class.hash ^ super_type.hash ^ set_hash(constraints)
end
def resolve_proxies(system)
SubType.new(super_type.resolve_proxies(system), constraints, name, metadata)
end
end # class SubType
end # module Finitio