Presentation is loading. Please wait.

Presentation is loading. Please wait.

Where Angels Fear To Tread: A switch statement for Perl Damian Conway School of Computer Science and Software Engineering Monash University Australia.

Similar presentations


Presentation on theme: "Where Angels Fear To Tread: A switch statement for Perl Damian Conway School of Computer Science and Software Engineering Monash University Australia."— Presentation transcript:

1 Where Angels Fear To Tread: A switch statement for Perl Damian Conway School of Computer Science and Software Engineering Monash University Australia

2 But Perl doesn't need a case statement
Already too many ways to implement multi-way conditional statement perlsyn, perlfaq7, and perl.com list a plethora of them...

3 Cascaded if if ($n == 1) { print "n is 1" } elsif ($n eq 'two') { print "n is 2" } elsif ($n =~ /3$/) { print "n ends in 3" } else { print "n is big" }

4 Named block and if statements
SWITCH: { if ($n == 1) { print "n is 1"; last SWITCH; } if ($n eq 'two') { print "n is 2"; last SWITCH; } if ($n =~ /3$/) { print "n has 3"; last SWITCH; } print "n is big" }

5 Named block and if qualifiers
SWITCH: { print "n is 1" and last SWITCH if $n == 1; print "n is 2" and last SWITCH if $n eq 'two'; print "n has 3" and last SWITCH if $n =~ /3$/; print "n is big" }

6 Logical connectives $n == 1 && print("n is 1") || $n eq 'two' && print("n is 2") || $n =~ /3$/ && print("n ends in 3") || print("n is big");

7 Ternary chain print( $n == 1 ? "n is 1" : $n eq 'two' ? "n is 2" : $n =~ /3$/ ? "n ends in 3" : "n is big" );

8 for and patterns for ($n) { /^1$/ and do{ print "n is 1" ; last }; /^two$/ and do{ print "n is 2"; last }; /3$/ and do{ print "n ends in 3"; last }; do{ print "n is big"; last }; }

9 Hashes print { '1' => "n is 1", 'two' => "n is 2", 'III' => "n is 3", }->{$n} || "n is big";

10 The infamous "Christmas tree" :
for ($n) { print do { /^1$/ && "n is 1" || /^two$/ && "n is 2" || /^.*3$/ && "n has 3" || "n is big" }}

11 The infamous "Christ()mas tree":
for ($n) { print do { /^1$/ && "n is 1" || /^two$/ && "n is 2" || /^.*3$/ && "n has 3" || "n is big" }}

12 The infamous "Christ(in)mas tree":
for ($n) { print do { /^1$/ && "n is 1" || /^two$/ && "n is 2" || /^.*3$/ && "n has 3" || "n is big" }}

13 The infamous "Christ(iaen)mas tree":
for ($n) { print do { /^1$/ && "n is 1" || /^two$/ && "n is 2" || /^.*3$/ && "n has 3" || "n is big" }}

14 The infamous "Christ(iansen)mas tree":
for ($n) { print do { /^1$/ && "n is 1" || /^two$/ && "n is 2" || /^.*3$/ && "n has 3" || "n is big" }}

15 But Perl does need a case statement
Cornucopia of options but... ...that's the problem! Even veteran Perl hackers tire of the endless elsif-ing So perltodo dreams of a better solution:

16 But Perl does need a case statement
Explicit switch statements Nobody has yet managed to come up with a switch syntax that would allow for mixed hash, constant, regexp checks. Submit implementation with syntax, please.

17 But Perl does need a case statement
So I did

18 So what do you like in a case statement, baby?
Most languages have it Semantics vary enormously A quick tour...

19 Fortran's "computed GO TO":
 GO TO (10,20,30) N PRINT *, 'N is 1' GOTO PRINT *, 'N is 2' GOTO PRINT *, 'N is 3' GOTO

20 Fortran's "assigned GO TO":
 GO TO N (10,20,30) PRINT *, 'N is 10' GOTO PRINT *, 'N is 20' GOTO PRINT *, 'N is 30' GOTO

