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

Make Array#transpose, Enumerable#reject, Enumerable#to_h work with tuples #10445

Merged

Conversation

HertzDevil
Copy link
Contributor

@HertzDevil HertzDevil commented Feb 26, 2021

As noted in #3718 (comment), methods cannot depend on typeof(exp.first) if exp is an Enumerable or Indexable, because it breaks when exp is also a Tuple:

{1, 'a'}.reject(Int32)        # Error: no overload matches 'Array(NoReturn)#<<' with type Char
{ {1, 'a'}, {"", true} }.to_h # Error: no overload matches 'Hash(Int32, Char)#[]=' with types (Int32 | String), Char
[{1, 'a'}].transpose          # Error: expected block to return T, not (Char | Int32)

This PR fixes them:

{1, 'a'}.reject(Int32)        # => ['a']
{ {1, 'a'}, {"", true} }.to_h # => {1 => 'a', "" => true}
[{1, 'a'}].transpose          # => [[1], ['a']]

typeof({1, 'a'}.reject(Int32))        # => Array(Char)
typeof({ {1, 'a'}, {"", true} }.to_h) # => Hash(Int32 | String, Bool | Char)
typeof([{1, 'a'}].transpose)          # => Array(Array(Char | Int32))

Some methods like Enumerable#flat_map call typeof(yield first), which is okay as long as the method also yields each element from the collection itself, but for clarity these first calls inside typeofs are also replaced with first_internal Enumerable.element_type(self).

src/enumerable.cr Outdated Show resolved Hide resolved
src/tuple.cr Outdated Show resolved Hide resolved
@straight-shoota straight-shoota added kind:bug A bug in the code. Does not apply to documentation, specs, etc. topic:stdlib:collection labels Feb 26, 2021
@asterite
Copy link
Member

I wonder if, instead of adding a nodoc methods to every type here, we can introduce an internal type inside Enumerable, with a method that computes this. For every object except tuple it calls first, but for tuple it would be different. What do you think?

@HertzDevil
Copy link
Contributor Author

HertzDevil commented May 30, 2021

#10429 does that:

module Enumerable(T)
  def self.element_type(x)
    x.each { |elem| return elem }
    ::raise ""
  end
end

class Foo # not an Enumerable
  def each
    yield 1
    yield 'a'
  end
end

typeof(Enumerable.element_type([1, 'a'])) # => (Char | Int32)
typeof(Enumerable.element_type({1, 'a'})) # => (Char | Int32)
typeof(Enumerable.element_type(Foo.new))  # => (Char | Int32)

Like String.interpolation, it also has public docs there.

@asterite
Copy link
Member

Cool! Can we bring that method from that PR into this one? I'll try to review the other one this week. Sorry, we can't catch up with so many great PRs! 😅

@HertzDevil
Copy link
Contributor Author

I didn't replace the internal methods in NamedTuple because Enumerable.element_type doesn't work on them (it requires the value to satisfy Enumerable's interface even if the value's type doesn't include the module, and that requires single-element yields whereas NamedTuple#each yields the key and value separately).

src/enumerable.cr Outdated Show resolved Hide resolved
# NOTE: there should never be a need to call this method outside the standard library.
def self.element_type(x)
x.each { |elem| return elem }
::raise ""
Copy link
Member

Choose a reason for hiding this comment

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

This method is intended for being used in typeof and not supposed to be actually called. But still, it can be. And then it should raise a proper exception, instead of an empty message.

Unfortunately it's not possible to move the typeof into the method because of nesting calls and yielded values. That would probably be the best solution.

Copy link
Member

Choose a reason for hiding this comment

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

Do we want element_type as a public method like this?

I realize it's already merged from #10429, thus not necessarily subject to this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It cannot be even protected because splat expansions must be able to access the method in any scope, for the same reason as ::String.interpolation being public.

Copy link
Member

Choose a reason for hiding this comment

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

I think it's actually good to have this public because it enables shards to use it if they need it.

Copy link
Member

Choose a reason for hiding this comment

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

But what about the empty raise? It's public API and can be called outside of typeof.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Either EmptyError or a generic BUG: exception, I guess.

Copy link
Member

@oprypin oprypin left a comment

Choose a reason for hiding this comment

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

Note: #10429 is merged, and now the diff from src/enumerable.cr entirely disappears, as it exactly matches what's in master.

@straight-shoota straight-shoota added this to the 1.2.0 milestone Jul 9, 2021
@straight-shoota straight-shoota merged commit 0cfc798 into crystal-lang:master Jul 25, 2021
@straight-shoota straight-shoota changed the title Make Array#transpose, Enumerable#reject, Enumerable#to_h work with tuples Make Array#transpose, Enumerable#reject, Enumerable#to_h work with tuples Jul 25, 2021
@HertzDevil HertzDevil deleted the bug/tuple-typeof-first branch July 25, 2021 16:29
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. topic:stdlib:collection
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants