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

caseStmtMacros to support C#8-like Recursive Patterns matching (Deconstruction Pattern) #57

Open
timotheecour opened this issue Aug 15, 2018 · 15 comments

Comments

@timotheecour
Copy link
Member

see https://gist.github.com/Araq/169d1e24b2b996d024a780ef6a4e6c09#file-casestmtmacros-nim for context, introduced by @Araq for a 1st version of this concept.

IT reminds me of Recursive Patterns matching (Deconstruction Pattern) in C#8, see https://www.infoq.com/articles/cs8-ranges-and-recursive-patterns

switch (employee) {
 case Employee {Name: "Bassam Alugili", Company: Company(_, _, _)} employeeTmp: ...
 case Employee(_, _, ("Stratec", _, _)) employeeTmp: ...
  case Employee(_, _, Company(_, _, _)) employeeTmp: ...
}
  • recursive could be handled later.

  • But supporting _ placeholder is very useful, would be nice to support it

  • furthermore, if could be handled as well, it should be no different from case in terms of capabilities (ie introduce also ifStmtMacros)
    analog to how if and case both handle pattern matching in C#
    EDIT see reply below; this is better handled with a regular macro matches

if (employee is Employee(_, _, ("Stratec", _, _)) employeeTmp) { Console.WriteLine($ "The employee: {employeeTmp.Name}!");}

where?

matchers.nim

links

snippet copied over from @Araq's post

{.experimental: "caseStmtMacros".}

import macros

macro match(n: tuple): untyped =
  result = newTree(nnkIfStmt)
  let selector = n[0]
  for i in 1 ..< n.len:
    let it = n[i]
    case it.kind
    of nnkElse, nnkElifBranch, nnkElifExpr, nnkElseExpr:
      result.add it
    of nnkOfBranch:
      for j in 0..it.len-2:
        let cond = newCall("==", selector, it[j])
        result.add newTree(nnkElifBranch, cond, it[^1])
    else:
      error "'match' cannot handle this node", it
  echo repr result

case ("foo", 78)
of ("foo", 78): echo "yes"
of ("bar", 88): echo "no"
else: discard
@GULPF
Copy link
Member

GULPF commented Aug 15, 2018

I think it's hard to support if. How would the compiler know that a macro should be invoked? For case it's simple since the type of the expression decides it. Could you give an example of how ifStmtMacros would work?

@mratsim
Copy link
Collaborator

mratsim commented Aug 15, 2018

In a v2 I'd like to have assignment support when deconstructing, maybe via a special prefix symbol like @:

case ("foo", 78)
of ("foo", @a): echo $(a*2) # prints 78 * 2 = 156
of ("bar", 88): echo "no"
else: discard

@timotheecour
Copy link
Member Author

@GULPF @Araq on second thought, ifStmtMacros are not needed, maybe we can just use a regular macro (which itself could share code with macro match perhaps, idk):

let expr = ("foo", 78)
if expr.matches(("foo", _)): discard # matches a tuple of length 2
if expr.matches(("foo", )): discard # matches a tuple of length 1
if expr.matches("foo"): discard # same as if expr == "foo"
# works with matching against derived type, allowing also to capture derived instance
if expr.matches(Employee(name: "bob"), myderivedinstance): echo myderivedinstance.name # same as C#  example `if (bassam is Employee {Name: "bob"} myderivedinstance)`

this is not specific to if and could be used in any context where a boolean is expected, eg:

when expr.matches(("foo", _)) : discard
doAssert expr.matches(("foo", _))
let ok = expr.matches(("foo", _))

implementation

macro matches(pattern: untyped) = untyped # will evaluate to bool
macro matches(pattern: untyped, capture: untyped) = untyped # will evaluate to bool and capture derived pattern in `capture`, as in C# and example above

@timotheecour timotheecour changed the title caseStmtMacros and ifStmtMacros to support C#8-like Recursive Patterns matching (Deconstruction Pattern) caseStmtMacros to support C#8-like Recursive Patterns matching (Deconstruction Pattern) Aug 15, 2018
@drslump
Copy link

drslump commented Aug 15, 2018

IMHO these proposals should start by providing a good example of current Nim code that would benefit from it. Is there something that gets written time and time again that would improve with this? (also including readability not only terseness when writing the code).

I do think that deconstructing is a good tool though, but often times we just want to guard and alias to locals, which is an use case that can be solved more cleanly with something like this I think:

if let a = ("foo", a):
   echo $(a*2)

if let _ = ("foo", _):   # `_` is just used to force deconstructuring
  echo "hi!"

# from the original comment
# case let a, b = ("foo", 78)
# of ("foo", a): echo $(a*2)  # referencing `b` would produce an error, not in scope
# of ("bar", c): echo "no"  # error: `c` is not part of the case let statement 
# else: discard

# this I think is better
case ("foo", 78)
of let a = ("foo", a): echo $(a*2)
of let _ = ("foo", _): echo "ok"  # forced matching
of ("bar", 80): echo "no"  # no destructuring or matching
else: discard

Alternatively, a fully featured match expression could be nice but I think that would change how the language is used quite substantially.

@LemonBoy
Copy link

For reference also scheme's matchable is implemented as a macro and is quite flexible.

@mratsim
Copy link
Collaborator

mratsim commented Aug 16, 2018

Seems like Rust-like if let, while let, case let for deconstruction + assignment is coming back quite often (and this fits a similar needs to the new Python := I feel).

The most ergonomic pattern matching I came across was Haskell there are 2 syntaxes, unfortunately the nicer one does not fit Nim at all (though the second one is very similar to Nim's).

Syntax 1:

take  0   _       =  []
take  _   []      =  []
take  n   (x:xs)  =  x : take (n-1) xs

Syntax 2

take m ys = case (m,ys) of
              (0,_)    ->  []
              (_,[])   ->  []
              (n,x:xs) ->  x : take (n-1) xs

@timotheecour
Copy link
Member Author

timotheecour commented Aug 21, 2018

isn't this {.experimental: "caseStmtMacros".} gonna run into nim-lang/Nim#8676 (comment) ?
@yglukhov does your proposal nim-lang/Nim#8676 (comment) address this for caseStmtMacros?

@alehander92
Copy link

I like the https://github.com/nim-lang/Nim/issues/8649#issuecomment-413323627 idea, if one can capture a submatch

Pattern matching is a big ergonomic win, there are so many if a.field == and a.field.b == and a.other == which will look so much better with a matching syntax

@krux02 's macro in his ast pattern lib can also be made to work on runtime values, we just have to agree on some kind of standard API

@krux02
Copy link
Contributor

krux02 commented Sep 3, 2018

With the features that are already there in Nim I implemented recursive pattern matching fo AST nodes here: https://github.com/krux02/ast-pattern-matching.

Please get to the point and explain why the given language features are not enough. The issue is just a compilation of random ideas and in general quite a mess.

@andreaferretti
Copy link

Whatever comes out of this, I would argue that it should be enough to support both patty and AST pattern matching (both suitably adapted)

@alehander92
Copy link

alehander92 commented Sep 12, 2018

The problem is that both @andreaferretti 's patty and @krux02 's ast-pattern-matching currently lack a lot of features, but in a very different way: e.g.

now, I really want to have a working lib:

  • patty already works on runtime with a lot of typical nim types
  • but ast-pattern-matching seems to have more of the "matching/capturing" functionality ready, which just needs to be generalized

on the other hand the patty and the ast-pattern-matching dsl-s differ .. but not greatly. this means we might have two slightly different dsl-s for the same thing in an already small ecosystem.

Do you guys want to compromise on a single api and somehow combine the two libs approaches into a single , more universal nim pattern matching lib?

Such a lib can still be

  • flexible enough to be used for pattern matching on most possible nim types(and kind of types)
  • able to work on compile time, on ast nodes and other types
  • zero overhead (with optional slower error reporting when used on compile time)

I worked before on personal forks of both libs, but I couldn't decide which would be more feasible to adapt (and I didn't have time). I also planned to make my own match macro, but then we will just have 3 libs (insert xkcd standards comic)

@andreaferretti
Copy link

I would certainly want a library that generalize both patty and @krux02s' library. I am also not especially attached to patty: if a better library comes out that does pattern matching, I will be happy yo deprecate it. It was just a small experiment, but it is by no means a complete solution.

@alehander92
Copy link

so I decided to test my ideas in https://github.com/alehander42/gara

@andreaferretti I got the idea for many features from patty , especially unification, I am thinking of a more powerful variant of it: e.g. support @[@x, expression(@x)] (to check expressions based on captured names), do you think this would be useful?

@timotheecour I also implemented your matches idea, but I am not sure about the API: I have a.matches(pattern) which returns bool and a.maybeMatches(pattern) which returns an option with a tuple[name: value..] with the captured names(see more details in README). I feel that's cleaner than passing a capture arg?

@narimiran narimiran transferred this issue from nim-lang/Nim Jan 2, 2019
@metagn
Copy link
Contributor

metagn commented Feb 12, 2021

Fusion pattern matching does not seem to have "custom matching" or "deconstruction", but it seems plenty sufficient for most pattern matching (Some(@x) => (isSome: true, get: @x)). If not, assigns allows for overloading unpacks based on type (matches are an abstraction over unpacks, so not as efficient), but I don't like to advertise this library because fusion matching is a lot more comprehensive and will be more stable.

@konsumlamm
Copy link

Fusion pattern matching does not seem to have "custom matching" or "deconstruction", but it seems plenty sufficient for most pattern matching.

Agreed, I think this RFC has been superseded by #245. fusion/matching implements all of the originally proposed features (recursive matching, _ placeholder and matching in if via the matches macro).

For "custom matching" or "deconstruction", most mechanisms can be customized (write procs to support object patterns, write an items iterator to support seq patterns, write a kind proc to support variant patterns, etc.)

See also nim-lang/fusion#61.

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

10 participants