Open landyacht opened 6 months ago
WhT would that do for List? Break? I mean (1, 2, 3) as Int
yep, I think that going as
on a thing that can't be typed should break
BTW I like all the options [] as Int
, [] of Int
and []:Int
- if forced I would say that of
is probably the most natural and second @raiph' observation of the fit with the current trait
@librasteve
yep, I think that going
as
on a thing that can't be typed should break
If we're talking about as
(at least as I thought @vrurg had conceived it) then it's the RHS type that would ultimately determine whether the coercion fails, not the LHS. (And any such failure will end compilation, unless the LHS and/or RHS are allowed to be dynamic and someone takes advantage of that and the combination fails to coerce due to a fundamental mismatch of container types, so no coercion is even available, or there's a failure to convert some element.)
In contrast, if we're talking about of
rather than as
then it could indeed break due to the LHS having a non-parameterizable of
type, with List
being a classic case. Fortunately it would again be a compile phase catch (unless dynamism is allowed as @vrurg discussed and I just recapped above, but in that regard I think as
and of
are no different).
What about a variant for as
(or of
) something like:
[1,2,3] as *
where the *
invokes .are
to choose the type rather than coercing to a fixed type?
@vrurg wrote:
Whenever I need to pass in a typed array what I usually do is:
foo( my Int @ = [1,2,3] );
Not working here (tried in a script and in the REPL). But I'm only on Rakudo 2023.05, so is this functionality due to a recent commit? And how to call?
~ % raku
Welcome to Rakudo™ v2023.05.
Implementing the Raku® Programming Language v6.d.
Built on MoarVM version 2023.05.
To exit type 'exit' or '^D'
[0] > foo( my Int @ = [1,2,3] );
===SORRY!=== Error while compiling:
Undeclared routine:
foo used at line 1
[0] >
@alabamenhu
Undeclared routine It seems you haven't declared the foo function
Thanks @FCO !
[1] > sub foo(Int @a) { dd @a }
&foo
[2] > foo( my Int @ = [1,2,3] ){};
Array[Int @ = Array[Int].new(1, 2, 3)
Nil
Using the sub name typify
instead of foo
:
[7] > sub typify(Int @a) { @a }
&typify
[8] > typify my Int @x = [1,2,3];
[1 2 3]
[9] > dd typify my Int @x = [1,2,3];
Array[Int @x = Array[Int].new(1, 2, 3)
Nil
[10] > dd typify my Int @x = [1,2,"three"];
Type check failed for an element of @x; expected Int but got Str ("three")
in block <unit> at <unknown file> line 1
[10] >
Maybe a sub named something like typify
(lowercase) errors on Type-checking violations,
while Typify
(uppercase) coerces?
@jubilatious1 wrote:
Maybe a sub named something like
typify
(lowercase) error on Type-checking, whileTypify
(uppercase) coerces?
While I do like the idea of making any coercion opt in to be as simple as possible, I think we have better mechanisms for differentiating between these forms than capitalization.
Even a whole new keyword would be preferable, IMO. Though naming it successfully would require quite a discussion in itself :)
That said, it really feels like the more natural location for specifying this on the callee -side is as a trait on the parameter.
From the perspective of specifying at the call site, so far the 'as' proposal has my vote.
[1, 2, 3] of Int
makes most sense to me if we want a short version (i.e. one that doesn't explicitly mention Array) as it reas like "this array of ints" and the of trait is also used for specifying the member type and Array.of
does return that member type constraint. It's a quite natural fit.
I'd veto [1, 2, 3] as Int
but can see [1,2, 3] as Array[Int]
working.
Anyway this can easily be prototyped in module space (as has been demonstrated).
What about a variant for
as
(orof
) something like:[1,2,3] as *
where the
*
invokes.are
to choose the type rather than coercing to a fixed type?
That sounds to me like "let's introduce type constraints to the code as an additional safety net and then...let's ignore those type constraints and have the compiler guess what we mean".
let's ignore those type constraints and have the compiler guess what we mean".
Except for one thing: we explicitly ask the compiler to do it for us. I see more significant problem here. While the example above is all transparent due to use of constant values, something like @a as *
might be way more surprising and even WAT-ish in production.
[1, 2, 3] of Int
makes most sense to me if we want a short version (i.e. one that doesn't explicitly mention Array) as it reas like "this array of ints" and the of trait is also used for specifying the member type andArray.of
does return that member type constraint. It's a quite natural fit.I'd veto
[1, 2, 3] as Int
but can see[1,2, 3] as Array[Int]
working.Anyway this can easily be prototyped in module space (as has been demonstrated).
Agree that as Int
looks wrong. While I do like the simplicity and congruity of
, the issue is that it will probably assume Array
, yeah? The calling sub might insist on some other Positional
(not easily indicated with @
sigils at the moment, see https://github.com/rakudo/rakudo/issues/5540 ) and we'd have the same issue. Unless maybe we could somehow figure out [1,2,3] as Array of Ints
but there we might be treading into AppleScript territory lol.
Ultimately, I'm not opposed to of
, I just think its utility would be a bit more limited, relative to as
which could be a general purpose compile-time-able COERCE
mechanism.
I'll sign up to try to work on these in module space and see what we think about them. Will try to get something this weekend maybe.
that it will probably assume Array, yeah?
It could just use the same Positional as passed?
@alabamenhu
While I do like the simplicity and congruity
of
, the issue is that it will probably assumeArray
, yeah?
You meant an issue -- there are multiple issues. ;)
I wasn't thinking it would assume an Array
. I was thinking it would construct a new container that's the same container type as the LHS. So something like:
"{LHS.^name ~~ / .* \[? /}[{RHS.^name}]".EVAL.(LHS)
The calling sub might insist on some other
Positional
To be clear, I was never suggesting of
replaces as
, if that's what you were thinking.
Anyhow, while I get that the of
and is
traits don't currently work on parameters, I failed to figure out what you meant was a problem after looking at the issue you linked.
Ultimately, I'm not opposed to
of
, I just think its utility would be a bit more limited, relative toas
which could be a general purpose compile-time-able COERCE mechanism.
FWIW I thought of of
as also a compile-time-able COERCE mechanism, just one that presumed it should produce the same container as the LHS. I was imagining it was plausible that a large chunk of uses in real world code, perhaps most, would be that case.
But like I said, there are multiple issues, with the most basic being whether having an extra construct is worthwhile. I'm now thinking of
isn't worthwhile.
More generally allowing this for any type, would be slightly more complicated, but still doable.
multi circumfix:<[ ]>(*@a, *%_){
my $type = ::(%_.head.key);
die ('Exception to be done.') if $type.WHAT === Failure && $type.exception ~~ X::NoSuchSymbol;
Array[$type].new: @a
};
dd [1,2,3]:Foo
It sure can! But we may exchange one confusing error with another when this syntax is chained with other operators, because the adverb goes to the wrong operator. Also, there will be a runtime error if the type is not found (and it is kinda slow).
It's getting really weird when we change that with constant
:
constant Int @a = [1,2,3]:Int;
dd @a;
# OUTPUT: Missing initializer on constant declaration
# at /home/dex/projects/raku/tmp/2021-03-08.raku:2898
# ------> constant Int⏏ @a = [1,2,3]:Int;
Looke like another Rakudobug.
The following syntax would allow runtime lookups (and looks neat in my eyes).
my Int @array = Int [1,2,3];
my @list = Int (1,2,3);
my $type = 'Str';
my Str @a = ::($type) <a b c>;
sub foo(Int @a) { }
foo(Int (1,2,3));
Minimal Example
Why This is Surprising
Other behavior in Raku would lead one to believe that type constraints apply not to the container (variable) as a whole, but rather to the element(s) that can be put into our pulled out of the container, the exact meaning of which depends on the type of said container.
Specifically, we do not constrain an array to only hold
Int
s withArray[Int] @a
, rather we doInt @a
. We do not constrain a hash to hold onlyInt
s withHash[Int] %h
, rather we doInt %h
. We do not constrain a callable to return onlyInt
s withCallable[Int] &c
, rather we doInt &c
. All this implies to the newly-learning Raku developer that constraints apply "intelligently" based on container shape, rather than "dumbly" to the container as a whole. In other words, the user doesn't have to worry about typing whole containers as long as the contained elements satisfy the constraint.This is further reinforced by the fact that literals with no explicit type specified can be assigned into type-constrained containers, e.g.
my Int @ints = [1, 2, 3]
. Nowhere does the programmer explicitly say that[1, 2, 3]
isArray[Int]
, yet the language accepts it into anInt
-constrained@
-sigiled container. Why, then, should the Minimal Example fail?Should This Behavior Change?
More experienced developers understand that the underlying difference is that between assignment (
=
) and binding (:=
), and what happens when calling a code block is binding to parameters, not assigning. While that suffices as a technical explanation, it does not feel sufficient as a philosophical justification for the surprising behavior. I (and I believe others too) would like to be able to apply the "intelligent constraint" principle consistently, or at least in the absence of something "unusual" likemy @a is Array[Foo]
.Furthermore, it seems to violate the concept of optional, gradual typing. Going back and adding type constraints to your quickly-prototyped code should not break it if that constraint was always satisfied anyway. It feels wrong that the Minimal Example could be made to work, with equivalent function, by removing the type constraint on
foo
's parameter.Practical Considerations
Making parameter binding behave as naïvely expected is easy enough for cases where the container's values are all known at compile time. However, in the general case, we would likely have to scan through arrays and hashes, and I'm not even sure what sort of black magic would be necessary for callables (perhaps those are an acceptable exception where the user must explicitly call out the return type).