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

[RFC] Expand Tuple.from and Tuple#from to accept Tuple #10080

Open
Daniel-Worrall opened this issue Dec 15, 2020 · 3 comments
Open

[RFC] Expand Tuple.from and Tuple#from to accept Tuple #10080

Daniel-Worrall opened this issue Dec 15, 2020 · 3 comments
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. kind:feature topic:compiler:codegen topic:stdlib:collection

Comments

@Daniel-Worrall
Copy link
Contributor

Daniel-Worrall commented Dec 15, 2020

Proposed Changes

This would extend the parameters to accept a Tuple as well as Array. A working example

Expand Diff
--- a/src/tuple.cr
+++ b/src/tuple.cr
@@ -95,6 +95,23 @@ struct Tuple
     {% end %}
   end

+  # Creates a tuple from the given tuple, with elements casted to the given types.
+  #
+  # ```
+  # Tuple(String, Int64).from({"world", 2_i64}.as({String?, Int64})).class # => Tuple(String, Int64)
+  # ```
+  #
+  # See also: `#from`.
+  def self.from(tuple : Tuple(*U)) : self forall U
+    {% if U.size != T.size %}
+      {% raise "ArgumentError: Expected tuple of size #{T.size} but one of size #{U.size} was given."%}
+    {% end %}
+
+    {% begin %}
+    Tuple.new(*{{T}}).from(tuple)
+    {% end %}
+  end
+
   # Expects to be called on a tuple of types, creates a tuple from the given array,
   # with types casted appropriately.
   #
@@ -124,6 +141,22 @@ struct Tuple
     {% end %}
   end
 
+  # Expects to be called on a tuple of types, creates a tuple from the given array,
+  # with types casted appropriately.
+  def from(tuple : Tuple(*U)) forall U
+    {% if U.size != T.size %}
+      {% raise "ArgumentError: Expected tuple of size #{T.size} but one of size #{U.size} was given."%}
+    {% end %}
+
+    {% begin %}
+    Tuple.new(
+    {% for i in 0...@type.size %}
+      self[{{i}}].cast(tuple[{{i}}]),
+    {% end %}
+    )
+    {% end %}
+  end
+

Motivation

As with all my crystal coding in December, this starts with Advent of Code. I wished to map an Array(Int32?) to an Array(Tuple(Int32, Int32))` that included the index from the original array.

Something akin to this.

arr
  .each
  .with_index
  .reject({Nil, Int32})
  .to_a

This did work for removing the elements but didn't work for different reasons to do with being unable to cast from one Tuple type to another so it remained an Array(Tuple(Int32?, Int32)). Can deal with that, of course, but it could be cleaner. This lead me to a rabbit hole of casting Tuples and needing to do something like {t[0].as(Int32), t[1]}

I wish this change to allow something like this too, but so far it ends up in an Invalid memory access

arr
  .each
  .with_index
  .reject({Nil, Int32})
  .map(&->Tuple(Int32,Int32).from(Tuple(Int32?,Int32))

Related Issue/PR #10041 #10047

@j8r
Copy link
Contributor

j8r commented Dec 15, 2020

In fact, any Indexable can be accepted.

@Daniel-Worrall
Copy link
Contributor Author

We could extend the Array one to allow any Indexable since it raises Argument Error on size mismatches, but it's not as simple with Tuples and I added a macro raise so it doesn't produce a difficult to read compile error when one tuple is bigger than the other.

@HertzDevil HertzDevil added kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:compiler:codegen labels Aug 13, 2021
@HertzDevil
Copy link
Contributor

HertzDevil commented Aug 13, 2021

The Proc literal should not break; .map { |v| Tuple(Int32, Int32).from(v) } works. Another example:

struct Tuple(*T)
  def self.foo(tuple)
    new(tuple[0].as(Int32), tuple[1].as(Int32))
  end
end

arr = [{1, 2}, {4, 6}, {9, -1}]
pp arr
  .each
  .map(&->Tuple(Int32, Int32).foo(Tuple(Int32, Int32)))
  .to_a # => [{2, -857032528}, {6, -857032528}, {-1, -857032528}]

Here .map(&->Tuple.foo(Tuple(Int32, Int32))) also works, but of course this does not apply to .from which requires type arguments.


By the way, .reject({Nil, Int32}) invokes the pattern overload instead of the type overload, and uses Tuple#=== rather than is_a?, so it never performs type filtering. Type filtering of Tuples would not work without #10786, but if that is supported, .select(Tuple(Int32, Int32)) will do the job, no Tuple.from needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
kind:bug A bug in the code. Does not apply to documentation, specs, etc. kind:feature topic:compiler:codegen topic:stdlib:collection
Projects
None yet
Development

No branches or pull requests

3 participants