Raku / doc

🦋 Raku documentation
https://docs.raku.org/
Artistic License 2.0
286 stars 293 forks source link

Potential trap with concatenating Bufs/Blobs, [~] returns a Str #2124

Closed AlexDaniel closed 6 years ago

AlexDaniel commented 6 years ago

Let's say you have a bunch of Bufs or Blobs in array @chunks, and you want to concat them:

my @chunks = Blob.new(60, 61, 62), Blob.new(63, 64, 65);
my $x = [~] @chunks;
say $x; # Blob:0x<3c 3d 3e 3f 40 41>

This will work fine as long as you have something in @chunks. However, if it happens that @chunks is empty, then [~] returns an empty Str ("").

my @chunks;
my $x = [~] @chunks;
say $x.perl; # ""

Further operations with $x may fail because you don't necessarily expect a Str there. For example, doing $x.decode will not work.

The shortest way to get what you actually want (with all inputs) is Blob.new: |«@chunks, or alternatively you can call .append on a Buf in a loop.

rafaelschipiura commented 6 years ago

Should Nil ~ Nil return an empty Str at all?

JJ commented 6 years ago

I seem to remember there was something related to that kind of operator working on empty lists... It's kind of a more general thing if I remember correctly. Also, since when do we have the trap label?

JJ commented 6 years ago

I think this is basically the consequence of putting Nil or Any in a string context. ~ becomes the string contextualizer then. But... that's rightfully a trap, the problem is that I don't know how general will that be. My hunch is that any empty list will get the same treatment.

rafaelschipiura commented 6 years ago

@JJ AlexDaniel created the trap label a few hours ago: https://github.com/perl6/doc/issues/1603#event-1697561775

AlexDaniel commented 6 years ago

It has nothing to do with Nils. It's just that calling &infix:<~>() with no args returns an empty string. You can do this kind of stuff with other ops, like * returns 1, + returns 0, min returns Inf.

AlexDaniel commented 6 years ago

See also: https://en.wikipedia.org/wiki/Identity_element

JJ commented 6 years ago

But my point is that the trap would be concatenating anything that is concatenated with ~. Come to think of it, it might be only Blob/Bufs and Strs...

JJ commented 6 years ago

Please also note that @chunks above is an empty Positional, not an empty Blob...

JJ commented 6 years ago

In fact:

my Blob $chunks; 
say $chunks.^name; 
say ([~] $chunks).^name; 
# OUTPUT: «Blob␤Use of uninitialized value of type Blob in string context.
Methods .^name, .perl, .gist, or .say can be used to stringify it to something meaningful.
Str␤  in block <unit> at /tmp/S2dkUqd2ea line 1␤»

So the traps is unrelated to Blob/Buf, as far as I can see...

AlexDaniel commented 6 years ago

No trap was added?

JJ commented 6 years ago

Well, the whole thing is mentioned but not as a trap. I don't really see it as a trap, but I can add an entry there pointing to the explanation in the contexts page.

AlexDaniel commented 6 years ago

I don't think it's entirely right, and I don't think that the issue mentioned in this ticket is covered. To recap, the issue here is that the identity element for concatenation is an empty String, it has nothing to do with concatenating Bufs/Blobs with other types. I'll edit it later myself.

JJ commented 6 years ago

@AlexDaniel I think it does. The problem is that ~ concats either Bufs or Strings. Not everything can be converted to Buf, but most stuff can be converted to Str. So as long as there's something that's not Buf, the first form kicks in, trying to convert a Buf into a String.

JJ commented 6 years ago

I mean, in your example above, $x is already a String. Of course any Buf operation fails, it's not a Buf.

AlexDaniel commented 6 years ago

The problem is that ~ concats either Bufs or Strings

Correct.

[…] So as long as there's something that's not Buf

The problem is that when there's nothing (no Bufs, no Strs), it gives its identity element "" (which isn't obviously what you expect when you're working with Bufs/Blobs).

I mean, in your example above, $x is already a String. Of course any Buf operation fails, it's not a Buf.

Correct, but I never intended it to be a Str. That's what this trap is about. I was working with array of Blobs, and I didn't think of an edge case when my array of Blobs was actually empty (so it's an array of nothing, I understand).

JJ commented 6 years ago

Let me see if I follow you. What you are saying is that we reduce an empty array via the concat operator. It's empty, so it could be either strings or bufs so it could opt for returning an empty string or an empty Buf. However, it opts for the former, and that's a trap. Is that it?

JJ commented 6 years ago

Also, this reduce operator on an empty list should return identity for the operation. But that's old, and a lot has rained since then...

JJ commented 6 years ago

Ping...

AlexDaniel commented 6 years ago

Yeah, it's much better now. But there's a way to go for perfection, I'll do some finishing touches during the squashathon.

JJ commented 6 years ago

Ping...