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

MAIN: Str arguments do not accept True or False #2794

Open
treyharris opened this issue Mar 24, 2019 · 20 comments
Open

MAIN: Str arguments do not accept True or False #2794

treyharris opened this issue Mar 24, 2019 · 20 comments
Labels
CLI perl6 executable and user programs command-line interface

Comments

@treyharris
Copy link
Collaborator

treyharris commented Mar 24, 2019

The Problem

MAIN: Str arguments do not accept True or False

Expected Behavior

% for arg in hi 42 3.5 Nil True False; do
perl6 -e 'sub MAIN(Str :$flag) { say $flag.perl }; BEGIN @*ARGS="--flag='$arg'"'
done
"hi"
IntStr.new(42, "42")
RatStr.new(3.5, "3.5")
"Nil"
"True"
"False"

(Or, possibly the last two should be a currently-missing BoolStr allomorph or a compound type of some sort.)

Actual Behavior

% for arg in hi 42 3.5 Nil True False; do
perl6 -e 'sub MAIN(Str :$flag) { say $flag.perl }; BEGIN @*ARGS="--flag='$arg'"'
done
"hi"
IntStr.new(42, "42")
RatStr.new(3.5, "3.5")
"Nil"
Usage:
  -e '...' [--flag=<Str>]
Usage:
  -e '...' [--flag=<Str>]

If Str is replaced with Str(), this works much as would have been expected:

% for arg in hi 42 3.5 Nil True False; do
perl6 -e 'sub MAIN(Str() :$flag) { say $flag.perl }; BEGIN @*ARGS="--flag='$arg'"'
done
"hi"
IntStr.new(42, "42")
RatStr.new(3.5, "3.5")
"Nil"
"True"
"False"

I think that, in the context of MAIN, a Str argument should always accept anything from a non-validating CLI like a Unix or Windows shell, so the Str() should, perhaps, be implicit.

Steps to Reproduce

See above for examples that can be directly entered into the shell.

Environment

  • Operating system: Ubuntu 18.04.2 LTS
  • Compiler version (perl6 -v): This is Rakudo Star version 2018.10 built on MoarVM version 2018.10
    implementing Perl 6.c.
@lizmat
Copy link
Contributor

lizmat commented Mar 25, 2019

I assume the order of the arguments was intended to be: % for arg in hi 42 3.5 Nil True False; do ?

So that Nil => "Nil", True -> "True", etc...

@lizmat
Copy link
Contributor

lizmat commented Mar 25, 2019

Looks like the most obvious solution would be to introduce a EnumStr allomorph. This would not only handle True and False, but any other enum such as Planned, Kept, Broken, Less, More, Same, and any locally defined enums.

@lizmat
Copy link
Contributor

lizmat commented Mar 25, 2019

Having an EnumStr would also allow specification of candidates using "True" and "False", which some might consider a feature.

@treyharris
Copy link
Collaborator Author

I assume the order of the arguments was intended to be: % for arg in hi 42 3.5 Nil True False; do ?

So that Nil => "Nil", True -> "True", etc...

Indeed; that's what I get for noticing the prose would work better if I'd thought to put Nil in the middle before True and False and hand-munging the console output rather than re-running it!

I've edited to fix. Apologies for the confusion.

@treyharris
Copy link
Collaborator Author

Having an EnumStr would also allow specification of candidates using "True" and "False", which some might consider a feature.

There's one more thorn in the side which EnumStr needs to fix:

perl6 -e 'sub MAIN(Str() :$flag) { say "{$flag.perl} is ", $flag.WHAT.perl; }; BEGIN @*ARGS="--flag='$arg'"'
done
RatStr.new(42.0, "42.00") is RatStr
RatStr.new(3.5, "3.50") is RatStr
"True" is Str

From the results shown in the RatStr examples there, I assume it will fall out naturally from the creation of EnumStr, but it should be kept in mind that "Bool::True" is a valid string, too, and is ≠ to "True".

@treyharris
Copy link
Collaborator Author

I think an 'EnumStr' is a requirement, actually, after considering this example:

% ./cat-p6 *
@*ARGS is ["cat-p6"]
=== cat-p6 ===
#!/usr/bin/env perl6

unit sub MAIN(*@files);

say '@*ARGS is ', @*ARGS.perl;

for @files -> $f {
  say "=== $f ===";
  put $f.IO.lines.join("\n");
}
% for file in 42 hi True Bool::True; do
for> echo "This is $file" > $file
for> done
% ls
42  Bool::True  True  cat-p6  hi
% ./cat-p6 *
@*ARGS is ["42", "Bool::True", "True", "cat-p6", "hi"]
=== 42 ===
This is 42
=== True ===
This is True
=== True ===
This is True
=== cat-p6 ===
#!/usr/bin/env perl6

unit sub MAIN(*@files);

say '@*ARGS is ', @*ARGS.perl;

for @files -> $f {
  say "=== $f ===";
  put $f.IO.lines.join("\n");
}
=== hi ===
This is hi

Without taking over @*ARG handling, it's impossible to access the file named "Bool::True". That can't be right.

@ugexe
Copy link
Member

ugexe commented Mar 25, 2019

I think EnumStr would end up in confusion. Consider:

sub MAIN($s) {
    if $s {
        say 42
    }
}

What should this do for Planned and Same? Is it the same behavior that would be used for False? Must developers be aware of all of these details, and that they should explicitly coerce everything in their signatures when possible? I'm not sure a consistent behavior can be achieved.

@lucasbuchala lucasbuchala added the CLI perl6 executable and user programs command-line interface label Mar 25, 2019
@lizmat
Copy link
Contributor

lizmat commented Mar 25, 2019 via email

@ugexe
Copy link
Member

ugexe commented Mar 25, 2019

IntStr does fall victim to the same issue. But I'm mostly (not 100%) ok with this since its only for a single value and there is no other way to pass in an Int (something I actually want to pass in on the command line). EnumStr on the other hand affects multiple values, and anytime a new Enum is added that has a Falsey item it potentially introduces a breaking change to any existing code that accepts strings.

I can't think of a use case for passing non-string Planned, Kept, Broken, Less, More, or Same. I can almost buy False (but don't).

Ideally MAIN should coerce to the declared type, or that to get Int the developer should explicitly coerce via Int(). But this cannot be done for a slurpy *@_ and so is still flawed.

# MAIN(IntStr.new(0,"0")) should say Int
sub MAIN(Int $a) { say $a.WHAT }
# Explicitly coerce to Int
sub MAIN(Int() $a) { say $a.WHAT }

@Leont
Copy link
Contributor

Leont commented Feb 23, 2020

I can't think of a use case for passing non-string Planned, Kept, Broken, Less, More, or Same. I can almost buy False (but don't).

I concur. This abstraction is more than leaky.

Ideally MAIN should coerce to the declared type, or that to get Int the developer should explicitly coerce via Int(). But this cannot be done for a slurpy *@_ and so is still flawed.

That is generally a decent idea, but fails badly when passing an argument multiple times:

$ raku -e 'sub MAIN(Int() :$a) { say $a }' -a=42 -a=24
2

In other circumstances that can be worked around with sub MAIN(Int(Real) :$a) { say $a }, but I'm not sure we could do such a thing here.

@Leont
Copy link
Contributor

Leont commented Mar 2, 2020

This issue affacts all these values

  • True
  • False
  • Less
  • Same
  • More
  • Planned
  • Kept
  • Broken
  • Bytes
  • Chars
  • NativeEndian
  • LittleEndian
  • FileChanged
  • FileRenamed
  • BigEndian
  • SeekFromBeginning
  • SeekFromCurrent
  • SeekFromEnd
  • PF_UNSPEC
  • PF_INET
  • PF_INET6
  • PF_LOCAL
  • PF_UNIX
  • PF_MAX
  • SOCK_PACKET
  • SOCK_STREAM
  • SOCK_DGRAM
  • SOCK_RAW
  • SOCK_RDM
  • SOCK_SEQPACKET
  • SOCK_MAX
  • PROTO_TCP
  • PROTO_UDP
  • SIGXFSZ
  • SIGCHLD
  • SIGIO
  • SIGALRM
  • SIGXCPU
  • SIGFPE
  • SIGSYS
  • SIGILL
  • SIGVTALRM
  • SIGINFO
  • SIGUSR2
  • SIGTHR
  • SIGABRT
  • SIGPWR
  • SIGINT
  • SIGCONT
  • SIGTSTP
  • SIGSTKFLT
  • SIGPROF
  • SIGSEGV
  • SIGTTOU
  • SIGQUIT
  • SIGURG
  • SIGTRAP
  • SIGBUS
  • SIGKILL
  • SIGPIPE
  • SIGBREAK
  • SIGEMT
  • SIGUSR1
  • SIGSTOP
  • SIGTERM
  • SIGTTIN
  • SIGWINCH
  • SIGHUP

This also means that, for example, one currently can't install a module with any such name via zef

@2colours
Copy link
Contributor

The problem is that even if you specify

# Explicitly coerce to Int
sub MAIN(Int() $a) { say $a.WHAT }

It will happily consume a string and keep it as IntStr, so actually this won't even help. I suppose this is because the Int() constraint is a no-operation when you match the target type, and IntStr is Int.

Weirdly enough, though, the same reasoning doesn't apply (and possibly never did) for coercion methods themselves, therefore $a.Int.WHAT will actually be Int and not IntStr.

I don't know if there is an issue for this or not but this also seems much rather a design topic than a "simple matter of programming".

@Leont
Copy link
Contributor

Leont commented Aug 25, 2023

It will happily consume a string and keep it as IntStr, so actually this won't even help. I suppose this is because the Int() constraint is a no-operation when you match the target type, and IntStr is Int.

Weirdly enough, though, the same reasoning doesn't apply (and possibly never did) for coercion methods themselves, therefore $a.Int.WHAT will actually be Int and not IntStr.

That makes sense, for IntStr the conversion won't be called at all. But it is inconvenient indeed.

@2colours
Copy link
Contributor

This is kind of a different topic but it is kind of unexpected that for something that is an Int (and is hard to even tell apart from Int, basically you would need to investigate the exact type object), it is possible that "coercing to Int" yields a different value from the original one. But then it is also weird for something to be Int, be == to 5 but not be === to 5 and therefore not matching in a Map or Set... In which case it's probably better that .Int changes the concrete type.

The part that does belong here is that allomorphs have multiple ways to bite you: not only because an infinite amount of inputs can evaluate to boolean False (different representations of zero) but also because the numbers won't really be numbers, they won't match the real integer in a Set.

@Leont
Copy link
Contributor

Leont commented Aug 26, 2023

This is kind of a different topic but it is kind of unexpected that for something that is an Int (and is hard to even tell apart from Int, basically you would need to investigate the exact type object), it is possible that "coercing to Int" yields a different value from the original one. But then it is also weird for something to be Int, be == to 5 but not be === to 5 and therefore not matching in a Map or Set... In which case it's probably better that .Int changes the concrete type.

The part that does belong here is that allomorphs have multiple ways to bite you: not only because an infinite amount of inputs can evaluate to boolean False (different representations of zero) but also because the numbers won't really be numbers, they won't match the real integer in a Set.

I think that a no allomorphs pragma would be most welcome. Hell I'd even support that becoming the default in a future version of Raku but that may require a rewrite of our argument logic.

@lizmat
Copy link
Contributor

lizmat commented Aug 26, 2023

@Leont
Copy link
Contributor

Leont commented Aug 26, 2023

https://docs.raku.org/language/create-cli#coerce-allomorphs-to ??

It's not well documented, but setting that to Str and then using conversion types will give the expected result (via a hilarious route of Str → IntStr → Str → Int)

@2colours
Copy link
Contributor

2colours commented Aug 26, 2023

Honestly, in a language like Raku which is perfectly willing to coerce types for operators, and has a very loose sense of what Str value counts as a valid number, basically doing a parsing for coercion - what use do Allomorphs have, besides problematic diamond inheritance and lack of identity with the concrete constants you might define in your code, thereby introducing a new level of complexity?

I have been thinking about this a lot but the only thing that has ever appeared to me was that this allows "weak typing" for method calls as well, just like for Cool. Is there something else that I miss? It sounds, I would say downright disheartening, that all this complexity with additional problems serve a single purpose: to save someone from having to write $argument.Int.method-call instead of $argument.method-call.

@Leont
Copy link
Contributor

Leont commented Aug 26, 2023

Honestly, in a language like Raku which is perfectly willing to coerce types for operators, and has a very loose sense of what Str value counts as a valid number, basically doing a parsing for coercion - what use do Allomorphs have, besides problematic diamond inheritance and lack of identity with the concrete constants you might define in your code, thereby introducing a new level of complexity?

I have been thinking about this a lot but the only thing that has ever appeared to me was that this allows "weak typing" for method calls as well, just like for Cool. Is there something else that I miss? It sounds, I would say downright disheartening, that all this complexity with additional problems serve a single purpose: to save someone from having to write $argument.Int.method-call instead of $argument.method-call.

I can imagine the use-case for dualvars such as $*USER (though I find even that a bit dubious), but it has never made sense to me for either command line argument handing and for <>, it has only ever gotten in my way. In the rare case they're useful I would want to be really explicit about creating them.

It's a case of "this makes easy things easier but hard things harder" IME.

@lizmat
Copy link
Contributor

lizmat commented Aug 26, 2023

The allomorphs are more than just for MAIN: they're really produced by val

$ raku -e 'dd val("42")'
IntStr.new(42, "42")

And val happens to be the default processor for quoted words:

$ raku -e 'dd <a 42 b>'
("a", IntStr.new(42, "42"), "b")

Now, I can totally see Allomorphs becoming a module at some point in the future, though. Allomorphs as a core feature feel a little bit too much like Perl, I agree :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLI perl6 executable and user programs command-line interface
Projects
None yet
Development

No branches or pull requests

6 participants