Class: UnionType

Inherits:
Object show all
Includes:
Enumerable
Defined in:
lib/union_type/union_type.rb,
lib/union_type/version.rb

Overview

A union type that matches values belonging to any of its member classes.

Member classes are stored in a SortedSet sorted alphabetically by name. Redundant subclasses are dropped automatically: if a superclass is already present, its subclasses add no information and are removed.

Examples:

Basic usage

union = String | Integer   # => UnionType(Integer | String)
union === "hello"          # => true
union === 42               # => true
union === :sym             # => false

case/when

case value
when String | Integer then "string or int"
when Float            then "float"
end

Superclass deduplication

UnionType[Integer, Float, Numeric]  # => UnionType(Numeric)

Constant Summary collapse

VERSION =

The current gem version.

Returns:

  • (String)
"0.1.1"

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*classes) ⇒ UnionType

Returns a new instance of UnionType.

Parameters:

  • classes (Array<Class>)

    one or more classes to include in the union. Subclasses of other members are dropped automatically.

Raises:

  • (ArgumentError)

    if no classes are given



50
51
52
53
54
55
56
# File 'lib/union_type/union_type.rb', line 50

def initialize(*classes)
  raise ArgumentError, "requires at least one class" if classes.empty?

  minimal = classes.reject { |c| classes.any? { |other| c != other && c < other } }
  @types = SortedSet.new(minimal.map { Entry.new(_1) }).freeze
  freeze
end

Class Method Details

.[](*classes) ⇒ UnionType

Creates a frozen UnionType from the given classes. Equivalent to new but reads more naturally as a literal.

Examples:

UnionType[String, Integer]  # => UnionType(Integer | String)

Parameters:

  • classes (Array<Class>)

    one or more classes

Returns:



45
# File 'lib/union_type/union_type.rb', line 45

def self.[](*classes) = new(*classes)

Instance Method Details

#&(other) ⇒ UnionType?

Returns a new UnionType containing only classes matched by both unions, keeping the more specific class when one is a subclass of the other. Returns nil when the intersection is empty.

Examples:

(String | Integer) & (Integer | Float)  # => UnionType(Integer)
(String | Integer) & Numeric            # => UnionType(Integer)
(String | Integer) & Float              # => nil

Parameters:

Returns:

Raises:



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
# File 'lib/union_type/union_type.rb', line 132

def &(other)
  other_types = case other
                when UnionType then other.to_a
                when Class     then [other]
                else raise TypeError, "expected Class or UnionType, got #{other.class}"
                end

  classes = flat_map { |a|
    other_types.filter_map { |b|
      if a == b || a < b then a
      elsif b < a        then b
      end
    }
  }.uniq

  UnionType.new(*classes) unless classes.empty?
end

#==(other) ⇒ Boolean Also known as: eql?

Value equality: two unions are equal when they contain the same classes. Argument order does not matter.

Parameters:

Returns:

  • (Boolean)


155
156
157
# File 'lib/union_type/union_type.rb', line 155

def ==(other)
  other.class == UnionType && to_a == other.to_a
end

#===(value) ⇒ Boolean

Returns true if value is an instance of any member class. This makes UnionType work in case/when expressions.

Examples:

(String | Integer) === "hi"  # => true
(String | Integer) === :sym  # => false

Parameters:

Returns:

  • (Boolean)


76
77
78
# File 'lib/union_type/union_type.rb', line 76

def ===(value)
  any? { _1 === value }
end

#cover?(klass) ⇒ Boolean

Returns true if this union covers klass — i.e. klass is a member or a subclass of a member.

Examples:

(Numeric | String).cover?(Integer)  # => true  (Integer < Numeric)
(Numeric | String).cover?(Numeric)  # => true  (exact member)
(Integer | String).cover?(Numeric)  # => false (superclass, not covered)

Parameters:

Returns:

  • (Boolean)


98
99
100
# File 'lib/union_type/union_type.rb', line 98

def cover?(klass)
  any? { klass <= _1 }
end

#each {|klass| ... } ⇒ self

Yields each member class in sorted (alphabetical) order. Required by Enumerable; enables #to_a, #map, #include?, etc.

Yield Parameters:

Returns:

  • (self)


63
64
65
# File 'lib/union_type/union_type.rb', line 63

def each(&block)
  @types.each { block.call(_1.klass) }
end

#hashInteger

Returns hash consistent with #eql?, for use as a Hash key.

Returns:

  • (Integer)

    hash consistent with #eql?, for use as a Hash key



161
162
163
# File 'lib/union_type/union_type.rb', line 161

def hash
  to_a.hash
end

#inspectString Also known as: to_s

Returns human-readable representation, e.g. UnionType(Integer | String).

Returns:

  • (String)

    human-readable representation, e.g. UnionType(Integer | String)



166
167
168
# File 'lib/union_type/union_type.rb', line 166

def inspect
  "UnionType(#{map(&:name).join(" | ")})"
end

#typesSortedSet

The underlying SortedSet of internal entry objects. Entries are sorted by class name; use #to_a to get plain Class objects.

Returns:

  • (SortedSet)


84
85
86
# File 'lib/union_type/union_type.rb', line 84

def types
  @types
end

#|(other) ⇒ UnionType

Returns a new UnionType that is the union of self and other. Redundant subclasses are removed as in new.

Examples:

(String | Integer) | Float   # => UnionType(Float | Integer | String)
[String, Integer, Float].reduce(:|)  # => UnionType(Float | Integer | String)

Parameters:

Returns:

Raises:



112
113
114
115
116
117
118
# File 'lib/union_type/union_type.rb', line 112

def |(other)
  case other
  when Class      then UnionType.new(*self, other)
  when UnionType  then UnionType.new(*self, *other)
  else raise TypeError, "expected Class or UnionType, got #{other.class}"
  end
end