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

Add missing documentation to Hash.merge!(other : Hash, &block) #8380

Merged
merged 1 commit into from
Nov 8, 2019

Conversation

jan-zajic
Copy link
Contributor

Completely missing description in api docs for Hash.merge! method, version with block. Without description, one cannot known which arguments are passed to given block (Except Ruby programmers, of course, because it behave same).
Related to #8377.

src/hash.cr Outdated Show resolved Hide resolved
src/hash.cr Outdated
@@ -1343,6 +1343,15 @@ class Hash(K, V)
self
end

# Adds the contents of *other* to this hash.
# The value of each duplicate key is determined by calling the block with the key, its value in this hash and its value in *other*.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe a little bit more verbose description, but help understanding:

Suggested change
# The value of each duplicate key is determined by calling the block with the key, its value in this hash and its value in *other*.
# If a key exists in both hashes, the given block is called to determine the value to be used. The block arguments are the key, the value in `self` and the value in *other*.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right i chaged it.

src/hash.cr Outdated
# other = { "b" => 254, "c" => 300 }
# hash.merge!(other) { |key, v1, v2| v1 }
# hash # => {"a" => 100, "b" => 200, "c" => 300}
#```
def merge!(other : Hash, &block)
Copy link
Member

Choose a reason for hiding this comment

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

You can also add documentation for the block types:

Suggested change
def merge!(other : Hash, &block)
def merge!(other : Hash, & : (K, V, V) -> V) : self

Copy link
Contributor Author

@jan-zajic jan-zajic Oct 25, 2019

Choose a reason for hiding this comment

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

Are you sure? Types were specified, but removed in 3c17d07 (#1693). And it's sad for me, i read discussion here #5155 and say wow. It's seems like a very big bug for me - someone should fix this recursive types or it must be removed from lang, if they block clear type safety like here...

Copy link
Member

Choose a reason for hiding this comment

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

Wow, amazing that you digged up that old issue.

But anyway, even without recursive aliases, the type restriction is incorrect, because the generic arguments of other don't need to equal those of self, they can also be smaller.
For example you can merge Hash(String, Bool) into Hash(String | Int32, Bool | Nil). This can't be expressed currently, but is tracked in #3803. So, yeah let's leave it for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

At least the new syntax for non-captured blocks can be used.

@jan-zajic
Copy link
Contributor Author

And now i found that i forgot non bang version with block, def merge(other : Hash(L, W), &block : K, V, W -> V | W) forall L, W. But when i try it, i found maybe a bug, at least it's very confusing. It's documented to accept other hash with any types of keys and values, but block is defined to receive type of key K as in self, but if you look at implementation, it iterates second one so it can't compile at all.
Try for example this:

h1 = {"a" => 1, "b" => 2, "c" => 3}
h2 = {:a => 1, :b => 2, :c => 3}
h3 = h1.merge(h2){|k,v1,v2| v1||v2}

which leads to:
Error: argument #1 of yield expected to be String, not Symbol
In my opinion it must be reimplemented or changed to: def merge(other : Hash(K, W), &block : K, V, W -> V | W) forall W
Than we should add documentation to it.

@RX14
Copy link
Contributor

RX14 commented Oct 31, 2019

@jan-zajic how about

def merge(other : Hash(K2, V2), & : (K | K2, V, V2) -> V | V2) forall K2, V2

?

Copy link
Contributor

@RX14 RX14 left a comment

Choose a reason for hiding this comment

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

Thank you! This is already a strict improvement to the documentation.

@@ -1343,7 +1343,17 @@ class Hash(K, V)
self
end

def merge!(other : Hash, &block)
# Adds the contents of *other* to this hash.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
# Adds the contents of *other* to this hash.
# Adds the contents of *other* to `self`.

@straight-shoota
Copy link
Member

straight-shoota commented Oct 31, 2019

@RX14

def merge(other : Hash(K2, V2), & : (K | K2, V, V2) -> V | V2) forall K2, V2

That would work, but I think it's to complicated. The type restriction is not necessary for technical reasons, but mainly for documentation. For improving the documentation, this looks too complex IMO. And it's confusing because it's not really for all K2, V2. They must be covariant with K, V.

Only benefit would be more precise error messages when using this method with non-matching types.

Btw. the key in the block argument is not K | K2 but only K because only keys from self are yielded.

@RX14
Copy link
Contributor

RX14 commented Nov 8, 2019

They must be covariant with K, V.

Ah, I didn't look at the implementation....

class Hash(K, V)
  def merge2(other : Hash(K2, V2), & : (K2, V, V2) -> V | V2) : Hash(K | K2, V | V2) forall K2, V2
    hash = Hash(K | K2, V | V2).new
    hash.merge! self
    
    other.each do |k2, v2|
      if self.has_key?(k2)
        hash[k2] = yield k2, self[k2], v2
      else
        hash[k2] = v2
      end
    end
  
    hash
  end
end

This allows for the full type signature.

That's for another PR though...

That being said, I'd very much like the full type signature in the docs. That type signature encodes a lot of information, for example it's clear to see that the block is encoding a choice when you see V, V2 -> V | V2.

Plus it's actually K2 which is yielded.

Copy link
Contributor

@RX14 RX14 left a comment

Choose a reason for hiding this comment

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

Shall we merge?

Copy link
Member

@asterite asterite left a comment

Choose a reason for hiding this comment

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

Great, thank you!

@asterite asterite merged commit e09bdce into crystal-lang:master Nov 8, 2019
@asterite asterite added this to the 0.32.0 milestone Nov 8, 2019
@straight-shoota
Copy link
Member

@RX14 Your code looks good. But that's actually #merge not #merge!. And it matches its current implementation 😄

@RX14
Copy link
Contributor

RX14 commented Nov 8, 2019

@straight-shoota We were talking about the non-bang version the whole time! I was responding to @jan-zajic's comment about the non-bang version. Also, my implementation is different to the current implementation in that it uses self[k2] instead of hash[k2] which restricts the key type sent to the block from K | K2 to K2.

@jan-zajic
Copy link
Contributor Author

@straight-shoota @RX14 yes that #8380 (comment) comment ;). Well that this is already merged. We can continue that discussion in some issue or in the forum ...

@RX14
Copy link
Contributor

RX14 commented Nov 8, 2019

Yeah, I was planning to make this another PR.

@straight-shoota
Copy link
Member

Ooops, I think I got lost somewehere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants