Coder Social home page Coder Social logo

pampy's Introduction

Pampy in Star Wars

Pampy: Pattern Matching for Python

License MIT Travis-CI Status Coverage Status PyPI version

Pampy is pretty small (150 lines), reasonably fast, and often makes your code more readable and hence easier to reason about. There is also a JavaScript version, called Pampy.js.

You can write many patterns

Patterns are evaluated in the order they appear.

You can write Fibonacci

The operator _ means "any other case I didn't think of".

from pampy import match, _

def fibonacci(n):
    return match(n,
        1, 1,
        2, 1,
        _, lambda x: fibonacci(x-1) + fibonacci(x-2)
    )

You can write a Lisp calculator in 5 lines

from pampy import match, REST, _

def lisp(exp):
    return match(exp,
        int,                lambda x: x,
        callable,           lambda x: x,
        (callable, REST),   lambda f, rest: f(*map(lisp, rest)),
        tuple,              lambda t: list(map(lisp, t)),
    )

plus = lambda a, b: a + b
minus = lambda a, b: a - b
from functools import reduce

lisp((plus, 1, 2))                 	# => 3
lisp((plus, 1, (minus, 4, 2)))     	# => 3
lisp((reduce, plus, (range, 10)))       # => 45

You can match so many things!

match(x,
    3,              "this matches the number 3",

    int,            "matches any integer",

    (str, int),     lambda a, b: "a tuple (a, b) you can use in a function",

    [1, 2, _],      "any list of 3 elements that begins with [1, 2]",

    {'x': _},       "any dict with a key 'x' and any value associated",

    _,              "anything else"
)

You can match [HEAD, TAIL]

from pampy import match, HEAD, TAIL, _

x = [1, 2, 3]

match(x, [1, TAIL],     lambda t: t)            # => [2, 3]

match(x, [HEAD, TAIL],  lambda h, t: (h, t))    # => (1, [2, 3])

TAIL and REST actually mean the same thing.

You can nest lists and tuples

from pampy import match, _

x = [1, [2, 3], 4]

match(x, [1, [_, 3], _], lambda a, b: [1, [a, 3], b])           # => [1, [2, 3], 4]

You can nest dicts. And you can use _ as key!

pet = { 'type': 'dog', 'details': { 'age': 3 } }

match(pet, { 'details': { 'age': _ } }, lambda age: age)        # => 3

match(pet, { _ : { 'age': _ } },        lambda a, b: (a, b))    # => ('details', 3)

It feels like putting multiple _ inside dicts shouldn't work. Isn't ordering in dicts not guaranteed ? But it does because in Python 3.7, dict maintains insertion key order by default

You can match class hierarchies

class Pet:          pass
class Dog(Pet):     pass
class Cat(Pet):     pass
class Hamster(Pet): pass

def what_is(x):
    return match(x,
        Dog, 		'dog',
        Cat, 		'cat',
        Pet, 		'any other pet',
          _, 		'this is not a pet at all',
    )

what_is(Cat())      # => 'cat'
what_is(Dog())      # => 'dog'
what_is(Hamster())  # => 'any other pet'
what_is(Pet())      # => 'any other pet'
what_is(42)         # => 'this is not a pet at all'

Using Dataclasses

Pampy supports Python 3.7 dataclasses. You can pass the operator _ as arguments and it will match those fields.

@dataclass
class Pet:
    name: str
    age: int

pet = Pet('rover', 7)

match(pet, Pet('rover', _), lambda age: age)                    # => 7
match(pet, Pet(_, 7), lambda name: name)                        # => 'rover'
match(pet, Pet(_, _), lambda name, age: (name, age))            # => ('rover', 7)

Using typing

Pampy supports typing annotations.

class Pet:          pass
class Dog(Pet):     pass
class Cat(Pet):     pass
class Hamster(Pet): pass

timestamp = NewType("year", Union[int, float])

def annotated(a: Tuple[int, float], b: str, c: E) -> timestamp:
    pass

match((1, 2), Tuple[int, int], lambda a, b: (a, b))             # => (1, 2)
match(1, Union[str, int], lambda x: x)                          # => 1
match('a', Union[str, int], lambda x: x)                        # => 'a'
match('a', Optional[str], lambda x: x)                          # => 'a'
match(None, Optional[str], lambda x: x)                         # => None
match(Pet, Type[Pet], lambda x: x)                              # => Pet
match(Cat, Type[Pet], lambda x: x)                              # => Cat
match(Dog, Any, lambda x: x)                                    # => Dog
match(Dog, Type[Any], lambda x: x)                              # => Dog
match(15, timestamp, lambda x: x)                               # => 15
match(10.0, timestamp, lambda x: x)                             # => 10.0
match([1, 2, 3], List[int], lambda x: x)                        # => [1, 2, 3]
match({'a': 1, 'b': 2}, Dict[str, int], lambda x: x)            # => {'a': 1, 'b': 2}
match(annotated, 
    Callable[[Tuple[int, float], str, Pet], timestamp], lambda x: x
)                                                               # => annotated

For iterable generics actual type of value is guessed based on the first element.

match([1, 2, 3], List[int], lambda x: x)                        # => [1, 2, 3]
match([1, "b", "a"], List[int], lambda x: x)                    # => [1, "b", "a"]
match(["a", "b", "c"], List[int], lambda x: x)                  # raises MatchError
match(["a", "b", "c"], List[Union[str, int]], lambda x: x)      # ["a", "b", "c"]

match({"a": 1, "b": 2}, Dict[str, int], lambda x: x)            # {"a": 1, "b": 2}
match({"a": 1, "b": "dog"}, Dict[str, int], lambda x: x)        # {"a": 1, "b": "dog"}
match({"a": 1, 1: 2}, Dict[str, int], lambda x: x)              # {"a": 1, 1: 2}
match({2: 1, 1: 2}, Dict[str, int], lambda x: x)                # raises MatchError
match({2: 1, 1: 2}, Dict[Union[str, int], int], lambda x: x)    # {2: 1, 1: 2}

Iterable generics also match with any of their subtypes.

match([1, 2, 3], Iterable[int], lambda x: x)                     # => [1, 2, 3]
match({1, 2, 3}, Iterable[int], lambda x: x)                     # => {1, 2, 3}
match(range(10), Iterable[int], lambda x: x)                     # => range(10)

match([1, 2, 3], List[int], lambda x: x)                         # => [1, 2, 3]
match({1, 2, 3}, List[int], lambda x: x)                         # => raises MatchError
match(range(10), List[int], lambda x: x)                         # => raises MatchError

match([1, 2, 3], Set[int], lambda x: x)                          # => raises MatchError
match({1, 2, 3}, Set[int], lambda x: x)                          # => {1, 2, 3}
match(range(10), Set[int], lambda x: x)                          # => raises MatchError

For Callable any arg without annotation treated as Any.

def annotated(a: int, b: int) -> float:
    pass
    
def not_annotated(a, b):
    pass
    
def partially_annotated(a, b: float):
    pass

match(annotated, Callable[[int, int], float], lambda x: x)     # => annotated
match(not_annotated, Callable[[int, int], float], lambda x: x) # => raises MatchError
match(not_annotated, Callable[[Any, Any], Any], lambda x: x)   # => not_annotated
match(annotated, Callable[[Any, Any], Any], lambda x: x)       # => raises MatchError
match(partially_annotated, 
    Callable[[Any, float], Any], lambda x: x
)                                                              # => partially_annotated

TypeVar is not supported.

All the things you can match

As Pattern you can use any Python type, any class, or any Python value.

The operator _ and built-in types like int or str, extract variables that are passed to functions.

Types and Classes are matched via instanceof(value, pattern).

Iterable Patterns match recursively through all their elements. The same goes for dictionaries.

