11

I'd like to create some parametrized types for Raku; basically, I'd like to create some different classes whose main difference would be the range of values of one of its attributes; for instance, classes represent types of building, I'd like to have different classes for buildings with 3 or any other number of floors. So this is the best I could think of:

subset Two-Tops of UInt where * <=2;
subset Three-Tops of UInt where * <=3;

role Zipi[ ::Capper ] {
    has Capper $.floor;
}
    
class Capped-at-three does Zipi[Three-Tops] {}


my $capped = Capped-at-three.new( floor => 2 );
say $capped.raku;

This is clearly unpractical as soon as you need to take care of many different numbers of floors (not here in Granada, where they have at most 10, I think, but well... ). The problem here is basically you need to have the information for subsets at compile time, so unless you use macros (still experimental), there's no way you can use any kind of variable. So can you think of a practical way of defining this kind of curried roles for any value of the parameter?

jjmerelo
  • 22,578
  • 8
  • 40
  • 86

4 Answers4

8

Actually, unlike I said in my previous you can use conditions in where clauses without problem, you just need to encase them in braces:

role Zipi[$condition] {
    has $.floor is rw where {$_ ~~ $condition}
    method foo($x) { $!floor = $x }
}

class A does Zipi[2 < * < 5] {
    method bar($x) { $.floor = $x }
}

#my $a = A.new( floor => 10); # error
my $a = A.new( floor => 4); # OK

#$a.foo(10); # error
$a.foo(3); # OK

#$a.bar(0); # error
$a.bar(4); # OK

#$a.floor = 9; # error
$a.floor = 3; # OK

That should cover all of the assignment types

user0721090601
  • 5,276
  • 24
  • 41
  • 1
    This does not require high magic, so sorry, @raiph, I've chosen it. Thanks! – jjmerelo Nov 28 '20 at 08:56
  • .oO ( I'm glad @jj got & accepted this solution and not mine!) What?!? Who said that? I encountered the same issue with `where` clauses when using `*` but found it worked using braces and `~~`. But I didn't realize that that's what you meant by "I tried using anonymous `where` clauses". Maybe you read my answer and saw that option and it inspired the above? In which case my aspirational "step in the right direction" applies, and in turn the sense that it might inspire us all to stay ahead of dreaded lurchers, and know we *are* in this together, and share the desire to spread what we have... ;) – raiph Nov 28 '20 at 11:44
  • @raiph: ps, check out reddit — would love some feedback on my post there re CLDR (def value your input/observations) – user0721090601 Nov 28 '20 at 15:54
  • Heh. I just posted a comment there, then visited here and have just seen your comment for the first time. Maybe it's a zombie power that I read your mind without realizing I'd done so? – raiph Nov 28 '20 at 20:43
  • @raiph I'm not so totally sure about this now. At the end of the day, it needs runtime constants. It's a bit more flexible. So will take a second look at your answer, and maybe switch again. – jjmerelo Nov 29 '20 at 08:05
5

I have very limited MOP chops, and the following seems ugly, but it works, and might be a step in the right direction.

What I've done:

  • Dynamically constructed an array of 10,000 subsets via the MOP.

  • Time shifted their construction to compile time via BEGIN.

  • Used an appropriate element from the array to parameterize the role.

my @max-floors-checkers; 
BEGIN {
  @max-floors-checkers = do for ^10_000 -> \floors {
    Metamodel::SubsetHOW.new_type: 
      refinee => UInt,
      refinement => { $^floors <= floors }
    }
}

role BuildingCategory[ ::MaxFloorsCheck ] { has MaxFloorsCheck $.floors }
    
class Capped-at-three does BuildingCategory[ @max-floors-checkers[3] ] {}

my $capped3 = Capped-at-three.new( floors => 2 );
say $capped3.raku;                                # Capped-at-three.new(floors => 2

my $capped4 = Capped-at-three.new( floors => 4 ); # Type check failed 
raiph
  • 31,607
  • 3
  • 62
  • 111
  • 2
    Although I came up with a simpler way for the particular task at hand.... this is pretty incredible. Never would have thought to create so many subsets like that. – user0721090601 Nov 28 '20 at 08:57
  • 1
    The MOP is one side of [Peter Parker's principle](https://en.wikipedia.org/wiki/With_great_power_comes_great_responsibility). Of course, scary subsets of mass destruction(†) lurched thru my nightmares last night (and will perhaps now spread to those who read the above). But I do not feel pain or fear or **responsibility**, and refuse to stop! .oO ( Dare I hope [a superhero like @user0721090601 will show we can have power *and* beauty?](https://stackoverflow.com/questions/65041788/parametrized-types-in-raku-how-to-use-run-time-values-as-parameters#comment115000750_65047914) ) – raiph Nov 28 '20 at 15:04
  • [(†)](https://en.wikipedia.org/wiki/ZMD:_Zombies_of_Mass_Destruction_\(film\)) – raiph Nov 28 '20 at 15:09
  • As long as we're going MOP, why don't go all the way? Theoretically, we could create a runtime function that creates the Role on the fly. We're going to need it anyway, since the classes will have to be created on the fly either, or we end up with pretty much the same problem. We need to know, during compile time, the number of floors we are going to need. – jjmerelo Nov 29 '20 at 08:11
  • @jjmerelo [I will not stop!](https://www.youtube.com/watch?v=Jd_41tM6H2Y#t=2m28s) :) I'm confused about what you're after for multiple reasons. I'll mention two in this comment. First is XY. Your Q's preamble was grounded in the link to a simple Java example for which there are simple Raku equivalents. But your example is very complex. In my answer above I decided to just directly address your complex example, ignoring the sense of XYness. Second is your comment above. Are you ignoring the `BEGIN`? More importantly, do you mean *compile time* or *compile phase*? *Run time* or *run phase*? – raiph Nov 29 '20 at 14:06
  • @raiph I guess run and compile phase. Main point is, this is good for floors, but just imagine I want to parametrize by something of a size and number I can't pre-create. A real number, for instance. Your example would not work... – jjmerelo Nov 29 '20 at 17:10
  • @jjmerelo There are at least 2 different things going on here. I get that one can't use an array if you need to index by real numbers. But you could just use a hash instead. There must be something else that you're driving at. By definition, using any PL, the run phase happens after the compile phase. So if you are insisting on something being a run phase value then it is guaranteed to be too late for the compile phase. (All you could possibly do is do some *compile time* work during the *run phase*.) This is why I thought you instead meant doing run time MOP work during the compile *phase*. – raiph Nov 29 '20 at 19:36
  • @raiph you can do that if you know the numbers in advance. My point is that you're using the metamodel to create subsets. Why stop at that? You can create also roles and subclasses. And you can do it in the run phase. – jjmerelo Nov 30 '20 at 08:02
  • @raiph "you can do that if you know the numbers in advance." Right, that's what I said: if you are insisting on something being a run phase value then it is guaranteed to be too late for the compile phase. "Why stop at that? You can create also roles and subclasses." I like a minimum of MOPing. "in the run phase." That would eschew compile phase type safety (what most folk mean by "static typing"). A compiler could in principle reject my last line of code *during the compile phase*. And I could switch from a `my` to a `constant` so even *current* Rakudo would give me compile phase type safety. – raiph Nov 30 '20 at 11:59
3

I tried using anonymous where clauses, but similarly to no avail, but I tracked down the issue: the where clause is apparently being ignored by the BUILD method . I'm not sure if it's because it has direct access (via $!floor) which bypasses the where clause, or if something else weird is going on (probably the latter, I general got Nil if I tried to use the paramaterized value in a where clause).

Nonetheless, this should work nicely, including giving a helpful error message:

role Zipi[$condition] {
    has $.floor;

    submethod BUILD(:$floor, |c) {
        die "Invalid floor number."
            unless $floor ~~ $condition;
        $!floor = $floor;
    }
}

You can see how it'd be easy to modify if you can assume floors are always 0 .. x, or x .. y and could provide an even more helpful error message.

user0721090601
  • 5,276
  • 24
  • 41
  • The problem here is that you don't guarantee `$.floor` will continue to meet the constraint in runtime; you would need additional checks. But it's a nice solution, thanks. – jjmerelo Nov 28 '20 at 07:36
1

A nanswer covering the case a reader knows Java but not Raku.

Collection<String> coll = new LinkedList<String>();

parametrized types for Raku

The linked Java example is:

The instantiation of a generic type with actual type arguments is called a parameterized type. Example (of a parameterized type):

Collection<String> coll = new LinkedList<String>();

A reasonable Raku analog is:

my Positional[Str] \coll = Array[Str].new;

The Positional type is a parameterizable role. A role specifies an interface and/or partial implementation of a type. I believe Raku's Positional is sufficiently analogous to Java's Collection that it serves for the purposes of this nanswer.

The Array type is a parameterizable class. It specifies a data structure that adheres to the Positional role. It isn't a linked list but it will suffice for the purposes of this nanswer.

raiph
  • 31,607
  • 3
  • 62
  • 111