
Perl 6's given: switch on steroids - lizmat
https://tenesianu.blogspot.com/2019/05/perl-6s-given-switch-on-steroids.html
======
rwmj
They've reinvented OCaml / SML pattern matching. I wonder if they also compile
it to efficient machine code? Xavier Leroy's famous ZINC paper goes into how
you do that, and it's pretty complicated (page 64 of
[http://caml.inria.fr/pub/papers/xleroy-
zinc.pdf](http://caml.inria.fr/pub/papers/xleroy-zinc.pdf))

~~~
dan-robertson
Not really.

Let’s first consider how this code would be translated to ocaml. One would
expect to use variants instead of strings, so e.g. gender should be:

    
    
      type t = | Masculine | Feminine | Mixed
    

And then one can match on them and match on tuples and so on. But what about
countries? In this case you could write out all the cases separately
(difficult to maintain), or you could use polymorphic variants to achieve the
@foo.any behaviour:

    
    
      type vosotros = [ `Spain | ... ]
      type dialect = [ vosotros | ... ]
    

And then do e.g.

    
    
      match dialect with
      | #vosotros -> ...
    

But this has the disadvantages that polymorphic variants usually bring
(difficulties with type inference/exhaustiveness checks). So instead one would
probably have some function to decide if a dialect Is vosotros, and then match
on the result of that, a slightly annoying to read boolean.

And one would also need to match on the (perhaps) nonsense case of singular
mixed-gender. One can get around this with types in one of two annoying ways.
Either combine gender and number:

    
    
      type gender = Masculine | Feminine
      type genders = Masculine | Mixed | Feminine
      type number = | Singular of gender | Plural of genders
    

Which is not great because it is now hard to extract the gender if that is all
one wants (but it is perhaps not grammatically useful); or one can use GADTs:

    
    
      type singular = [ `singular ]
      type plural = [ `plural ]
      type 'number gender =
        | Masculine : 'a gender
        | Feminine : 'a gender
        | Mixed : plural gender
      type 'number number =
        | Singular : singular number
        | Plural : plural number
      type t =
        T : { number : 'num number; gender : 'num gender } -> t
    

But this is also unpleasant.

And how does one do other smart-match predicates? For example * > 0\. This
could either be:

    
    
      match number, ... with
      | num, ... when num > 1 -> ...
    

Which separates the important part (num > 1) from the unimportant part (giving
it the name when you don’t care), and you still need a catch all because of
the exhaustiveness checks. So maybe instead you would have a helper function
to change the number into a variant like One | Zero | Many.

This also ignores the case when one might want to write some more traditional
style Perl and put regular expressions as the predicates of those when blocks.
This isn’t really an ocaml/SML thing to do but that doesn’t mean it should be
disregarded as something bad/useless.

~~~
oisdk
Other than the ability to put a predicate in the pattern position (which you
can do in several languages, including Haskell with ViewPatterns), I still
don't see what Perl 6 adds here? Of course it's more difficult to model the
problem using sum types, but there's an asymmetry here: whereas given _can 't_
do powerful pattern-matching features like exhaustiveness checking, GADTs,
auto case-split in an IDE, efficient case trees, etc., the opposite isn't
true. Haskell _can_ do everything Perl does here, just by using strings! The
other features (multiple variables to split on) are pretty standard these
days.

    
    
        vosotros = ["Spain", "EquitorialGuinea", "WesternSahara"]
        
        message = case (number, gender, formality, country) of
          (1         , "masculine"                        , "informal", _                     ) -> "¿Cómo estás mi amigo?"    
          (1         , "masculine"                        , "formal"  , _                     ) -> "¿Cómo está el señor?"     
          (1         , "feminine"                         , "informal", _                     ) -> "¿Cómo estás mi amiga?"   
          (1         , "feminine"                         , "formal"  , _                     ) -> "¿Cómo está la señora?"    
          ((> 1) -> T, (`elem` ["mixed","masculine"]) -> T, "informal", (`elem` vosotros) -> T) -> "¿Cómo estáis mis amigos?" 
          ((> 1) -> T, "feminine"                         , "informal", (`elem` vosotros) -> T) -> "¿Cómo estáis mis amigas?" 
          ((> 1) -> T,  _                                 , _         , _                     ) -> "¿Cómo están ustedes?"

------
combatentropy
This is nice.

In other languages, I would settle for a switch-statement like Go's, which by
default breaks instead of falls through. The common switch-statement seems
like a prank: If you omit the break, the compiler throws no error, like when
you forget a closing brace. Instead the program silently does the opposite of
what you meant.

I am less afraid of forgetting one, more of one day accidentally deleting one.
Every once in a while in Vim I order my keystrokes wrong and a random line
disappears. Usually it catches my eye, but it is unnerving. The risk is
eternal.

You would want to leave the switch-statement as is, for backward
compatibility, and come up with a new key word to set off the improved
version, like Perl did with "given."

With all of the ongoing improvements to JavaScript, I'm surprised this has not
been addressed. And in general with most languages I am surprised that their
flow control is so stunted.

~~~
pantalaimon
Nowadays GCC warns about implicit fall-through.

If you want to have a fall-through, you have to annotate it with, e.g /* fall-
through */

This is of course not ideal, suddenly comments have to be considered by the
compiler. But I guess it's the only way to fix this in a backwards compatible
way.

~~~
melling
Shouldn’t we optimize for the common case?

The language should do want we want most of the time.

~~~
toast0
We should not optimize for the common case at the expense of making the
uncommon case impossible.

With C style switch, you can fall through or break. In languages that don't
provide fall through (say Erlang's case expression), if you have something
that fits fall through, you need to either duplicate the inner code, or have a
more verbose construction with multiple phases of matching.

~~~
melling
So, 99% of the time to need to be more verbose to handle the 1% case? I’d
rather wrap the duplicate code in a function and call it in each instance.

------
cracauer
Of course if you'd use Lisp in the first place you could make a new syntax to
input those things in the visually safest manner without waiting for a
language revision.

Compile-time computing is precisely about this, e.g.
[https://medium.com/@MartinCracauer/a-gentle-introduction-
to-...](https://medium.com/@MartinCracauer/a-gentle-introduction-to-compile-
time-computing-part-3-scientific-units-8e41d8a727ca)

~~~
sametmax
And of course, your code base will end up riddled with those untested,
undocumented customizations. People getting into it will hate you for it, and
you will look at them with a smug face, stating how they don't get the power
of a true language.

There is a reason Lisp based languages are not more popular: most people don't
want to learn a new language every time they work on a new project.

~~~
cracauer
There is no specific difference between compile-time and run-time (regular)
computing when it comes to testing and documenting it.

As for learning something new, perl6 is a new language, this construct isn't
in perl5, so they are in for a learning. In Lisp no language modification was
required to enable this functionality in the first place, and so you do not
have to learn any new language or language construct.

Yes, you have to learn the library that you or somebody else writes to enable
this "enter the data in the safest way possible". But that is learning a
library, not a new language, or language evolution.

It goes back to object-oriented programming. How badly did they hack up C
compilers to implement either C++ or Objective-C? In Lisp the OO system used
to this day was implemented as a compile-time library. A library still used
today, too.

You don't have to wait for anybody to hack up the compiler, and test it, and
document it, when compile-time computing is part of the language in the first
place.

ETA: Another thing you can do with compile-time computing, without waiting for
somebody to hack up the compiler: [https://medium.com/@MartinCracauer/static-
type-checking-in-t...](https://medium.com/@MartinCracauer/static-type-
checking-in-the-programmable-programming-language-lisp-79bb79eb068a)

~~~
b2gills
Actually this feature is in Perl5, you just have to ask the compiler for a
recent enough version.

Though it has been marked as experimental because the Perl6 design for this
didn't fit into Perl5 as well as was hoped for. That is it had edge-cases that
need to be worked through before it is recommended for general use.

    
    
        use v5.10;
        no warnings qw(experimental);
    
        given (1) {
          when (1) { say 'one' } # only prints this line
          when (1) { say 1 }
        }

------
xisukar
The `when` block seems to exhibit a different behavior when used as a block vs
as a statement modifier. The latter lets things falls through until it matches
the most general case. For instance, given

    
    
        class Audience {
            has $.number;
            has $.gender;
            has $.formality;
            has $.country;
        }
        
        my @vosotros = <Spain EquitorialGuinea WesternSahara>;
        
        my $audience = Audience.new:
         :number(100), :gender('feminine'),
         :formality('informal'), :country('Spain')
        ;
    

Contrast:

    
    
        my $message = do given (.number, .gender, .formality, .country given $audience) {
          when     1, 'masculine', 'informal', *              { '¿Cómo estás mi amigo?'    }
          when     1, 'masculine',   'formal', *              { '¿Cómo está el señor?'     }
          when     1, 'feminine',  'informal', *              { '¿Cómo estás mi amiga?'    }
          when     1, 'feminine',    'formal', *              { '¿Cómo está la señora?'    }
          when * > 1, 'feminine',  'informal', @vosotros.any  { '¿Cómo estáis mis amigas?' }
          when * > 1,  *,          'informal', @vosotros.any  { '¿Cómo estáis mis amigos?' }
          when * > 1,  *,                  * , *              { '¿Cómo están ustedes?'     }
        }
    
        say $message; #=> «¿Cómo estáis mis amigas?␤»
    

with this:

    
    
        my $message = do given (.number, .gender, .formality, .country given $audience) {
          '¿Cómo estás mi amigo?'     when     1, 'masculine', 'informal', *;
          '¿Cómo está el señor?'      when     1, 'masculine',   'formal', *;
          '¿Cómo estás mi amiga?'     when     1, 'feminine',  'informal', *;
          '¿Cómo está la señora?'     when     1, 'feminine',    'formal', *;
          '¿Cómo estáis mis amigas?'  when * > 1, 'feminine',  'informal', @vosotros.any;
          '¿Cómo estáis mis amigos?'  when * > 1,  *,          'informal', @vosotros.any;
          '¿Cómo están ustedes?'      when * > 1,  *,                  * , *;
        }
        
        say $message; #=> «¿Cómo están ustedes?␤»

~~~
raiph
Yes, that's deliberate.

If you use an ordinary `when` (with its statement(s) on the right) then P6
assumes it should succeed with the value of the last statement of the matched
`when`. To succeed means to leave the block and return that last value if the
block's value is being kept (eg via a `do`). If you wish instead to proceed to
the next statement rather than succeed, you must write `proceed`.

Conversely, if you use a modifier `when` (with its statement(s) on the left)
then P6 assumes it should proceed as explained above after matching. If you
wish instead to succeed as explained above you must write `succeed` (with a
value unless you wish to succeed with a value of `Nil`).

This is great bit of language design work. Thank you Larry.

------
harryf
Every time I see something about Perl 6 I think this is awesome. Shame that a
younger generation of programmers will never use it, because it’s not the new
cool.

If they renamed Perl 6 into something cool like “Liquid” or some think you
find in the kitchen, like Chilli. Then hide away it’s origins a bit, they’d
have a language kids would be raving about.

I know that’s not the point but I can’t think of any programmer I know today
that doesn’t talk about Perl with a certain contempt. “It’s Perrrrl” eyes up

~~~
Sharlin
There's a concept in programming language design called "weirdness budget".
There's only a finite amount of idiosyncratic syntax and semantics your
language is allowed to have before it starts to turn off potential adapters
because it looks too unfamiliar, too unintuitive, or too opinionated compared
to what they already know.

Perl has never cared what the mainstream programmer community thinks. It is
willfully and deliberately weird; when it adopts or reinvents concepts from
other languages it does it in a way that seems purposively quirky; it invents
bespoke vocabulary for things that already have well-established names. The
Perl community appears cultish, lacking a certain self-awareness and
cognizance of the programming world at large. It's like an alternate timeline
where everything seems eerily _off_ in some way.

Perl blew its weirdness budget a long time ago and never cared. And given its
well-deserved reputation as a write-only language, it's no wonder at all that
people don't take it entirely seriously.

~~~
ryl00
I don't think this is correct. If you come from Unix programming, perl should
not at all be unfamiliar. It was an intentional amalgamation of shell, sed,
awk, etc. And there was a time when that was the "mainstream" programmer
community.

~~~
Sharlin
I think the key word is "was". Yes, in the beginning it was a pretty
straightforward amalgamation of a bunch of standard Unix tools. (Although it
should be noted that those were even then in a separate category from
languages meant for application programming!) But that's where the Perl
timeline started to diverge from the mainstream, in which production-ready
programming languages have mostly grown to agree on basic concepts and
terminology, and to emphasize design principles like clarity over cleverness
and readability over writability.

------
CDSlice
This looks a lot like Rust's match blocks (which were inspired by pattern
matching in ML languages). It's nice to see pattern matching constructs come
to more languages.

~~~
lizmat
But, this has really not a lot to do with pattern matching. Please see my
comment at
[https://www.reddit.com/r/perl6/comments/bvhzwh/perl_6s_given...](https://www.reddit.com/r/perl6/comments/bvhzwh/perl_6s_given_switch_on_steroids_matéu/eppm0oz/)

~~~
Sharlin
Surely it has everything to do with pattern matching? A C-style switch
statement is an extremely primitive version of pattern matching, and in the
article the flattened equivalent of the nested switch structure is just tuple
destructuring and matching, just like in Rust I could write

    
    
      match (foo, bar, baz) {
        (1, 2, 3) => something(),
        (3, 2, 1) => somethingelse()
      }

------
makecheck
I find it odd that no language seems to have a syntax short-cut (like
operators) for something as common as switches; the closest thing might be a
_shell script_!

This is a very common thing to write. Why couldn’t a switch avoid all keywords
and look more like a list...such as:

    
    
        ?(value):
        - 0: { doThing(); }
        - 1: { otherThing(); }
        - *: { doDefault(); }

~~~
rbonvall
Haskell's syntax is pretty minimal:

    
    
      f 0 = doThing
      f 1 = otherThing
      f _ = doDefault

------
ohithereyou
given/when is also available in Perl 5 from 5.10.1 onward[1]. There are times
when it is very helpful.

[1] [https://perldoc.perl.org/perlsyn.html#Switch-
Statements](https://perldoc.perl.org/perlsyn.html#Switch-Statements)

~~~
Grinnz
Don't use it in Perl 5. You have to first read the whole documentation on what
when does[1] and then the whole documentation on what smartmatch does[2] and
then you probably still won't know what will happen if you compare two values
that might or might not be numbers, because Perl does not make this
distinction for you, so smartmatch guesses. There is a reason every other
operator has explicit stringy and numeric comparison versions, and optional
new stringy bitwise operators were added because they had the same
ambiguity[3].

[1] [https://perldoc.pl/perlsyn#Experimental-Details-on-given-
and...](https://perldoc.pl/perlsyn#Experimental-Details-on-given-and-when)

[2] [https://perldoc.pl/perlop#Smartmatch-
Operator](https://perldoc.pl/perlop#Smartmatch-Operator)

[3]
[https://perldoc.pl/feature#The-'bitwise'-feature](https://perldoc.pl/feature#The-'bitwise'-feature)

~~~
Grinnz
A more explicit switch statement for only strings or numbers is available from
CPAN:
[https://metacpan.org/pod/Switch::Plain](https://metacpan.org/pod/Switch::Plain)

------
skywhopper
I feel like you can approach most of this with Ruby’s `case`. Not sure about
the `when @ary.any` but it wouldn’t be hard to monkeypatch a fix (not that it
would be a good idea).

------
codr7
I'm usually looking for two kinds of switching, most implementations support
one of them. Switching on a list of conditions in priority order like CL's
COND, and switching on a value.

Simply making the value optional and injecting it in specified conditions if
present would allow using the same construct for both.

Note that this may well be possible in P6, most things are; but I have yet to
come across and example of COND-behavior, they're all switching on values from
what I can see.

~~~
aasasd
Behold:

    
    
        switch (true) {
            case a > b: ...
            case isFullMoon(): ...
        }

~~~
codr7
Thanks, I have yet to come across anything that P6 doesn't have a feature for.
The languages [0] I've designed so far don't even come close in complexity,
and I'm still balancing the edge of my abilities to keep track of everything.
If they manage to make that monster run reasonably correct and fast, I'm
seriously impressed.

[0]
[https://github.com/codr7/g-fu/tree/master/v1](https://github.com/codr7/g-fu/tree/master/v1)

~~~
aasasd
Well, that's not specifically Perl above―it should work in most languages with
`switch`. It's also highly questionable to my taste, as I don't see why `if`
wouldn't do. I've chastised a dude many a time for its use, but that was only
one of his dubious choices in life, so...

