Perl6: How to get your own distinct type from Int?

I would like to define two types of data in Perl 6 derived from Int , but at the same time incompatible with Int or with each other.

For instance:

  • Distance obtained from Int with a range from 0 to 32000 and
  • Offset derived from Int with a range of -32000 to 32000

Now I would like the Distance , Offset and Int types to be distinguishable and incompatible with each other by default.

So (pseudo Perl 6):

  my Distance $d = Distance(12); // ok my Offset $o = Offset(-1); // ok my Distance $d2 = $o; // BUMMER! sub myprint(Int $i) { say $i } say $d + $o; // BUMMER! myprint $d; // BUMMER! myprint Int($d); // ok 

and so on! I want the Perl 6 compiler to complain if I try to mix Distance and Offset implicitly.

The books that I have read so far have not given a hint of how to achieve this. To ask Google for several days also not to offer me any answer, is this possible, and if so, how?

I found about subset , but this only limits the type, but does not make it incompatible with the original type. In addition, it does not differ from the original type if its restrictions are satisfied both in the original type and in its subset.

So, I would like to ask here if anyone knows if this is possible in Perl 6? And if so, how do I do this?

+5
source share
2 answers

Well, if you really want them to be distinguishable and incompatible by default, just make them completely separate classes. You can determine which ones you want. If you use the "has" relationship with an integer (instead of the "is" relationship), it is very easy to delegate functionality to this value (in this example, I delegate .Int so that your example works):

 class Distance { has Int $!value handles<Int>; method new($value where 0 <= * <= 32000) { self.bless(:$value) } submethod BUILD(:$!value) {} } class Offset { has Int $!value handles<Int>; method new($value where -32000 <= * <= 32000) { self.bless(:$value) } submethod BUILD(:$!value) {} } my Distance $d = Distance.new(12); # ok my Offset $o = Offset.new(-1); # ok my Distance $d2 = $o; # Bummer! Type check fail sub myprint(Int $i) { say $i } say $d + $o; # Bummer!, can't add those objects myprint $d; # Bummer!, $d isn't an Int, can't print myprint Int($d); # ok, prints 12, converting with Int 

Regardless of the functionality that you want Distance and Offset , you will need to embed in these classes, possibly delegating $!value , to simplify it.

EDIT: if you really want your desired syntax my Distance $d = Distance(12); , you could add a method to the Int class to call your constructor:

 Int.^add_method('Distance', method () { Distance.new(self) }); Int.^compose; 

I would not really advise this - perhaps more confusing than useful. Better encourage the use of a standard constructor. @raiph also points to idiomatic Perl:

 my Distance $d .= new(12); 
+3
source

Kurt's answer catches the bugs that they wanted to receive. This additional answer is my initial research on Chi, which follows on :

Why does Curt Rakudo wait for runtime when compiling / running code to report two out of three errors?

At the end of this initial research, I came to the end of Kurt's code in the BEGIN block. This code reports all errors during compilation (one after the other, of course, after commenting on each previous error). ( Click to start the fragment .)

This is a straw response configured for 6-core Perl developers to shoot down.


Initial start of the Curt code results in:

=== SORRY! === ... Calling myprint (Distance) will never work ...

The host ===SORRY!=== means the error "compile-time" 1 .

But if we comment on the failed line and try again, we get:

Type check error when assigning $ d2 ...

This error message is a "run-time" 1 message - it does not start with ===SORRY!=== .

Why did the compiler wait for a "run time" to complain?

The answer seems to be a combination of Perl 6, mostly dynamic by default, and code that doesn't execute:

 my Distance $d2 = $o; # Bummer! Type check fail 

Part of this part of my Distance $d2 fully processed (introducing the new lexically restricted character $d2 ) when the compiler first encounters this declaration at compile time. But the = part of this line is a "run-time" operator; the initialization assignment and the corresponding type check occur at runtime.

But the developer may want to force type checking, and therefore a type checking error, to happen at compile time. Now what?

BEGIN time

Perl 6 supports space / runtime of the program through phasers . The first phaser is the BEGIN phaser, which "starts at compile time as soon as possible" and can be used as follows:

 BEGIN my Distance $d2 = $o; 

If you recompile with this change, an error now appears during compilation 1 :

 ===SORRY!=== Error while compiling... An exception occurred while evaluating a BEGIN... Type check failed in assignment to $d2... 

If we now comment on the last unsuccessful line and try again, we get:

Cannot Allow Caller Numeric (Distance:) ...

No lead ===SORRY!=== , so again this is a run-time error.

Reusing code with a modified line:

 BEGIN say $d + $o; 

gives:

 0 12 

on stdout, and on stderr we get:

 Use of uninitialized value of type Distance in numeric context... Use of uninitialized value of type Offset in numeric context... 

Uhh. There is not only a compile-time error, there is no runtime error! (Runtime warnings can give the game about 0 Since the lines my... declaring $d and $o were not prefixed with BEGIN , these characters have not yet been initialized at compile time, which is the time that BEGIN say $d + $o; line being launched But all this is debatable .. we are clearly a step back)

What happens if we pack all the Curt code in one BEGIN block?

BEGIN { ... Curt code goes here ... }

 BEGIN { class Distance... class Offset... my Distance $d = Distance.new(12)... sub myprint(Int $i) { say $i } say $d + $o;... } 

Bingo! Now all errors are detected because they were with the source code of Curt, but reporting seems to happen at compile time (one after the other, after commenting on each previous error).


1 I scare the citation of many references to “compile time” and “run time” because they are ambiguous in Perl 6. Perl 6 allows the user code to do anything, including starting the compiler at runtime, and allows the user code to do at compile time anything, including at runtime. Therefore, on the one hand, there can be one or more run-time phases during the compilation phase and vice versa. But from the second point of view, there is a compilation time phase, i.e. When you sit there during a development session, and you just started the compiler. There is also a run-time phase, that is, when your code, for example, works "in production". Where I do not frighten runtime / compilation quotes, I mean a link to this second perspective.

+2
source

Source: https://habr.com/ru/post/1268434/


All Articles