21 Pascal's CASE statement
CASE (n) OF 1: WRITELN('N is 1'); 2: WRITELN('N is 2'); 3: WRITELN('N is 3'); 10,20,30: WRITELN('N is big') END;

22 Ada's case statement case (n) is when 1 => PUT("N is 1"); when 2 => PUT("N is 2"); when 3 => PUT("N is 3"); when 10|20|30 => PUT("N is big"); when others => PUT("Ada is fussy"); end;

23 C's switch statement switch (n) { case 1: printf("N is 1\n"); break; case 2: printf("N is 2\n"); break; case 3: printf("N is 3\n"); break; case 10: case 20: case 30: printf("N is big\n"); break; }

24 C's () switch statement switch (n) { case 1: printf("N is 1\n"); break; case 2: printf("N is 2\n"); break; case 3: printf("N is 3\n"); brea cas case 10: case 20: case 30: printf("N is big\n"); break; }

25 C's (is) switch statement
switch (n) { case 1: printf("N is 1\n"); break; case 2: printf("N is 2\n"); break; case 3: printf("N is 3\n"); bre case ' case 10: case 20: case 30: printf("N is big\n"); break; }

26 C's (inus) switch statement
switch (n) { case 1: printf("N is 1\n"); break; case 2: printf("N is 2\n"); break; case 3: printf("N is 3\n"); br case 'a': case 10: case 20: case 30: printf("N is big\n"); break; }

27 C's (infous) switch statement
switch (n) { case 1: printf("N is 1\n"); break; case 2: printf("N is 2\n"); break; case 3: printf("N is 3\n"); b case 'a': if case 10: case 20: case 30: printf("N is big\n"); break; }

28 C's (infamous) switch statement
switch (n) { case 1: printf("N is 1\n"); break; case 2: printf("N is 2\n"); break; case 3: printf("N is 3\n"); case 'a': if (0) case 10: case 20: case 30: printf("N is big\n"); break; }

29 Visual Basic's Select Case statement
Select Case N Case 1: out = "N is 1" Case 2: out = "N is 2" Case 3: out = "N is 3" Case 10: out = "N is big" Case 20: out = "N is big" Case 30: out = "N is big" End Select

30 Visual Basic's Select Case statement
Select Case Nword Case "one": out = "N is 1" Case "two": out = "N is 2" Case "three": out = "N is 3" Case "many": out = "N is big" End Select

31 Bourne Shell's case statement
case $n in 1) echo "N is 1" ;; "two") echo "N is 2" ;; *3) echo "N ends in 3" ;; 10|20|30) echo "N is big" ;; esac

32 Lisp/Scheme's cond statement
(display (cond ( (= n 1) "N is 1" ) ( (eq n 2) "N is identical to 2" ) ( (eqv n 3) "N is equivalent to 3" ) ( (equal n "ten") "N is same as 10" ) ( (isprime n) "N is prime" ) ( (= n (fib n)) "N is self-Fibonacci" ) ) )

33 Perl's switch statement presumptive
Over the years, discussed on P5P Focussed on possible hash-based syntax

