Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement some methods on Literal::Array #143

Merged
merged 11 commits into from
Nov 14, 2024
66 changes: 66 additions & 0 deletions lib/literal/array.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ def initialize(value, type:)
@__type__ = type
@__value__ = value
@__collection_type__ = collection_type
@__generic__ = Literal::Array(type)
end

def __initialize_without_check__(value, type:, collection_type:)
@__type__ = type
@__value__ = value
@__collection_type__ = collection_type
@__generic__ = Literal::Array(type)
self
end

Expand All @@ -77,6 +79,39 @@ def &(other)
end
end

def *(times)
case times
when String
@__value__ * times
else
__with__(@__value__ * times)
end
end

def +(other)
case other
when ::Array
values = @__value__ + @__generic__.new(*other).__value__
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably check other against the collection type and then use __with__ to return a Literal::Array. I believe this will return an Array at the moment.

Copy link
Contributor Author

@christopher-b christopher-b Nov 14, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This creates a temporary variable values (perhaps poorly named, too close to __values__) that is used to initialize a new Literal::Array on line 101. So the type checking is deferred to the instantiation of the new Literal::Array at the end of the method.

I think my error here is that I'm creating a new Literal::Array twice. This should work as just:

values = @__value__ + other

And the return value could actually be:

@__generic__.new(*values)

So the whole method would be

def +(other)
	case other
	when ::Array
		values = @__value__ + other
	when Literal::Array
		values = @__value__ + other.__value__
	else
		raise ArgumentError.new("Cannot perform `+` with #{other.class.name}.")
	end

	@__generic__.new(*values)
end

Does this approach make sense; should we be checking types inside the case...when?

when Literal::Array
values = @__value__ + other.__value__
else
raise ArgumentError, "Cannot perform `+` with #{other.class.name}."
end

Literal::Array.new(values, type: @__type__)
end

def -(other)
case other
when ::Array
__with__(@__value__ - other)
when Literal::Array
__with__(@__value__ - other.__value__)
else
raise ArgumentError.new("Cannot perform `-` with #{other.class.name}.")
end
end

def <<(value)
Literal.check(actual: value, expected: @__type__) do |c|
c.fill_receiver(receiver: self, method: "#<<")
Expand All @@ -86,6 +121,17 @@ def <<(value)
self
end

def <=>(other)
case other
when ::Array
@__value__ <=> other
when Literal::Array
@__value__ <=> other.__value__
else
raise ArgumentError.new("Cannot perform `<=>` with #{other.class.name}.")
end
end

def [](index)
@__value__[index]
end
Expand All @@ -112,6 +158,10 @@ def any?(...)
@__value__.any?(...)
end

def assoc(...)
@__value__.assoc(...)
end

def at(...)
@__value__.at(...)
end
Expand All @@ -125,6 +175,16 @@ def clear(...)
self
end

def compact
# @TODO if this is an array of nils, we should return an emtpy array
__with__(@__value__)
end

def compact!
# @TODO if this is an array of nils, we should set @__value__ = [] and return self
nil
end

def count(...)
@__value__.count(...)
end
Expand Down Expand Up @@ -199,6 +259,12 @@ def map(type, &block)
end
end

def map!(&)
new_array = map(@__type__, &)
@__value__ = new_array.__value__
self
end

def max(n = nil, &)
if n
__with__(@__value__.max(n, &))
Expand Down
113 changes: 113 additions & 0 deletions test/array.test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@
}.to_raise(Literal::TypeError)
end

test "#map! maps each item correctly" do
array = Literal::Array(Integer).new(1, 2, 3)

expect(array.map!(&:succ).to_a) == [2, 3, 4]
end

test "map! raises if the type is wrong" do
array = Literal::Array(Integer).new(1, 2, 3)

expect { array.map!(&:to_s) }.to_raise(Literal::TypeError)
end

test "#[]" do
array = Literal::Array(Integer).new(1, 2, 3)

Expand Down Expand Up @@ -110,6 +122,86 @@
expect((array & other).to_a) == [2, 3]
end

test "#* with an integer multiplies the array" do
array = Literal::Array(Integer).new(1, 2, 3)

result = array * 2
assert Literal::Array(Integer) === result
expect(result.to_a) == [1, 2, 3, 1, 2, 3]
end

test "#* raises with a negative integer" do
array = Literal::Array(Integer).new(1, 2, 3)

expect { array * -1 }.to_raise(ArgumentError)
end

test "#* with a string joins the elements" do
array = Literal::Array(Integer).new(1, 2, 3)

expect(array * ",") == "1,2,3"
end

test "#+ adds another Literal::Array" do
array = Literal::Array(Integer).new(1, 2, 3)
other = Literal::Array(Integer).new(4, 5)

result = array + other
assert Literal::Array(Integer) === result
expect(result.to_a) == [1, 2, 3, 4, 5]
end

test "#+ adds an array" do
array = Literal::Array(Integer).new(1, 2, 3)
other = [4, 5]

result = array + other
assert Literal::Array(Integer) === result
expect(result.to_a) == [1, 2, 3, 4, 5]
end

test "#+ raises if the type is wrong" do
array = Literal::Array(Integer).new(1, 2, 3)
other = Literal::Array(String).new("a", "b")
other_primitive = ["a", "b"]

expect { array + other }.to_raise(Literal::TypeError)
expect { array + other_primitive }.to_raise(Literal::TypeError)
end

test "#- removes elements from another Literal::Array" do
array = Literal::Array(Integer).new(1, 2, 3)
other = Literal::Array(Integer).new(1)

result = array - other
expect(result.to_a) == [2, 3]
end

test "#- removes elements from an array" do
array = Literal::Array(Integer).new(1, 2, 3)
other = [1]

result = array - other
expect(result.to_a) == [2, 3]
end

test "#<=> works as expected" do
array = Literal::Array(Integer).new(1, 2, 3)

expect(array <=> [1, 2, 4]) == -1
expect(array <=> [1, 2, 2]) == 1
expect(array <=> [1, 2, 3, 4]) == -1
expect(array <=> [1, 2]) == 1
expect(array <=> [1, 2, 3]) == 0
end

test "#<=> works with another Literal::Array" do
array = Literal::Array(Integer).new(1, 2, 3)
other = Literal::Array(Integer).new(1, 2, 4)

expect(array <=> other) == -1
end

test "#sort sorts the array" do
array = Literal::Array(Integer).new(3, 2, 1)

Expand Down Expand Up @@ -137,6 +229,27 @@
expect { array.push(4, "5") }.to_raise(Literal::TypeError)
end


test "#assoc returns the correct element" do
array = Literal::Array(Array).new([1, 2], [3, 4])

expect(array.assoc(1)) == [1, 2]
expect(array.assoc(3)) == [3, 4]
end

test "#compact returns a new Literal::Array" do
array = Literal::Array(Integer).new(1, 2, 3)

expect(array.compact.to_a) == [1, 2, 3]
expect(array.compact) != array
end

test "#compact! returns nil" do
array = Literal::Array(Integer).new(1, 2, 3)

expect(array.compact!) == nil
end

test "#drop returns a new array with the first n elements removed" do
array = Literal::Array(Integer).new(1, 2, 3)
dropped = array.drop(1)
Expand Down
Loading