Problem-solving: Gist type objects as `Foo:U` rather than `(Foo)`

Created on 16 May 2020  Â·  26Comments  Â·  Source: Raku/problem-solving

https://stackoverflow.com/questions/61818213/what-is-the-meaning-of-any-in-raku-specifically-the/61818345

The gisting of type objects is from an era when we did not have type smileys (or at least not implemented) yet, so it's maybe a good time to rethink this. Judging from the test fallout, it looks like we want to make this a version dependent thing.

I've applied this change and it causes some make test fallout:

t/02-rakudo/13-exceptions.t                                   (Wstat: 256 Tests: 3 Failed: 1)
  Failed test:  1
  Non-zero exit status: 1
t/05-messages/02-errors.t                                     (Wstat: 256 Tests: 37 Failed: 1)
  Failed test:  22
  Non-zero exit status: 1

And quite significant make spectest fallout:

t/spec/S02-types/WHICH.t                                        (Wstat: 65024 Tests: 1609 Failed: 398)
  Failed tests:  8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48
                56, 60, 64, 68, 72, 76, 80, 84, 88, 92
                96, 100, 104, 108, 112, 116, 120, 124, 128
                132, 136, 144, 148, 152, 156, 160, 164
                168, 172, 176, 180, 184, 188, 192, 196
                200, 204, 208, 212, 216, 220, 224, 228
                232, 236, 240, 244, 248, 252, 256, 260
                264, 268, 272, 276, 280, 284, 288, 292
                296, 300, 304, 308, 312, 316, 320, 324
                328, 332, 336, 340, 344, 348, 352, 356
                360, 364, 368, 372, 376, 380, 384, 388
                392, 396, 400, 404, 408, 412, 416, 420
                424, 428, 432, 436, 440, 444, 448, 452
                456, 460, 464, 468, 472, 476, 480, 484
                488, 492, 496, 500, 504, 508, 512, 516
                520, 524, 528, 532, 536, 540, 544, 548
                552, 556, 560, 564, 568, 572, 576, 580
                584, 588, 592, 596, 600, 604, 608, 612
                616, 620, 624, 628, 632, 636, 640, 644
                648, 652, 656, 660, 664, 668, 672, 676
                680, 684, 688, 692, 696, 700, 704, 708
                712, 716, 720, 724, 728, 732, 736, 740
                744, 748, 752, 756, 760, 764, 768, 772
                776, 780, 784, 788, 792, 796, 800, 804
                808, 812, 816, 820, 824, 828, 832, 836
                840, 844, 848, 852, 856, 860, 864, 868
                872, 876, 880, 884, 888, 892, 896, 900
                904, 908, 912, 916, 920, 924, 928, 932
                936, 940, 944, 948, 952, 956, 960, 964
                968, 972, 976, 980, 984, 988, 992, 996
                1000, 1004, 1008, 1012, 1016, 1020, 1024
                1028, 1032, 1036, 1040, 1044, 1048, 1052
                1056, 1060, 1064, 1068, 1072, 1076, 1080
                1084, 1088, 1092, 1096, 1100, 1104, 1108
                1112, 1116, 1120, 1124, 1128, 1132, 1136
                1140, 1144, 1148, 1152, 1156, 1160, 1164
                1168, 1172, 1176, 1180, 1184, 1188, 1192
                1196, 1200, 1204, 1208, 1212, 1216, 1220
                1224, 1228, 1232, 1236, 1240, 1244, 1248
                1252, 1256, 1260, 1264, 1268, 1272, 1276
                1280, 1284, 1288, 1292, 1296, 1300, 1304
                1308, 1312, 1316, 1320, 1324, 1328, 1332
                1336, 1340, 1344, 1348, 1352, 1356, 1360
                1364, 1368, 1372, 1376, 1380, 1384, 1388
                1392, 1396, 1400, 1404, 1408, 1412, 1416
                1420, 1424, 1428, 1432, 1436, 1440, 1444
                1448, 1452, 1456, 1460, 1464, 1468, 1472
                1476, 1480, 1484, 1488, 1492, 1496, 1500
                1504, 1508, 1512, 1516, 1520, 1524, 1528
                1532, 1536, 1540, 1544, 1548, 1552, 1556
                1560, 1566, 1570, 1574, 1578, 1582, 1586
                1590, 1594, 1598, 1602, 1606
  Non-zero exit status: 254
t/spec/S02-types/array.rakudo.moar                              (Wstat: 256 Tests: 105 Failed: 1)
  Failed test:  93
  Non-zero exit status: 1
t/spec/S02-types/native.rakudo.moar                             (Wstat: 256 Tests: 95 Failed: 1)
  Failed test:  21
  Non-zero exit status: 1
t/spec/S02-types/pair.rakudo.moar                               (Wstat: 768 Tests: 182 Failed: 3)
  Failed tests:  164-166
  Non-zero exit status: 3
t/spec/S02-types/undefined-types.rakudo.moar                    (Wstat: 512 Tests: 49 Failed: 2)
  Failed tests:  46-47
  Non-zero exit status: 2
t/spec/S03-junctions/misc.t                                     (Wstat: 512 Tests: 150 Failed: 2)
  Failed tests:  34-35
  Non-zero exit status: 2
t/spec/S03-operators/context.t                                  (Wstat: 768 Tests: 38 Failed: 3)
  Failed tests:  31-33
  Non-zero exit status: 3
t/spec/S03-metaops/reduce.rakudo.moar                           (Wstat: 1280 Tests: 561 Failed: 5)
  Failed tests:  508, 512, 518, 520, 522
  Non-zero exit status: 5
t/spec/S05-grammar/inheritance.rakudo.moar                      (Wstat: 256 Tests: 35 Failed: 1)
  Failed test:  32
  Non-zero exit status: 1
t/spec/S06-multi/unpackability.t                                (Wstat: 256 Tests: 10 Failed: 1)
  Failed test:  6
  Non-zero exit status: 1
t/spec/S06-multi/type-based.rakudo.moar                         (Wstat: 256 Tests: 65 Failed: 1)
  Failed test:  6
  Non-zero exit status: 1
t/spec/S06-parameters/smiley.rakudo.moar                        (Wstat: 1024 Tests: 39 Failed: 4)
  Failed tests:  36-39
  Non-zero exit status: 4
t/spec/S06-traits/as.t                                          (Wstat: 512 Tests: 5 Failed: 2)
  Failed tests:  1, 3
  Non-zero exit status: 2
t/spec/S06-signature/unpack-array.t                             (Wstat: 512 Tests: 15 Failed: 2)
  Failed tests:  6-7
  Non-zero exit status: 2
t/spec/S09-typed-arrays/arrays.rakudo.moar                      (Wstat: 256 Tests: 84 Failed: 1)
  Failed test:  81
  Non-zero exit status: 1
t/spec/S12-class/attributes.t                                   (Wstat: 512 Tests: 39 Failed: 2)
  Failed tests:  37-38
  Non-zero exit status: 2
t/spec/S11-modules/require.t                                    (Wstat: 512 Tests: 58 Failed: 2)
  Failed tests:  1, 3
  Non-zero exit status: 2
t/spec/S12-class/lexical.t                                      (Wstat: 256 Tests: 14 Failed: 1)
  Failed test:  8
  Non-zero exit status: 1
t/spec/S12-class/mro-6c.t                                       (Wstat: 256 Tests: 5 Failed: 1)
  Failed test:  2
  Non-zero exit status: 1
t/spec/S12-class/type-object.t                                  (Wstat: 256 Tests: 6 Failed: 1)
  Failed test:  3
  Non-zero exit status: 1
t/spec/S12-coercion/coercion-types.rakudo.moar                  (Wstat: 256 Tests: 21 Failed: 1)
  Failed test:  19
  Non-zero exit status: 1
t/spec/S12-introspection/attributes.rakudo.moar                 (Wstat: 1024 Tests: 45 Failed: 4)
  Failed tests:  3, 9, 14, 34
  Non-zero exit status: 4
t/spec/S12-enums/thorough.rakudo.moar                           (Wstat: 256 Tests: 60 Failed: 1)
  Failed test:  1
  Non-zero exit status: 1
t/spec/S12-enums/basic.rakudo.moar                              (Wstat: 256 Tests: 54 Failed: 1)
  Failed test:  16
  Non-zero exit status: 1
t/spec/S12-methods/what.t                                       (Wstat: 2560 Tests: 33 Failed: 10)
  Failed tests:  9, 16-22, 26, 28
  Non-zero exit status: 10
t/spec/S12-methods/private.t                                    (Wstat: 256 Tests: 14 Failed: 1)
  Failed test:  14
  Non-zero exit status: 1
t/spec/S14-roles/lexical.t                                      (Wstat: 256 Tests: 9 Failed: 1)
  Failed test:  4
  Non-zero exit status: 1
t/spec/S14-roles/instantiation.t                                (Wstat: 256 Tests: 19 Failed: 1)
  Failed test:  5
  Non-zero exit status: 1
t/spec/S14-roles/namespaced.t                                   (Wstat: 256 Tests: 11 Failed: 1)
  Failed test:  1
  Non-zero exit status: 1
t/spec/S14-roles/parameterized-basic.t                          (Wstat: 512 Tests: 41 Failed: 2)
  Failed test:  5
  Non-zero exit status: 1
t/spec/S16-io/say.t                                             (Wstat: 256 Tests: 6 Failed: 1)
  Failed test:  5
  Non-zero exit status: 1
t/spec/S16-io/say-and-ref.t                                     (Wstat: 256 Tests: 3 Failed: 1)
  Failed test:  3
  Non-zero exit status: 1
t/spec/S10-packages/basic.rakudo.moar                           (Wstat: 2048 Tests: 83 Failed: 8)
  Failed tests:  2, 55, 64, 67, 70, 73, 76, 79
  Non-zero exit status: 8
t/spec/S32-container/stringify.t                                (Wstat: 1024 Tests: 16 Failed: 4)
  Failed tests:  1, 5, 9, 13
  Non-zero exit status: 4
t/spec/S32-io/io-handle.t                                       (Wstat: 256 Tests: 30 Failed: 1)
  Failed test:  22
  Non-zero exit status: 1
t/spec/S32-io/io-cathandle.rakudo.moar                          (Wstat: 256 Tests: 30 Failed: 1)
  Failed test:  9
  Non-zero exit status: 1
t/spec/S32-num/stringify.rakudo.moar                            (Wstat: 512 Tests: 62 Failed: 2)
  Failed tests:  45, 58
  Non-zero exit status: 2
t/spec/S32-str/utf8-c8.t                                        (Wstat: 65280 Tests: 54 Failed: 0)
  Non-zero exit status: 255
  Parse errors: Bad plan.  You planned 66 tests but ran 54.
t/spec/integration/advent2009-day01.t                           (Wstat: 256 Tests: 2 Failed: 1)
  Failed test:  1
  Non-zero exit status: 1
t/spec/integration/advent2009-day18.t                           (Wstat: 256 Tests: 7 Failed: 1)
  Failed test:  2
  Non-zero exit status: 1
t/spec/integration/advent2013-day02.t                           (Wstat: 256 Tests: 12 Failed: 1)
  Failed test:  3
  Non-zero exit status: 1
t/spec/integration/advent2013-day08.t                           (Wstat: 1024 Tests: 11 Failed: 4)
  Failed tests:  1-3, 6
  Non-zero exit status: 4
t/spec/integration/advent2013-day15.t                           (Wstat: 1280 Tests: 30 Failed: 5)
  Failed tests:  1, 12, 16, 26, 28
  Non-zero exit status: 5
t/spec/integration/pair-in-array.t                              (Wstat: 768 Tests: 3 Failed: 3)
  Failed tests:  1-3
  Non-zero exit status: 3
t/spec/integration/weird-errors.rakudo.moar                     (Wstat: 256 Tests: 35 Failed: 1)
  Failed test:  3
  Non-zero exit status: 1
language

Most helpful comment

This discussion has been done many times in the past 20 years. So in a way, it is way too late to suggest changes like these.

@rjattrill I don't think you appreciate the intent of the .gist method: it is intended to provide a human readable form of an object, to literally "give you a gist" of an object. Without needing to be syntactically correct. If you want syntactic correctness, you need the raku method, which should provide a Str with code that can be EVALled to get the original object back (as far as possible, because e.g. Code objects are very hard in that respect).

I have just been playing with IRB in Ruby and Reply in Perl. Both of those return quoted strings for variables with strings and the word nil or undef for undefined variables:

From this I gather that you'd be most interested in having the behaviour of the REPL change. To that end, I have committed: https://github.com/rakudo/rakudo/commit/285717a0d3

I hope this will stick.

All 26 comments

Why not simply Foo:U? Or even use some other Unicode grouping character, to visually make it clearly different to lists?

That would work for me as well. :-) But would still suffer from the same test fallout

Yep, that will have to be changed, I guess. Any way, maybe tests need to be revised; in some cases, they might be found to be testing the wrong thing. Not in the case of WHICH.

Foo:U I'm a bit iffy on. How would you differentiate between literal Foo:U type objects and plain old Foo ones this way like you could before?

This issue doesn't clearly state a problem to be solved, but just proposes a change. Problem solving language issues should first establish that a problem exists.

The goal of wrapping parens around type object names in .gist was to make them look different, so as to be less likely to be confused with data. Given the result in the linked Stack Overflow ticket was a question asking "what is this", it apparently works decently well at that.

As noted by @Kaiepi, sticking a :U in there will only cause confusion when the type object actually is a definiteness type. It could also be unfamiliar syntax to language learners who didn't come across those yet, and so feels liable to increase confusion.

That was closed off quite quickly. As a newcomer (Any) doesn't look anything like nil or undef which is what I get in other languages. For newcomers using the language for typical tasks, knowing that a value is undefined is possibly more important than what base class it came from.
Just () would have been better. For Raku to get more popular uptake it must be more accessible to newcomers.

That was closed off quite quickly. As a newcomer (Any) doesn't look anything like nil or undef which is what I get in other languages. For newcomers using the language for typical tasks, knowing that a value is undefined is possibly more important than what base class it came from.
Just () would have been better. For Raku to get more popular uptake it must be more accessible to newcomers.

One of the tricks is that when modifications are made, they have to internally consistent. One of the problematic cases might be:

my Str $a;       # typed to Str, not defined
my     $b;       # typed to Any, not defined
my     $c = Str; # typed to Any, but *defined*

Right now, if you were to add .say for $a, $b, $c, the output would be (stripping newlines) (Str) (Any) (Str). If we changed to using the type smiley :U, it should only change the first two. But it leaves us with a question of how to gist an Any-typed variable whose value is a type value. It's not that there's no way a change would be made, but it as Jonathan points out, there needs to be a really good case made, since it would involve changes that would be (as shown by the failed tests in lizmat's OP) quite pervasive. At the moment, the (Type) fills the niche decently well, although its meaning does need to be learned.

Understand that I say the above as someone who would love a way to distinguish it nicely (I was the one on SE that proposed using (Foo:U), after all). A lower bar to clear might be to use a different symbol like ⸢Foo⸣, sort of like how Match objects (which use 「」 to show their contents), but the issue there is that after ()[]{}, you're having to count on the Unicode character to be available.

I feel like I am being a nuisance. But given that everybody is taking a lot of time to explain these things to me - I do have a question: How important is it to see the Type for an undefined scalar in a .gist? The only time a type seems to be shown is for an undefined scalar. It is not shown for an undefined array or hash or a defined scalar. See for example:

> my $x
(Any)
> my $y=1
1
> my @a
[]
> my %h
{}

In the above example (Any) does look inconsistent to me.
As an outsider it feels like Any is an implementation detail that is being inappropriately exposed.

I can see Any if if I do $x.^name. Not sure if I need to see it in the .gist context.

If it didn't look quite so incongrous and surprising I would quietly go away. I do appreciate all the explanations offered here and on SO.

If .gist was just to return either () or an empty string (which is what Perl 5 does) your 3 cases would all behave consistently.

On Tue, May 19, 2020 at 01:39:03AM -0700, Ross Attrill wrote:

[...] How important is it to see the Type for an undefined scalar in a .gist? The only time a type seems to be shown is for an undefined scalar. It is not shown for an undefined array or hash or a defined scalar. See for example:

> my $x
(Any)
> my $y=1
1
> my @a
[]
> my %h
{}

In the above example (Any) does look inconsistent to me.

...except that in the above example, $x is the only thing holding an undefined value. Both @a and %h are defined -- empty, but defined.

> my @a
[]
> say @a.defined
True
> my %h
{}
> say %h.defined
True

Pm

That's even more inconsistent then @pmichaud.
Maybe
my $x
should create a defined empty scalar in the same way that my %h creates an empty hash or my @a an empty array.

El mar., 19 may. 2020 a las 10:55, Ross Attrill (notifications@github.com)
escribió:

That's even more inconsistent then @pmichaud https://github.com/pmichaud
.
Maybe
my $x
should create a defined empty scalar in the same way that my %h creates
an empty hash or my @a an empty array.

There's no such thing as a defined empty scalar, I'm afraid. A Scalar is
simply an "internal indirection" https://docs.raku.org/type/Scalar

I find () a bit overloaded when it comes to gist. (Any) could be an undefined, untyped scalar...

bastille% raku -e 'say my $'
(Any)

...or it could be a one element list of strings:

bastille% raku -e 'say ("Any",)'
(Any)

Whenever I'm using say like this, I don't always know exactly what type of object I'm stringifying and outputting. Usually, the gists of various types are different enough for me to be able to say "OK, this is probably an x," but this isn't the case when the parentheses get involved; there's an extra step in my thought process where I have to think about whether it's sensible for the gist to be that of a type object or not. Sure, the parentheses do make type objects look different from other kinds of values, but maybe not different enough.

How important is it to see the Type for an undefined scalar in a .gist?

I think you're getting too caught up on how undefined scalars are type objects. There are other cases where type objects can pop up, particularly when you're metaprogramming. I find it helpful to be able to see the names of type objects from a simple say like this.

I think we can fix the say my $ case to something more meaningful, like $(Any)

I think we can fix the say my $ case to something more meaningful, like $(Any)

Only if we make every gist of every scalar also do that too. At which point, why not just use dd?

No, this would just be for the case that say gets a type object, and we then prefix the .gist with a $. Or would that be too magic?

Or would that be too magic?

Yes. The most I can see us doing in this space is finding an alternative to wrapping it in parens to represent its type-object nature, to avoid the ambiguity with a single-element list, but as you've already discovered, any change in this area has back-compat consequences, both in software terms, but also for everyone who will have to re-learn it.

Moreover, it's far from the only situation you can construct where the gist is, well, just the gist of what you have, not a precise dump of what you have for debugging purposes. I mean, I can make a string "[1 2 3]", which is indistinguishable from [1, 2, 3]. Which is why things like dd and .raku exist.

Maybe
my $x
should create a defined empty scalar in the same way that my %h creates an empty hash or my @a an empty array.

It does, it's just that Scalar containers are transparent. Although what exactly "empty" means is where this topic all began.

A Scalar can be thought of as having a $!value attribute, which has to be set to something. There is no null or undef in Raku (there's Nil, but it decays to the appropriate type object upon assignment into a Scalar). Instead, type objects play that role (or put another way, you have many typed undefs). This is the reason one can do things like my SomeType $foo .= new; and have it work: because $foo starts out holding the SomeType type object, and we can then call .new on it to make an instance.

Noting the significant backward compatibility issues in code, training and maybe documentation, is it technically possible for the .gist of an undefined scalar (of any type) to return an empty string?

Documentation is not a big deal; it can be simply refactored. I wouldn't
worry about that.

Well I was going to ask why not just an empty string then? It would make this example from above:

> my $x
(Any)
> my $y=1
1
> my @a
[]
> my %h
{} 
> my $s='string'
string

look more elegant I think. Information on type is lost for the sake of:

  • consistency,
  • hiding implementation details,
  • simplicity,
  • avoiding overloading () as per @Kaiepi 's comment above.

But then I did this:

> my @a[2]
[(Any) (Any)]

That just wouldn't work if the .gist for an undefined scalar became an empty string.

Maybe it is unfortunate that the .gist of a my $s='string' is unquoted string. If the gist of a string was quoted, then the .gist of my $x; could just be the word undef. Or undef (Any) - or anything you wanted. undef (Any) says more than (Any) but is getting a bit verbose.

I have just been playing with IRB in Ruby and Reply in Perl. Both of those return quoted strings for variables with strings and the word nil or undef for undefined variables:

Perl:

C:\>reply
0> my $s='s'
$res[0] = 's'
1> my $x
$res[1] = undef
2> $x
$res[2] = undef

Ruby:

irb(main):001:0> x = nil
nil
irb(main):002:0> s = 's'
"s"

So my suggestion is that the gist of a string should be quoted and the gist of an undefined variable should be undef without quotes.

the word nil or undef for undefined variables

Yes, and the words "nil" and "undef" mean something in those languages, but do not in Raku.

the gist of a string should be quoted

So the thousands of programs out there using say "hello" start outputting 'hello'" instead ofhello`?

the gist of an undefined variable should be undef without quotes

So this today:

> my @roles = Rat.^roles; say @roles
[(Rational[Int,Int]) (Real) (Numeric)]

Would become this:

> my @roles = Rat.^roles; say @roles
[undef undef undef]

(Note: an array is made up of Scalar containers, so it's effectively like each container is a variable, so even if one tried to introduce a discontinuity between type and type in scalar container, we'd still end up with things like this.)

Information on type is lost

Yes, but types in Raku, represented by their type objects, are first class values. That is different from in other languages, but as a concept is used throughout the language. I'm not sure delaying the point at which one needs to learn about that helps.

Not to mention that there already is a way to get an undefined value to turn into an empty string: stringify it. You get a warning along the way too. There's also put, which does .Str instead of .gist.

> my $x; put $x;
Use of uninitialized value $x of type Any in string context.
Methods .^name, .raku, .gist, or .say can be used to stringify it to something meaningful.
  in block <unit> at <unknown file> line 1

This thread mostly consists of suggestions that try to change something to address a particular situation, but fail to consider all of the other contexts in which that change would also have an impact. That isn't how language design works.

This discussion has been done many times in the past 20 years. So in a way, it is way too late to suggest changes like these.

@rjattrill I don't think you appreciate the intent of the .gist method: it is intended to provide a human readable form of an object, to literally "give you a gist" of an object. Without needing to be syntactically correct. If you want syntactic correctness, you need the raku method, which should provide a Str with code that can be EVALled to get the original object back (as far as possible, because e.g. Code objects are very hard in that respect).

I have just been playing with IRB in Ruby and Reply in Perl. Both of those return quoted strings for variables with strings and the word nil or undef for undefined variables:

From this I gather that you'd be most interested in having the behaviour of the REPL change. To that end, I have committed: https://github.com/rakudo/rakudo/commit/285717a0d3

I hope this will stick.

Thank you for reviewing my suggestion @jnthn - especially the example of type information from inspecting Rational.

I like your proposal @lizmat. It puts quotes around strings and removes the parens from Any in REPL if RAKU_REPL_OUTPUT_METHOD=raku.

I'm late to the party, but is there a way to use a user defined function instead.

Lets say I create my own &dumper function, but since it's not a method, i can't set RAKU_REPL_OUTPUT_METHOD=dumper. Using &dumper doesn't work either.

Is there a way to make it work with a function? If not, can the repl-print function be modified to also accept a function name?

The repl-print function then might look something like this

sub repl-print(Mu $value --> Nil) {
    my $method := %*ENV<RAKU_REPL_OUTPUT_METHOD> // "gist";
    with ::{$method} -> &f {
        say f($value);
    }
    else { ... }

Then presuming a MyDumper lib that exports a dumper function, one could invoke

RAKU_REPL_OUTPUT_METHOD='&dumper' raku -M MyDumper

This has the added benefit of not breaking the current - albeit undocumented - behaviour.

Final though... Maybe RAKU_REPL_OUTPUT_FUNCTION might be a better name, or simply RAKU_REPL_OUTPUTTER.

I've noticed that ::{'&MyDumper::dumper'} is not a valid function, but &::('MyDumper::dumper) is.

Maybe I'm missing the finer details and there's a better way to support this, but regardless... there should also be support for providing the FQN of a function if needed.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

AlexDaniel picture AlexDaniel  Â·  12Comments

JJ picture JJ  Â·  12Comments

AlexDaniel picture AlexDaniel  Â·  11Comments

ash picture ash  Â·  13Comments

ToddAndMargo picture ToddAndMargo  Â·  6Comments