Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Potential features: Conditionals, Negative Patterns, And Pattern Completion #36

Open
lemeb opened this issue Oct 4, 2019 · 0 comments

Comments

@lemeb
Copy link

lemeb commented Oct 4, 2019

Hi @santinic! Once again, thank you so much for this great package that puts all of us very much closer to proper functional programming on Python.

I've been thinking a bit about what to improve in the pampy. Once again, I'm super happy doing the work of implementation myself, but I wanted to run that in an issue first. (see #35.)

I've looked mostly at the differences between pampy and pattern matching as it exists in OCaml, the purely functional programming language I'm the most familiar with. A good reference of all pattern matching functionalities of OCaml is this one, if you're curious.

1. Conditionals

Consider this OCaml function, which checks that two rationals are equal

let eq_rat cr = match cr with
   ((_,0),(_,0)) ->  true
 | ((_,0),_) ->  false
 | (_,(_,0)) ->  false
 | ((n1,1), (n2,1)) when n1 = n2 -> true
 | ((n1,d1), (n2,d2)) when ((n1 * d2) = (n2 * d1)) -> true
 | _ -> false;;
(*  REPL output:
 *  val eq_rat : (int * int) * (int * int) -> bool = <fun> 
 *)

OCaml, here, uses when as a guard that executes a conditional expression before the expression is executed. How do we do that in Python? You could do nested matching, but that can get complicated quite quickly.

I'm thinking about passing some form of data structure. I put a dict here, but it could easily be a class instance. In my example, there are two reserved variables, pattern and when, that can be put as keys in this dict to indicate the pattern and the conditional expression to be executed.

from pampy import pattern, when
eq_rat = lambda pair: match(pair,
    ((_,0),(_,0)),                            True,
    ((_,0),_),                                False,
    (_, (_, 0)),                              False,
    {pattern: ((_,1), (_,1)),
     when: lambda n1,n2: n1==n2},             True,
    {pattern: ((_,_), (_,_)),
     when: lambda n1,n2,d1,d2: n1*d2==n2*d1}, True,
    _,                                        False,
)

The flow would be the pattern is matched and we return (True, vars), the vars are then passed to the function defined in when, and we execute the action if when returns True. Otherwise we move on to the next pattern.

2. Negative Patterns

Consider this OCaml code, which returns the minimum of a pair of rational numbers:

let min_rat pr = match pr with
   ((_,0),p2) ->  p2
 | (p1,(_,0)) ->  p1
 | ((n1,d1), (n2,d2)) ->  
         if (n1*d2) < (n2*d1) then (n1,d1) else (n2,d2);;
(*  Console output:
 *  val min_rat : (int * int) * (int * int) -> int * int = <fun>
 *)

How do we translate that to pampy? Something like this:

min_rat = lambda pair: match(pair,
    ((_,0),_),      lambda idc, p2: p2,
    (_,(_,0)),      lambda p1, idc: p1,
    ((_,_),(_,_)),  lambda n1, d1, n2, d2: (
                        (n1, d1) if (n1*d2) < (n2*d1) else (n2, d2)),
) # here, idc (i don't care) refers to the parts of the variable that we will not use.

Since _ (as well as ANY, int, or Any) returns the value of the object, we can't discriminate, in the pattern, between the variables we will use for the action and the variables we will not. You have to pass to the action useless parameters, which are indicated in the code above with the idc name.

If you compare to the OCaml code, it's easy to see why. There:

  • _ actually refers to the values that will be dropped in the right-hand action,
  • and the named values are the ones that will be passed to the right-hand action.

We may want to modify panpy so as to recognize the difference. Here, I used the variable ø, which is technically allowed as a variable name in Python 3, but easy to type only on macOS. So it's obviously not a realistic choice, but it's just to show you how it could work:

min_rat = lambda pr: match(pr,
    ((ø,0),_),      lambda p2: p2,
    (_,(ø,0)),      lambda p1: p1,
    ((_,_),(_,_)),  lambda n1,d1,n2,d2: (
                        (n1, d1) if (n1*d2) < (n2*d1) else (n2, d2)),
) # Instead of ø, maybe we could use BYE, or _no, or _NO, whatever helps readibility.

Much more readable, I think. And, in the case of complex pattern matching, it might reduce the verbose-ness of the lambda functions significantly.

3. Pattern Warning

Look at this OCaml code:

let is_zero n = match n with  0 -> true ;;
(*  REPL Warning (Will be issued at compile time as well):
 *
 *  Characters 17-40:
 *  Warning: this pattern-matching is not exhaustive.
 *  Here is an example of a value that is not matched:
 *  1 
 *  val is_zero : int -> bool = <fun> 
 *)

I'm wondering if there is a way to implement this in pampy.


There are just my ideas, and I'm probably going to try to go ahead and fork the repo. Please tell me if there are any red flags! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant