# encoding: utf-8 module Axiom module Types # Abstract base class for every type class Type extend Options, DescendantsTracker accept_options :constraint constraint Tautology # Infer the type of the object # # @example # type = Axiom::Types::Type.infer(Axiom::Types::Integer) # # => Axiom::Types::Integer # # @param [Object] object # # @return [Class] # # @api public def self.infer(object) self if equal?(object) end # Instantiate a new Axiom::Types::Type subclass # # @example # type = Axiom::Types::Type.new # => Axiom::Types::Type # # @param [#call] constraint # optional constraint for the new type # # @yield [object] # # @yieldparam object [Object] # test if the object matches the type constraint # # @yieldreturn [Boolean] # true if the object matches the type constraint # # @return [Class] # # @api public def self.new(constraint = Undefined, &block) type = ::Class.new(self, &block) type.constraint(constraint) type.finalize end # Finalize by deep freezing # # @return [Class] # # @api private def self.finalize IceNine.deep_freeze(constraint) freeze end # Test if the object matches the type constraint # # @example # type = Axiom::Types::Integer.new do # minimum 1 # maximum 100 # end # # type.include?(1) # => true # type.include?(100) # => true # type.include?(0) # => false # type.include?(101) # => false # # @param [Object] object # # @return [Boolean] # # @api public def self.include?(object) constraint.call(object) end # Add a constraint to the type # # @example with an argument # type.constraint(lambda { |object| object == 42 } # # @example with a block # type.constraint { |object| object == 42 } # # @example with no arguments # type.constraint # => constraint # # @param [#call] constraint # optional constraint # # @yield [object] # # @yieldparam object [Object] # test if the object matches the type constraint # # @yieldreturn [Boolean] # true if the object matches the type constraint # # @return [Class] # # @api public def self.constraint(constraint = Undefined, &block) constraint = block if constraint.equal?(Undefined) return @constraint if constraint.nil? add_constraint(constraint) self end # Add a constraint that the object must be included in a set # # @param [Array] members # # @return [undefined] # # @todo move into a module # # @api private def self.includes(*members) set = IceNine.deep_freeze(members.to_set) constraint(&set.method(:include?)) end # Add new constraint to existing constraint, if any # # @param [#call] constraint # # @return [undefined] # # @api private def self.add_constraint(constraint) current = self.constraint @constraint = if current lambda { |object| constraint.call(object) && current.call(object) } else constraint end end private_class_method :add_constraint end # class Type end # module Types end # module Axiom