HaloO, Autrijus Tang wrote:
So I'm starting to write the inferencer. Immediately I encounter the problem that every type can potentially contain "undef":
This is the reason why I've bounded Undef below Item disjoint with Value in my type lattice. Of course all types are applicable as type parameters. How implicit that is or if an explicit class IO does Undef[IO] {...} is needed, I can't decide. And I just realised today that decisions are made in places and at times where I am not. And they are not announced here either. But that belongs to the cabal thread. Sorry that I mention it here.
my IO $x = open('/etc/passwd'); $x = undef;
Hmm, that should dispatch to &infix{'='}:( IO, Undef of IO ) which is a type error if Undef of IO doesn't exist or pragmas or some such prevent its auto-generation. A nice implementor of IO would be so kind to close the file immediately in this method.
$x.close; This raises the runtime error: *** Can't call method "close" on an undefined value.
The dispatch should go to &close:( Undef of IO --> Void ) and again the nice implementor of IO.does(Undef of IO) would provide a noop implementation. A careless implementor however might leave his users with the above error ;)
The "undef" literal can defeat the type checker freely: my Int @x := undef; say @x.end; # boom, undefined list generator, per S04 I understand $Larry had rejected RFC192 "Undef Values ne Value", on the ground that "undef" can now carry more interesting information.
Yes, but I think this should now be read as !Value.does(Undef) and Item.does(Undef).
However, sometime it is better if we can have a clear boundary so the dreaded NullPointerException can be raised as early as possible.
I fully agree. Especially when it hits innocent users.
This is certainly too static for Perl6. However, it would be nice to be at least able to declare a variable so that all undef reaching it will instantly explode. So I propose the "is defined" trait:
I would leave that up to the type information stored at ::IO. The user has to know what it does anyhow. And if it is a Value subtype it'll instantly explode even on my IO $x; # boom unless IO provides default value, but how? If the inclined user intends to handle Undef even when the implementor of IO didn't this is how: my IO|Undef[IO] $x; # proper supertype my IO^Undef[IO] $y; # decisive, this is easier for the type checker
# must be assigned before use # if open() returns undef, die right there.
Even worse or better depending on your perspective or stricture in effect. If IO.does(Value) and &open:( Str --> IO^Undef ) then
my IO $x is defined = open('/etc/passwd');
Doesn't even compile. Not so if you take the Widera approach where the intersection of the return type of &open and the type constraint of $x is non-empty because it contains IO. But if your approach it the other way round, the potential error set isn't empty either. This is what I got from the (Book of Widera).does(Paper) so far. Well, and the fact that he calls things Frame that are elsewhere known as Record or Dictionary. OTOH, the implementor of &open could choose to guarantee a return type of IO given an arbitrary Str. In the event that there is no file of that name the returned object is a proper IO but e.g. immediately returns EOF when read or responds with false to .defined calls. But this is not a problem of the type system but of interface design. The type system is there to enforce the designer's decisions or at least point out mismatches to actual use.
$x = undef; # this fails at compile time For the compiler, $x is compiled to the real "IO" type, while the ordinary "my IO $x" is compiled to "Either Error IO" type. Furthermore, it would be nice to tell the inferencer to infer the type of a certain variable, instead of having to either write them out by hand, or accepting their fate as Any. I'd like to see "is typed":
I don't understand the purpose of that other than explicitly switching the inferencer on a per variable basis. Should that mean that a variable takes on a constraint as specific as the inferencer can statically determine? I don't see the point of it, sorry.
{ my $x is typed; # Infer $x's type (Str) for me $x = "Hello"; print $x; }
The optimizer might here just end up with { print "Hello"; } and drop $x completely. Or is that anti-social to some class sitting behind $-sigiled variables?
Finally, it would get tedious to write them out by hand. So a lexical "traits" pragma may help: { # Entering the realm of referential transparency... use traits < defined typed constant >; my $x; # automagically receives the three traits { # Falls back to the dynamic world... no traits < typed constant >; my $y; } } Does this sound sane?
To me it sounds more superfluous. What distinguishes 'referential transparency' from 'the dynamic world'? Do you mean pre-calculated dispatch targets? -- $TSa.greeting := "HaloO"; # mind the echo!