34 Perl's switch statement presumptive
switch ( $chromosome ) { 'X' => { print 'woman' }, 'Y' => { print 'man }, 'Z' => { print 'chicken' }, => { print 'Leeloo Minai Lekarariba Laminai-Tchai' }, }

35 Perl's switch statement presumptive
Variants proposed by Larry, Nat, and Graham Barr, amongst others With the deepest and most humble respect to these luminaries of Perldom...

36 Perl's switch statement presumptive
BLETCH!

37 So what does Perl need in a case statement?
Power Simplicity Generality Flexibility DWIMity

38 How to get there Need to rethink what a case statement actually is
One common feature in all mechanisms described Process of testing single controlling value (switch value) ...against set of possible matching values (case values)

39 How to get there Defining characteristic: two halves of test appear in separate places in code Case mechanism is distributed conditional test in two senses: Logically distributed over multiple case values Physically distributed over multiple locations in code

40 Anything they can do, Perl can do better
Need a "Swiss Army" case mechanism worthy of Perl Generalize notion of distributed conditional testing as far as possible Specifically, concept of "matching" between switch value and case values Need not be restricted to numeric (or string or referential) equality, as in other languages

41 Anything they can do, Perl can do better
Perl offers numerous ways in which two scalar values ($s and $c) could match: two numbers/refs $s == $c two strings $s eq $c regexp vs string $s =~ /$c/ array ref vs number s->[$c] defined $s->[$c] 0 < $c && $c

42 Anything they can do, Perl can do better
Perl offers numerous ways in which two scalar values ($s and $c) could match: hash ref vs scalar $s->{$c} defined $s->{$c} exists $s->{$c} array ref vs regexp grep hash ref vs regexp grep /$c/&&$s->{$_}, keys %$s sub ref vs array ref sub ref vs scalar $s->($c)

43 Anything they can do, Perl can do better
Perl offers numerous ways in which two scalar values ($s and $c) could match: two array refs map {$x=$_;map $x==$_, map {$x=$_;map $x map {$x=$_;map /$x/, map map map

44 Anything they can do, Perl can do better
Perl offers numerous ways in which two scalar values ($s and $c) could match: two array refs map {$x=$_;map $x==$_, map {$x=$_;map $x map {$x=$_;map /$x/, map map map

45 Anything they can do, Perl can do better
 2 (nearly) Only equality and intersection tests are commutative For all other cases, types of switch value and case value might be reversed Any Perl case mechanism should support all these WTDI

46 The switch.pm module Implements simple generalized case mechanism
Covers (most) matching alternatives shown Augments standard Perl syntax with two new control statements: switch case

47 The switch statement The switch statement takes single optional scalar argument of any type: number string array ref hash ref sub ref (can specify as simple block) pattern ref (can specify as /.../)

48 The switch statement If argument omitted, $_ used instead
Caches argument as current switch value Stored in (localized) control variable ${^SWITCH}

49 The case statement Takes a single scalar argument
...in mandatory parentheses if it's a variable Selects appropriate type of matching between argument and current switch value

50 The case statement Type of matching determined by respective types of ${^SWITCH} value and argument of case If match successful, block associated with the case statement is executed In all other respects, case statement semantically identical to if statement: can be followed by else clause can be postfix statement qualifier

51 The case statement Provides fully generalized case mechanism:

52 The case statement use switch; # and later %Homeric = ( woohoo => 1, d'oh => 1, hmmmmmmph => 0 ); while (<>) { switch ($_); case \%Homeric { print "homer\n"; next } # if $special{$_} case /a-z/i { print "alpha\n"; next } # if $_ =~ /a-z/i case [1..9] { print "small num\n"; next } # if $_ in [1..9] case { $_[0] >= 10 } { # if $_ >= my $age = <>; switch { $_[0] < $age }; case 20 { print "teens\n"; next } # if 20 < $age case 30 { print "twenties\n"; next } # if 30 < $age else { print "history\n"; next } } print "must be punctuation\n" case /\W/; # if $_ ~= /\W/ }

53 The case statement A switch can be nested within a case block
Series of case statements can try different types of matches against same switch value: hash membership pattern match array intersection simple equality

54 The case statement Also solves awkward eq vs == problem
Instead of: if ( do{ no warnings 'numeric'; $x==$y || $x eq $y} ) { print "same" } Can use: switch $x; case ($y) { print "same" }

55 The case statement Use of intersection tests against array reference useful for aggregating cases ...and ability to use patterns handles mixed types sub classify_digit { switch ($_[0]); case 0 { return 'zero' } case [2,4,6,8] { return 'even' } case [1,3,4,7,9] { return 'odd' } case /[A-F]/i { return 'hex' } }

56 Implementation Module implemented entirely in Perl
Uses source code filtering (Paul Marquess's Filter module)

57 Filtering The switch.pm module structured single subroutine named filter Calls Filter::Util::Call::filter_add in switch::import Activates source code filtering Whenever use switch processed, switch::filter invoked on any source code thereafter

58 Filtering Once filter has filtered, whatever remains in $_ is compiled and executed switch.pm uses this facility to replace calls to switch and case Inserts regular Perl code implementing same functionality

59 Parsing To replace switch and case, first have to find them
Differentiate between genuine case directives and: strings: q{Ceci n'est pas une case} symbol: *case = \&case barewords: ${case}{case}={case=>'upper'}

60 Parsing Have to parse source being filtered
Walk through source code and identify (in this order): Literal strings and other Perl quote-like operators (extract_quotelike) Perl variables (extract_variable) Genuine switch statements (/\s*switch/) Genuine case statements (/\s*case(?!\s*=>)/) Anything else (/\s*(\w+|#.*\n|\W)/)

61 Parsing Try quote-likes and variables first
So any "faux" case and switch dealt with correctly Passed through filter unchanged

62 Preprocessing switch Any switch statement identified has string 'local ${^SWITCH};' prepended Trailing argument then examined to see if it's a code block Extracted using extract_codeblock "Test compiled" to determine if actually anonymous hash constructor:

63 Preprocessing switch sub is_block { local ($SIG{__WARN__}, $^W, ) = (sub{die 1, undef); return !defined eval 'my $hr='.$_[0]; }

64 Preprocessing switch If is_block indicates trailing block is hash constructor, it's passed through filter unchanged Otherwise, has string 'sub' prepended Converts it to explicit anonymous subroutine Subroutine body then recursively filtered (to fix any nested switch or case)

65 Preprocessing case Processed in the similar way to switch
But no local ${^SWITCH} prepended Trailing code blocks identified, checked, modified, and recursively filtered Then converted to if statement Locate trailing code block Place parentheses around conditional

66 Execution No other preprocessing required
Semantics of new keywords implemented by two exported subroutines: switch::switch switch::case

67 The switch::switch subroutine
Determines type of argument passed to it Constructs closure about that argument Assigns closure to ${^SWITCH} variable Closure takes one argument and matches it against original switch value

68 The switch::switch subroutine
Suppose switch invoked with subroutine reference argument Would construct the following closure:

69 The switch::switch subroutine
my $s_val = $_[0]; my $s_ref = ref $s_val; if ( $s_ref ) eq 'CODE' ) { ${^SWITCH} = sub { my $c_val = $_[0]; my $c_ref = ref $c_val; return $s_val == $c_val if $c_ref eq 'CODE'; return if $c_ref eq 'ARRAY'; return $s_val->($c_val); } }

70 The switch::switch subroutine
Suppose, instead, switch passed non- numeric, non-reference scalar value Would set up following closure (optimized for comparisons against a string)

71 The switch::switch subroutine
my $s_val = $_[0]; my $s_ref = ref $s_val; if ($s_ref ) eq 'CODE' ) { ${^SWITCH} = sub { my $c_val = $_[0]; my $c_ref = ref $c_val; return $s_val == $c_val if $c_ref eq 'CODE'; return if $c_ref eq 'ARRAY'; return $s_val->($c_val); } } elseif ( ref($s_val) eq "" && (~s_val&$s_val) ne 0) { ${^_SWITCH} = sub { my $c_val = $_[0]; my $c_ref = ref $c_val; return $s_val eq $c_val if $c_ref eq ""; return in([$s_val],$c_val) if $c_ref eq 'ARRAY'; return $c_val->($s_val) if $c_ref eq 'CODE'; return scalar $s_val=~/$c_val/ if $c_ref eq 'Regexp'; return scalar $c_val->{$s_val} if $c_ref eq 'HASH'; return; }

72 The switch::switch subroutine
Construct and cache the matcher subroutine in switch::switch So switch::case does nothing but invoke closure currently cached in ${^SWITCH} on case value:

73 The switch::switch subroutine
sub case { }

74 Wishful thinking Current implementation of switch.pm slow to compile
Has to (lazily) parse entire file into which it's imported Solution would be to implement in core (assuming proposal survives P5P scrutiny)

75 Wishful thinking Problems because has to syntactically identify type of argument passed to switch and case ...to catch and normalize instances where argument is block or regex

76 Wishful thinking No way to emulate smart argument processing behaviour of a built-in function such as map ...where first argument can be value or expression or block Could be solved by allowing "alternate specifiers" in prototypes:

77 Wishful thinking sub my_map { if (ref $_[0] = 'ARRAY') { # map via array } elsif (ref $_[0] eq 'CODE') { # map via subroutine } else { # map via scalar } }

78 Wishful thinking sub my_map { if (ref $_[0] = 'ARRAY') { # map via array } elsif (ref $_[0] eq 'CODE') { # map via subroutine } else { # map via scalar } }

79 Wishful thinking my_map 'str', $count, @data;
my_map { do_something }

80 Wishful thinking For switch/case two other extensions to prototypes would also be required: A \/ type specifier expects regexp in argument slot automagically converts it to qr// reference Ability to use & specifier in argument positions after the first Would (nearly) eliminate for pre- filtering switch and case:

81 Wishful thinking sub switch::switch { ${^SWITCH} = #...select closure here } sub switch::case { $_[1]->() if ${^SWITCH}->($_[0]) }

82 Higher-order Operations
Does not always provide syntactic improvement over cascaded if For example, if need to test switch value against series of conditions:

83 Higher-order Operations
sub beverage { switch shift; case { $_[0] < 10 } { return 'milk' } case { $_[0] < 20 } { return 'coke' } case { $_[0] < 30 } { return 'beer' } case { $_[0] < 40 } { return 'wine' } case { $_[0] < 50 } { return 'malt' } case { $_[0] < 60 } { return 'Moët' } else { return 'milk' } }

84 Higher-order Operations
When importing switch.pm, can also import special "placeholder" subroutine named _ _ Converts (almost) any expression to higher-order function:

85 Higher-order Operations
That is: use switch '_ _'; $e = _ _ < 2 + _ _; ...is equivalent to: $e = sub { $_[0] < 2 + $_[1] }; Hence:

86 Higher-order Operations
sub beverage { switch shift; case { $_[0] < 10 } { return 'milk' } case { $_[0] < 20 } { return 'coke' } case { $_[0] < 30 } { return 'beer' } case { $_[0] < 40 } { return 'wine' } case { $_[0] < 50 } { return 'malt' } case { $_[0] < 60 } { return 'Moët' } else { return 'milk' } }

87 Higher-order Operations
use switch '_ _'; sub beverage { switch shift; case _ _ < 10 { return 'milk' } case _ _ < 20 { return 'coke' } case _ _ < 30 { return 'beer' } case _ _ < 40 { return 'wine' } case _ _ < 50 { return 'malt' } case _ _ < 60 { return 'Moët' } else { return 'milk' } }

88 Higher-order Operations
Makes extensive use of operator overloading One problem: can't overload && and ||

89 Higher-order Operations
case 0 <= __ && __ < 10 { return 'digit' } Means: sub { 0 <= $_[0] } && sub { $_[0] < 10 } Inevitably true

90 Higher-order Operations
Module catches error Much better solution would be to extend overload.pm Allow overloading of both logical connectives

91 Conclusion Propose this syntax and semantics for explicit case mechanism for Perl Minimal Conforms to general pattern of existing Perl control structures Rich, DWIM semantics

92 Conclusion Module available from CPAN A proof-of-concept only
If useful and popular, switch/case semantics should eventually be moved into core

93 Questions


Download ppt "Where Angels Fear To Tread: A switch statement for Perl Damian Conway School of Computer Science and Software Engineering Monash University Australia."

Similar presentations


Ads by Google