Pattern Example What it means Matched Example Arguments Passed to function NOT Matched Example
"hello" only the string "hello" matches "hello" nothing any other value
None only None None nothing any other value
int Any integer 42 42 any other value
float Any float number 2.35 2.35 any other value
str Any string "hello" "hello" any other value
tuple Any tuple (1, 2) (1, 2) any other value
list Any list [1, 2] [1, 2] any other value
MyClass Any instance of MyClass. And any object that extends MyClass. MyClass() that instance any other object
_ Any object (even None) that value
ANY The same as _ that value
(int, int) A tuple made of any two integers (1, 2) 1 and 2 (True, False)
[1, 2, _] A list that starts with 1, 2 and ends with any value [1, 2, 3] 3 [1, 2, 3, 4]
[1, 2, TAIL] A list that start with 1, 2 and ends with any sequence [1, 2, 3, 4] [3, 4] [1, 7, 7, 7]
{'type':'dog', age: _ } Any dict with type: "dog" and with an age {"type":"dog", "age": 3} 3 {"type":"cat", "age":2}
{'type':'dog', age: int } Any dict with type: "dog" and with an int age {"type":"dog", "age": 3} 3 {"type":"dog", "age":2.3}
re.compile('(\w+)-(\w+)-cat$') Any string that matches that regular expression expr "my-fuffy-cat" "my" and "puffy" "fuffy-dog"
Pet(name=_, age=7) Any Pet dataclass with age == 7 Pet('rover', 7) ['rover'] Pet('rover', 8)
Any The same as _ that value
Union[int, float, None] Any integer or float number or None 2.35 2.35 any other value
Optional[int] The same as Union[int, None] 2 2 any other value
Type[MyClass] Any subclass of MyClass. And any class that extends MyClass. MyClass that class any other object
Callable[[int], float] Any callable with exactly that signature def a(q:int) -> float: ... that function def a(q) -> float: ...
Tuple[MyClass, int, float] The same as (MyClass, int, float)
Mapping[str, int] Any subtype of Mapping acceptable too any mapping or subtype of mapping with string keys and integer values {'a': 2, 'b': 3} that dict {'a': 'b', 'b': 'c'}
Iterable[int] Any subtype of Iterable acceptable too any iterable or subtype of iterable with integer values range(10) and [1, 2, 3] that iterable ['a', 'b', 'v']

Using default

By default match() is strict. If no pattern matches, it raises a MatchError.

You can instead provide a fallback value using default to be used when nothing matches.

>>> match([1, 2], [1, 2, 3], "whatever")
MatchError: '_' not provided. This case is not handled: [1, 2]

>>> match([1, 2], [1, 2, 3], "whatever", default=False)
False

Using Regular Expressions

Pampy supports Python's Regex. You can pass a compiled regex as pattern, and Pampy is going to run pattern.search(), and then pass to the action function the result of .groups().

def what_is(pet):
    return match(pet,
        re.compile('(\w+)-(\w+)-cat$'),     lambda name, my: 'cat '+name,
        re.compile('(\w+)-(\w+)-dog$'),     lambda name, my: 'dog '+name,
        _,                                  "something else"
    )

what_is('fuffy-my-dog')     # => 'dog fuffy'
what_is('puffy-her-dog')    # => 'dog puffy'
what_is('carla-your-cat')   # => 'cat carla'
what_is('roger-my-hamster') # => 'something else'

Install for Python3

Pampy works in Python >= 3.6 Because dict matching can work only in the latest Pythons.

To install it:

$ pip install pampy

or $ pip3 install pampy

If you really must use Python2

Pampy is Python3-first, but you can use most of its features in Python2 via this backport by Manuel Barkhau:

pip install backports.pampy

from backports.pampy import match, HEAD, TAIL, _

pampy's People

Contributors

dinkopehar avatar ecialo avatar gillett-hernandez avatar holdenweb avatar lgessler avatar santinic avatar symbiont-lukasz-dobrek avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pampy's Issues

accessibility on readme

The most great and simple examples in README are images and blind programmers cant see this examples.

