Skip to content

Commit

Permalink
Literal::Array#product
Browse files Browse the repository at this point in the history
Closes #188
  • Loading branch information
joeldrapper committed Dec 8, 2024
1 parent 3780626 commit 43f7be5
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 1 deletion.
5 changes: 5 additions & 0 deletions lib/literal.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ module Literal
autoload :Struct, "literal/struct"
autoload :Type, "literal/type"
autoload :Types, "literal/types"
autoload :Tuple, "literal/tuple"

# Errors
autoload :Error, "literal/errors/error"
Expand All @@ -48,6 +49,10 @@ def self.Hash(key_type, value_type)
Literal::Hash::Generic.new(key_type, value_type)
end

def self.Tuple(*types)
Literal::Tuple::Generic.new(*types)
end

def self.check(actual:, expected:)
if expected === actual
true
Expand Down
32 changes: 31 additions & 1 deletion lib/literal/array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def ===(value)
def >=(other)
case other
when Literal::Array::Generic
@type >= other.type
Literal.subtype?(other.type, of: @type)
else
false
end
Expand Down Expand Up @@ -405,6 +405,36 @@ def pop(...)
@__value__.pop(...)
end

def product(*others, &)
if block_given?
@__value__.product(
*others.map do |other|
case other
when Array
other
when Literal::Array
other.__value__
end
end, &
)

self
elsif others.all?(Literal::Array)
tuple_type = Literal::Tuple(
@__type__,
*others.map(&:__type__)
)

values = @__value__.product(*others.map(&:__value__)).map do |tuple_values|
tuple_type.new(*tuple_values)
end

Literal::Array(tuple_type).new(*values)
else
@__value__.product(*others)
end
end

def push(*value)
Literal.check(actual: value, expected: _Array(@__type__)) do |c|
c.fill_receiver(receiver: self, method: "#push")
Expand Down
60 changes: 60 additions & 0 deletions lib/literal/tuple.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

class Literal::Tuple
class Generic
include Literal::Type

def initialize(*types)
@types = types
end

attr_reader :types

def ===(other)
return false unless Literal::Tuple === other
types = @types
other_types = other.__types__

return false unless types.size == other_types.size

i, len = 0, types.size
while i < len
return false unless Literal.subtype?(other_types[i], of: types[i])
i += 1
end

true
end

def >=(other)
case other
when Literal::Tuple::Generic
types = @types
other_types = other.types

return false unless types.size == other_types.size

i, len = 0, types.size
while i < len
return false unless Literal.subtype?(other_types[i], of: types[i])
i += 1
end

true
else
false
end
end

def new(*values)
Literal::Tuple.new(values, @types)
end
end

def initialize(values, types)
@__values__ = values
@__types__ = types
end

attr_reader :__values__, :__types__
end
23 changes: 23 additions & 0 deletions test/array.test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -626,3 +626,26 @@
assert array.equal?(result)
expect(result.to_a) == [2, 5, 3, 1, 4]
end

test "#product with block" do
a = Literal::Array(Integer).new(1, 2)
b = Literal::Array(String).new("a", "b")

yielded = []

result = a.product(b) { |x, y| yielded << [x, y] }

assert result.equal?(a)
expect(yielded) == [[1, "a"], [1, "b"], [2, "a"], [2, "b"]]
end

test "#product with another Literal::Array" do
a = Literal::Array(Integer).new(1, 2)
b = Literal::Array(String).new("a", "b")

result = a.product(b)

assert Literal::Array(Literal::Tuple(Integer, String)) === result
expect(result.size) == 4
expect(result.first.__values__) == [1, "a"]
end

0 comments on commit 43f7be5

Please sign in to comment.