On Tue, Dec 31, 2019 at 02:28:26PM -0800, Andrew Barnert via Python-ideas wrote:

>     if try 0, y, z := vec:
>         # do yz plane stuff
>     elif try x, 0, z := vec:
>         # do xz plane stuff
>     elif try x, y, 0 := vec:
>         # do xy plane stuff
>     elif x, y, z := vec:
>         # do slow/imprecise/whatever 3D stuff
>     else:
>         raise TypeError(f'{vec} is not a 3-vector!')

Comments below.


> Alternatively, this could just be a try expression that can be used 
> anywhere: it’s truthy if evaluating doesn’t raise, falsey if it does. 
> But I don’t think it’s needed anywhere but if/elif.

Have you read the exception catching PEP?

https://www.python.org/dev/peps/pep-0463/


> Anyway, neither of these features seems very useful on its own. (It 
> should be obvious how to rewrite that example as something just as 
> readable that just unpacks and then checks the values with normal if.)

Indeed. The ordinary "if" version is two lines shorter, although you 
lose the chance to provide a custom exception message.


> But I think the two of them together will allow a pure-library 
> implementation of pattern matching syntax that reads nicely and can do 
> everything most other languages do—in particular, concisely and 
> readably pattern match and deconstruct dataclasses (and other types 
> with an opt-in protocol or registry, but dataclasses would work out of 
> the box the way Scala case classes, Swift structs, etc. do).

I don't know about "reads nicely".


I really like the F# pattern matching syntax, even if it would require 
two new keywords (match, when) and an arrow symbol (F# uses -> but 
Python could use a colon). That reads nicely to me (and also supports 
guard clauses, although that's not needed in your example):

    # F# syntax
    match vec with
    | 0, y, z -> yz_expression
    | x, 0, z -> xz_expression
    | x, y, 0 -> xy_expression
    | x, y, z -> xyz_expression
    | _ -> fail


In F# matching is an expression. I don't know if that's an advantage or 
disadvantage.

I find the F# explanation for what pattern matching is all about *much* 
more understandable than the Haskell version -- even the "Gentle 
Introduction".

https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/pattern-matching

https://www.haskell.org/tutorial/patterns.html


Also relevant: Coconut has pattern matching:

https://marnee.silvrback.com/a-bit-of-railway-oriented-programming-with-coconut

https://coconut.readthedocs.io/en/master/DOCS.html#match


We don't need anything new to get prototype pattern matching.

Patterns are first class values in SNOBOL (and maybe in Icon?). Using 
that as inspiration, we could create Pattern objects to do the work, and 
a match function that we call like this:

    # Fail() is a function that raises an exception;
    # __ is a special predefined Pattern that always matches.

    result = match(vec, # object to match
              Pattern(0, 'y', 'z'), handle_yz,
              Pattern('x', 0, 'z'), handle_xz,
              Pattern('x', 'y', 0), handle_xy,
              Pattern('x', 'y', 'z'), handle_xyz,
              __, Fail('not a vector')
              )

The match() function would simply pass `vec` to each Pattern object in 
turn, until one matches, then calls the handle_* callback function with 
the variables stored in the pattern object after a successful match.

    def match(value, *args):
        if len(args)%2 != 0: raise TypeError
        for pattern, callback in pairs(args):
            if pattern.match(value):
                callback(**pattern.vars())

The implementation of match() is trivial: all the pattern matching 
goodies are in the Pattern object:

- if the match succeeds, the Pattern object stores the matched variables 
  ('x', 'y', 'z' in this case) and returns itself;

- if the match fails, it returns None.

- pattern.vars() returns a dict with the matched variables.


The pros: no new syntax required! Not even the walrus operator. This 
could, in principle, work all the way back to Python 1.5 if you were so 
inclined.

But the cons... 

* it's tediously verbose
* it's a nuisance having to pre-define the functions handle_* ahead of
  time (in Ruby, you would just pass a block) 
* but then the same applies for the old "use a dict dispatch table in 
  as a switch/case" idiom
* having to quote the names in the Pattern constructor is a pain.

But as a prototype, I think I've seen worse ideas.



-- 
Steven
_______________________________________________
Python-ideas mailing list -- python-ideas@python.org
To unsubscribe send an email to python-ideas-le...@python.org
https://mail.python.org/mailman3/lists/python-ideas.python.org/
Message archived at 
https://mail.python.org/archives/list/python-ideas@python.org/message/YZMP42SDPIW32GZMVSRJZBEABYYTTRVL/
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to