Why not use ``` tags to describe it?

Potential features: Conditionals, Negative Patterns, And Pattern Completion

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! :)

make use of github releases

Hi,

Would you mind making use of the tags/release so that we can be notified when you release a new version?

Match on 2d list ?

Hi, thanks for the very interesting work.
Is matching 2d lists supported ?
Example:

match([['a', 1, 2], ['b', 3, 4], ['a', 5, 6]], ['a', _, _], lambda x, y : [x, y])

expecting

[[1, 2], [5, 6]]

Return function without call

The current behavior is that if an action is a function it by default gets called; I have some scenarios where this is undesireable. For instance, I might want to decide how to process data based on an enum and bind my processing function to that enum. I would want to know which function to use, but not yet call it and certainly not with the enum value. Or alternatively I might want to interpret and pattern match on dataset A and based on that determine how to process an entirely different dataset B.
Example:

fn = match(exp,
{"field": "some_value"}, fn_a,
{"field": "some_other_value"}, fn_b,
)
fn("some_string")

The above would fail because it would call the function with a dict while the functions expects a string.

currently the above is possible with the following hack:

fn = match(exp,
{"field" : "some_value"}, lambda x: fn_a,
{"field" : "some_other_value"}, lambda x: fn_b
)

however, this drastically diminishes readability.

I propose one of the following 2 solutions:

  1. Check whether the action is a lambda function and only call it if this is the case
  2. Add a named parameter to match that determines whether the given action is called or not.

Project still alive?

Hello, is this project still alive? I noticed that the owner hasn't been active for a year and no PRs have been accepted for a while. I know that pattern matching is hitting python 3.10 at some point, but I feel like this project still has a place, especially as it supports 2.7 still.

"Type Dict cannot be instantiated" error when executing code line from readme

I get this error when i try to match a Dict[str, int] :

I cant find any imports for Dict but assumed it was from typing?

In [1]: from pampy import match, _

In [2]: from typing import Dict

In [3]: match({"a": 1, "b": 2}, Dict[str, int], lambda x: x)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-3-e5c0ae675d5a> in <module>
----> 1 match({"a": 1, "b": 2}, Dict[str, int], lambda x: x)

~/.virtualenvs/sqlparse/lib/python3.7/site-packages/pampy/pampy.py in match(var, default, strict, *args)
    176
    177     for patt, action in pairs:
--> 178         matched_as_value, args = match_value(patt, var)
    179
    180         if matched_as_value:

~/.virtualenvs/sqlparse/lib/python3.7/site-packages/pampy/pampy.py in match_value(pattern, value)
     45         return match_dict(pattern, value)
     46     elif callable(pattern):
---> 47         return_value = pattern(value)
     48         if return_value is True:
     49             return True, [value]

/usr/local/Cellar/python/3.7.3/Frameworks/Python.framework/Versions/3.7/lib/python3.7/typing.py in __call__(self, *args, **kwargs)
    666     def __call__(self, *args, **kwargs):
    667         if not self._inst:
--> 668             raise TypeError(f"Type {self._name} cannot be instantiated; "
    669                             f"use {self._name.lower()}() instead")
    670         result = self.__origin__(*args, **kwargs)

TypeError: Type Dict cannot be instantiated; use dict() instead

publish for conda

In conda-recipe-s it is not possible to include pip dependencies, could you please also publish pampy as conda package?

Matching binary strings

I think there is a problem with matching simple python objects that have binary strings in them.

In the example below the first three cases should be matched, but they all fail.

match({b'name':'John', b'surname':b'Doe', b'age':42},
      {b'age':42}, lambda x:x,
      {b'name':'John'}, lambda x:x,
      {b'surname':b'Doe'}, lambda x:x,
      _, lambda x:'fail')

Another example, but the problem is the same, the binary string isn't matched.

match([1,b'binary_string',3],
      [1,b'binary_string',_], lambda x:x,
       _, lambda x:x)

I've tried the examples with the latest pampy 0.2.1 on python 3.6 and 3.7.

Thanks.

Support regex library

Today I tried to write the following recursive regex {(?:[^{}]|(?R))*} to find a json inside a string, but the std library re do not support this, then I install regex. But in pampy typing validation the pattern instance is checked against stdlib re.

elif isinstance(pattern, RegexPattern):
    rematch = pattern.search(value)
    if rematch is not None:
        return True, list(rematch.groups())

Is possible to support a external library or will it against the project goal? If its possivel I can open a PR for this.

OneOf operator for matching

Say I want to have a match for any one of the items, instead of creating two statements to match against we only need one which specifies that the value must be one within a set.

For example:

class Choice(Enum):
	START = 0
	COOPERATE = 1
	DEFECT = 2

match(Choice.DEFECT, OneOf(Choice.DEFECT, Choice.COOPERATE), True)

This will make some types of rules more succinct, especially with support for the Enum type.

Sounds like a good idea?

Better looking nested patterns?

Hi,

Is there any better way achieving this?

from pampy import match, _

test_items = [5, '5', [5], 'not a digit']

for x in test_items:
    result = match(x,
        str, lambda x: match(x,
                           str.isdigit, lambda x: int(x),
                           _, 0
                        ),
        int, x,
        _, 0
    )
    print(f'{x} ({type(x).__name__}) -> {result}')

It is hard to describe what we want to achieve in words, but it's easy to see by example:

5 -> 5
'5' -> 5
'not a digit' -> 0
[1] -> 0

It looks over-complicated.

Some (may be) bugs I found

Hi. Nice small library, tested out a lot of things. I really does match anything. But I found some mistakes or bugs.

  1. I know None is matched in _, but shouldn't None be matched with None also? For example:
x = None
print(match(x, None, 'None', _, 'Everything else')) => return Everything else
  1. If I match integer 1 or float 1.0 with True, shouldn't match function not match with those values ? Same is for 0 or 0.0 . I know 0 and 1 are also boolean False and True, but there are a lot of other false values too.
x = 1
print(match(x, True, 'True value', _, 'Everything else')) => returns True value
  1. Last thing, I don't know if you wanted this to match or no, but child class inhereting parent class, when matched with any of those classes, returns True.
class Parent:
    pass

class Child(Parent):
    pass

x = Child()
print(match(x, Parent, 'Child class', _, 'Everything else')) => returns Child class

Thank you for your time and time it took you to write this library 👍 👍 👍 .
I also made a pull request to fix some minor mistakes, so your welcome 🦊

Weird behavior with the default match operator

>>> from pampy import match, _

>>> _
_


>>> def matcher(val):
...     return match(val, int, "int", _, "something else",)

>>> matcher("hi")
'something else'


>>> matcher(1)
'int'


>>> matcher("hi")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in matcher
  File "/usr/local/lib/python3.7/site-packages/pampy/pampy.py", line 186, in match
    raise MatchError("'_' not provided. This case is not handled:\n%s" % str(var))
pampy.pampy.MatchError: '_' not provided. This case is not handled:
hi

'_' not provided. This case is not handled:
hi

Possibilty to match same object

Matching an instance of a class against it's class works perfectly but i just realied that you cannot match with the class itself:

class Marker: pass
match({"my_marker": Marker}, {"my_marker": Marker}, lambda x: print('MATCH'))

I think this should be a valid match right? Looking at the code at

https://github.com/santinic/pampy/blob/master/pampy/pampy.py#L59

seems like here we should add a check based on

inspect.isclass

What do you think?

raise in match

Hi,
below sample code give me error:

   return match
    (
        name,
        "WarmupMultiStepLR",  WarmupMultiStepLR( \
            optimizer, \
            cfg.SOLVER.STEPS, \
            cfg.SOLVER.GAMMA, \
            warmup_factor=cfg.SOLVER.WARMUP_FACTOR, \
            warmup_iters=cfg.SOLVER.WARMUP_ITERS, \
            warmup_method=cfg.SOLVER.WARMUP_METHOD, \
        ),
        "WarmupCosineLR", WarmupCosineLR( \
            optimizer, \
            cfg.SOLVER.MAX_ITER, \
            warmup_factor=cfg.SOLVER.WARMUP_FACTOR, \
            warmup_iters=cfg.SOLVER.WARMUP_ITERS, \
            warmup_method=cfg.SOLVER.WARMUP_METHOD, \
        ),
        _, raise ValueError("Unknown LR scheduler: {}".format(name))
    )

_, raise ValueError("Unknown LR scheduler: {}".format(name))
^
SyntaxError: invalid syntax

python 2 support

hi there, thanks for writing pampy and it looks very interesting. at the moment, only python2 is possible in my work environment, may I ask if there is chance we can have this backport to python2? thanks

Patterns with guards?

Very cool module! This is one of the things I like about scala and I'm excited to be able to use this in python.

I was wondering if there would be some way to allow for conditionals in the patterns? Something like this (but not necessarily using this syntax):

match(input,
    pattern_1 if cond_1, action_1,  # matches pattern_1 under condition cond_1
    pattern_1, action_1a,  # default matching for pattern_1
...
)

So, in this case, the first pattern wouldn't match the input unless cond_1 was true. So, you could
do something like:

match(x,
    int if x < 10, print("integer value less than 10"),
    int, print("integer value >= 10")
}

Interactive demo for `pampy` code examples

Hello @santinic,

I'm following pampy since a few weeks and I really like what you guys have built. The concept is really cool, and it's incredible how simple your solution was. We've even started using it to teach our students at https://rmotr.com/ (co-founder and teacher here).

While looking at the code examples in the README file I thought it would be great to provide some interactive demo that people can use to play with the library before committing to download and install it locally.

A demo is worth a thousand words 😉

I spent some time compiling all pampy examples into a Jupyter Notebook file, and adapted it in a way that we can open it with a small service we have at RMOTR to launch Jupyter environments online. No account or subscription required to use it, so it's pretty convenient for people that wants to get hands on the library with a low entrance barrier.

The result is what you can see in my fork of the repo (see the new "Demo" section):
https://github.com/martinzugnoni/pampy

Do you agree that having such interactive demo would help people to know and use pampy?
Let's use this issue as a kick off to start a discussion. Nothing here is written in stone and we can change everything as you wish.

I hope you guys like it, and I truly appreciate any feedback.

thanks.

Possible C module?

Pampy is pretty small (150 lines), reasonably fast.

Reasonably fast

I didn't try pampy yet but I was wondering if a C native module would help to improve the speed?

Numpy has some parts written in C to improve performance.

This is just a minor suggestion. Perhaps it is completely useless.

add raise_error function

this is taken from #43

Idea:

from pampy import match, _, raise_error

return match(x,
  1, "foo",
  2, "bar",
  _, raise_error(ValueError, "Message")
)

Guard support ?

Hi, Great jobs on pampy ! this is the closest thing I could find to core.match from clojure.

I'm wondering if we can apply a new guard function in matching pattern ?

match ( 3,
    (lambda x: x%2==1), print("It is even number"),
    ....
    _, print("not match")

Iterable type checking on first element

Per README:

For iterable generics actual type of value is guessed based on the first element.

match([1, 2, 3], List[int], lambda x: x)                        # => [1, 2, 3]
match([1, "b", "a"], List[int], lambda x: x)                    # => [1, "b", "a"]

Is there a specific reason to do this, as opposed to check the type of the value is on every element? It seems like the current implementation is not great if one wants to do strict type checking.

I was considering forking this repo and correcting this [1], but I wanted to double check with you that it was a good idea to do so.

In any case, thank you so much for this kickass package, and have a great day! :)

[1]: I was considering using the TypeGuard library to make things go faster. I'm assuming the reason to guess the value on the first element is performance, but what do I know...

match if equal?

Is there a way to match a pattern like [_, 0, _] only if the two _s are equal, and if they aren't continue to the next pattern?

Possibly misbehaviour of match_value function?

Hello there

Thank for the great package. Gets really handy in my current projects :)

However, I'm wondering if following piece of code was intentionally written or it is just an logical error(?)

#pampy.py
....
ValueType = (int, float, str, bool)
....
elif isinstance(pattern, ValueType):
return pattern is value, []
....

It is a bad idea to compare strings with is operator, isn't it?
In one of the projects I'm getting errors because strings cannot be properly compared. I use regex as a workaround, but it seems like an overkill for my case, where I compare simple strings, nothing fancy

Matching of Python3 `enum`

Matching Python 3 Enum classes always fails.

This can be fixed by adding support for Enum at:

elif isinstance(pattern, (int, float, str, bool)):

To:

from enum import Enum

# ...

    elif isinstance(pattern, (int, float, str, bool, Enum)):

Built-in equality checks for enum types will work the same as other scalar values.

However, this will break Python 2.x compatibility, where no Enum type is available.

Typing support

Hi! Thanks for this library, it looks fun.

Does it support typing and mypy checks?
Are there any plans to support it?

_ is unsafe to use

_ already has several uses in Python:

  • Most REPL implementations assign the last output to it
  • It is commonly used in localization
  • Some Programmers use it to indicate a return value they don't need

All of this can, as I imagine, lead to very unexpected behaviour.

Proposed New Syntax

I always want to write something like Haskell in Python

with Multimethod() as fib:
    fib[1] = 1
    fib[2] = 2
    fib[int] = lambda n: fib(n-1) + fib(n-2)

and this can be done by leveraging the pampy package and some simple clasees

class Matcher:
    def __init__(self):
        self.pattern = []
    
    def __setitem__(self, pat, method):
        self.pattern.append(pat)
        self.pattern.append(method)
        
    def __call__(self, *x):
        if len(x)==1:
            x = x[0]
        return match(x, *self.pattern)
class Multimethod:
    
    def __init__(self, *sig):
        pass
    
    def __enter__(self):
        return Matcher()
    
    def __exit__(self, *args):
        pass

Would it be good to have something like this inside the package?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.