They apply a given operator enclosed (or preceded or followed, in the case of unary operators) by « and/or » to one or two lists, returning the resulting list, (1) with the pointy part of « or » aimed at the shorter list. (2)
R1: I don't know the exact definition but I suspect not only lists are okay. (1, 2, 3).Seq >>+>> (4, 5).Seq succeeds, yielding the list (5, 7, 8). It would be good to use a more exact term: Positional, Iterable, or whatever else makes the statement true. If they are turned into lists using .List (or .cache for example), that could also be mentioned, like with many methods.
R2: this is a much more important point overall:
the word "list" is even more problematic when referring to the length of something that might not actually be a list
the declaration that the direction of the arrows must be decided based on the size of the (supposed) lists does not conform with the implementation: (1, 2, 3) <<+<< (4, 5) is a successful call yielding the list (5, 7).
it sounds impractical that the developer should give up on the power of choosing the "wrong direction" and keep the sizes in mind instead. In other words: I think the implementation is "more valid" in this case and the documentation should be rephrased in a way that hints the actual behavior. I have ideas about the phrasing for simple, 2-operand cases like this one - the directionality should be described in terms of what is fully traversed / what pushes values and what pulls them - but it can still fail for multi-operand cases that succeed.
Single elements are turned to a list, so they can be used too. (3) If one of the lists is shorter than the other, the operator will cycle over the shorter list until all elements of the longer list are processed. (4)
R4: I need to address this first. Again, this is only true if we respect (2) over the implementation - if we set the direction the opposite way, only the operand on the open end is fully traversed, as the same example shown. Actually, if these points were true... there wouldn't be any sense in having directions for the hyper metaoperators at all.
R3: I think this statement could really hold regardless (2) but sadly this also fails if the single element would be on the open end. See
(1, 2, 3).Seq <<+<< 4
Lists on either side of non-dwimmy hyperop of infix:<+> are not of the same length while recursing
left: 3 elements, right: 1 elements
Interestingly, (1, 2, 3).Seq <<+<< (4,) does work and yields (5, ) - so the error message hints that (3) was respected but (2) was enforced, unlike other cases. Also, it introduces the term "dwimmy" which I couldn't find in the docs - after some Googling, I concluded this is just a word play on DWIM as in "Do What I Mean". I don't know if this concept should appear in official stuff - but if it already appears in Rakudo error messages, it's still better if it does appear in the docs as well, with some formal(-ish) definition.
You can chain hyper operators to destructure a List of Lists.
This works and I didn't know about it - honestly, I wouldn't have expected this under "chaining hyper operators", I meant something else with that. However, even here, it shows that a) either the behavior has changed lately b) or the author also wasn't sure about the behavior, because the WhateverStar is not needed on the right side. The example would work just as good with ($p, )
Actually, I don't even know why it works with the WhateverStar. (1, 2, 3, 4, 5) >>+>> (4, *) gives (5, 6, 7, 8, 9) and the output doesn't change from Whatevers added to the end. However, if I add it in front, the right side acts like an empty List - similarly, elements that appear after the asterisk seem to be cut off. I don't know how much this is intended behavior but if it is, I'm willing to write it down; we need this covered because it's all black magic right now.
I haven't talked about the >>[&op]<< and <<[&op]>> configurations because they weren't covered for lists - however, I realize that I can't actually turn to my concrete motive without taking a look at what the docs say about it for hashes (Maps - perhaps all Associatives, even?); and how it works for Lists.
(1, 2) <<~>> <a b c> # ("1a", "2b", "1c")
(1, 2, 3, 4) <<~>> <a b c> # ("1a", "2b", "3c", "4a")
(1, 2) >>~<< <a b c> # ("1a", "2b", "1c") # Lists on either side of non-dwimmy hyperop of infix:<~> are not of the same length while recursing; left: 2 elements, right: 3 elements
From this, I conclude that the "diamond shape" sets the direction according to the longer side while the "cross shape" enforces equal size (by throwing an error in all other cases). Again, this seems to be a good description for non-nesting cases but it doesn't seem to always help for nesting.
Let's compare this to the docs about Maps:
Hyper operators can work with hashes. The pointy direction indicates if missing keys are to be ignored in the resulting hash. The enclosed operator operates on all values that have keys in both hashes. (5)
R5: It seems to me the enclosed operator also operates on values that are selected by the pointy direction but are missing from the other "hash" - in that case, an undefined value (probably corresponding to a missing key in that Map) is used, but apparently without the warning of using undefined values. (I'm gonna show the example later.)
For the directions:
Code
Keys
%foo «+» %bar;
intersection of keys (6)
%foo »+« %bar;
union of keys (7)
%outer »+» %inner;
only keys of %inner that exist in %outer will occur in the result (8)
I used my %a =:common, only-here => 12; my %b = :!common, only-there => 53 for testing, also created sub own-add($a, $b) { say "RUN with $a.raku() $b.raku()"; $a + $b } to confirm when the calls are made and with what arguments.
R6-7: the documentation seems to match the behavior - however, they both go opposite to the semantics with Lists. The diamond shape covered both lists - for maps, that's the cross shape. I think, given the condition of the documentation so far, the behavior could be harmonized in either direction, with little damage.
R8: I don't understand the statement. Wouldn't "only keys of %inner that exist in %outer" mean the intersection again? Anyway, I cannot read it in a way that matches the behavior: the keys of %outer will occur in the result, simple as that:
> say %a >>[&own-add]>> %b
RUN with 12 Any
RUN with Bool::True Bool::False
{common => 1, only-here => 12}
Arriving to the hyper chains
First I would return to the chain the docs mentioned: in that case, it seems like the inner arrows do absolutely nothing and everything is decided by the outer pair of arrows. I would like to think this is NYI rather than the design itself... if I just don't get what they are supposed to do, please enlighten me.
Last but not least... I wanted to use hyper metaoperators to join strings separated by some character, like <a b c> >>~>> '_' <<~<< <e f g>. This example does work and gives ("a-e", "b-f", "c-g") as I expected. However:
<a b c> >>~>> '-' <<~<< <e f g h> # ("a-e", "b-f", "c-g", "a-h")
<a b c i j> >>~>> '-' <<~<< <e f g h> # ("a-e", "b-f", "c-g", "i-h")
Seems like the right handside wins, no matter what? Some further examples:
<a b c i j> <<~<< '-' <<~<< <e f g h> # Lists on either side of non-dwimmy hyperop of infix:<~> are not of the same length while recursing; left: 5 elements, right: 1 elements
<a b c i j> >>~>> '-' >>~>> <e f g h> # ("a-e", "b-f", "c-g", "i-h", "j-e")
<a b c i j> >>~>> '-' >>~<< <e f g h> # Lists on either side of non-dwimmy hyperop of infix:<~> are not of the same length while recursing; left: 5 elements, right: 4 elements
The last example made me pay more attention to the associativity: is it possible that the hypered operator decides it all? Compare (1, 2, 3) Z+ (4, 5, 6) Z* (-3, -4, -5) to (1, 2, 3) <<+>> (4, 5, 6) <<*<< (-3, -4, -5) (or any direction really). The former says: "Only identical operators may be list associative; since 'Z+' and 'Z' differ, they are non-associative and you need to clarify with parentheses". The latter succeeds and it always seem to apply a+bc order, giving (-11, -18, -27).
Then I started experimenting with operators that have list associativity according to the docs:
(1, 2, 3) <<max>> (4, 5, 6) <<max<< (-3, -4, -5) # Only identical operators may be list associative; since '<<max>>' and '<<max<<' differ, they are non-associative and you need to clarify with parentheses
(1, 2, 3) <<||>> (4, 5, 6) <<&&<< (-3, -4, -5) # (1, 2, 3)
(1, 2, 3) <<&&>> (4, 5, 6) <<&&<< (-3, -4, -5) # (-3, -4, -5)
(1, 2, 3) <<&&>> (4, 5, 6) <<||<< (-3, -4, -5) # (4, 5, 6)
Something seems off about that complaint with max and the arrows in different directions - especially since it doesn't complain when combined with || which has the same precedence and list associativity, too.
Actually, comparing the results for && with the results for ~, it seems to me that && is in fact left-associative - which is perfectly fine but it contradicts the docs.
And here I got stuck because for actually list-associative operators, I couldn't get away with varied arrows...
... or any arrows apparently:
(1, 2, 3) <<max>> (4, 5, 6) <<max>> (-3, -4, -5) # Too many positionals passed; expected 2 arguments but got 3
(1, 2, 3) >>max>> (4, 5, 6) >>max>> (-3, -4, -5) # Too many positionals passed; expected 2 arguments but got 3
Now I really don't know what happened or what should have happened.
I think if we can target half of these problems and questions, that's already a huge achievement.
Hello,
I didn't want to apply a template because I thought this topic can cover parts of documentation, implementation and design itself.
The main focus of my investigations was the chaining of hyper on flat iterables.
"Preface" about what is documented currently
Let's read the documentation carefully (https://docs.raku.org/language/operators#index-entry-hyper_%3C%3C-hyper_%3E%3E-hyper_%C2%AB-hyper_%C2%BB-Hyper_operators)
R1: I don't know the exact definition but I suspect not only lists are okay.
(1, 2, 3).Seq >>+>> (4, 5).Seq
succeeds, yielding the list(5, 7, 8)
. It would be good to use a more exact term: Positional, Iterable, or whatever else makes the statement true. If they are turned into lists using .List (or .cache for example), that could also be mentioned, like with many methods.R2: this is a much more important point overall:
(1, 2, 3) <<+<< (4, 5)
is a successful call yielding the list(5, 7)
.R4: I need to address this first. Again, this is only true if we respect (2) over the implementation - if we set the direction the opposite way, only the operand on the open end is fully traversed, as the same example shown. Actually, if these points were true... there wouldn't be any sense in having directions for the hyper metaoperators at all.
R3: I think this statement could really hold regardless (2) but sadly this also fails if the single element would be on the open end. See
Interestingly,
(1, 2, 3).Seq <<+<< (4,)
does work and yields(5, )
- so the error message hints that (3) was respected but (2) was enforced, unlike other cases. Also, it introduces the term "dwimmy" which I couldn't find in the docs - after some Googling, I concluded this is just a word play on DWIM as in "Do What I Mean". I don't know if this concept should appear in official stuff - but if it already appears in Rakudo error messages, it's still better if it does appear in the docs as well, with some formal(-ish) definition.This works and I didn't know about it - honestly, I wouldn't have expected this under "chaining hyper operators", I meant something else with that. However, even here, it shows that a) either the behavior has changed lately b) or the author also wasn't sure about the behavior, because the WhateverStar is not needed on the right side. The example would work just as good with
($p, )
Actually, I don't even know why it works with the WhateverStar.
(1, 2, 3, 4, 5) >>+>> (4, *)
gives(5, 6, 7, 8, 9)
and the output doesn't change from Whatevers added to the end. However, if I add it in front, the right side acts like an empty List - similarly, elements that appear after the asterisk seem to be cut off. I don't know how much this is intended behavior but if it is, I'm willing to write it down; we need this covered because it's all black magic right now.I haven't talked about the >>[&op]<< and <<[&op]>> configurations because they weren't covered for lists - however, I realize that I can't actually turn to my concrete motive without taking a look at what the docs say about it for hashes (Maps - perhaps all Associatives, even?); and how it works for Lists.
From this, I conclude that the "diamond shape" sets the direction according to the longer side while the "cross shape" enforces equal size (by throwing an error in all other cases). Again, this seems to be a good description for non-nesting cases but it doesn't seem to always help for nesting.
Let's compare this to the docs about Maps:
R5: It seems to me the enclosed operator also operates on values that are selected by the pointy direction but are missing from the other "hash" - in that case, an undefined value (probably corresponding to a missing key in that Map) is used, but apparently without the warning of using undefined values. (I'm gonna show the example later.)
For the directions:
I used
my %a =:common, only-here => 12; my %b = :!common, only-there => 53
for testing, also createdsub own-add($a, $b) { say "RUN with $a.raku() $b.raku()"; $a + $b }
to confirm when the calls are made and with what arguments.R6-7: the documentation seems to match the behavior - however, they both go opposite to the semantics with Lists. The diamond shape covered both lists - for maps, that's the cross shape. I think, given the condition of the documentation so far, the behavior could be harmonized in either direction, with little damage.
R8: I don't understand the statement. Wouldn't "only keys of %inner that exist in %outer" mean the intersection again? Anyway, I cannot read it in a way that matches the behavior: the keys of %outer will occur in the result, simple as that:
Arriving to the hyper chains
First I would return to the chain the docs mentioned: in that case, it seems like the inner arrows do absolutely nothing and everything is decided by the outer pair of arrows. I would like to think this is NYI rather than the design itself... if I just don't get what they are supposed to do, please enlighten me.
Last but not least... I wanted to use hyper metaoperators to join strings separated by some character, like
<a b c> >>~>> '_' <<~<< <e f g>
. This example does work and gives("a-e", "b-f", "c-g")
as I expected. However:Seems like the right handside wins, no matter what? Some further examples:
The last example made me pay more attention to the associativity: is it possible that the hypered operator decides it all? Compare
(1, 2, 3) Z+ (4, 5, 6) Z* (-3, -4, -5)
to(1, 2, 3) <<+>> (4, 5, 6) <<*<< (-3, -4, -5)
(or any direction really). The former says: "Only identical operators may be list associative; since 'Z+' and 'Z' differ, they are non-associative and you need to clarify with parentheses". The latter succeeds and it always seem to apply a+bc order, giving(-11, -18, -27)
.Then I started experimenting with operators that have list associativity according to the docs:
Something seems off about that complaint with max and the arrows in different directions - especially since it doesn't complain when combined with
||
which has the same precedence and list associativity, too.Actually, comparing the results for
&&
with the results for~
, it seems to me that&&
is in fact left-associative - which is perfectly fine but it contradicts the docs.And here I got stuck because for actually list-associative operators, I couldn't get away with varied arrows...
... or any arrows apparently:
Now I really don't know what happened or what should have happened.
I think if we can target half of these problems and questions, that's already a huge achievement.