Skip to content

Add "pattern" scope type #2177

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

Open
wenkokke opened this issue Jan 17, 2024 · 15 comments
Open

Add "pattern" scope type #2177

wenkokke opened this issue Jan 17, 2024 · 15 comments

Comments

@wenkokke
Copy link

wenkokke commented Jan 17, 2024

I propose we add a scope type for the patterns in pattern matches, which are very important in functional languages like Haskell, Scala, and Rust, and increasingly important in languages such as JavaScript (see tc39) and Python (see pep 636).

// TypeScript
function fst<A, B>(tup: [A, B]) {
  const { x, y } = tup;
  //    ^^^^^^^^
  return x;
}
// Using tc39
function fst<A, B>(tup: [A, B]) {
  match (tup) {
    when ({ x, y }): return x;
    //    ^^^^^^^^
  }
}
# Python
def fst(tup):
  match tup:
    case (x, y): return x
#        ^^^^^^
// Scala
def fst[A, B](tup:  (A, B)): A =
  tup match {
    case (x, y) => x
    //   ^^^^^^
  }
-- Haskell
fst :: (a, b) -> a
fst (x, y) = x
--  ^^^^^^
// Rust
fn fst(tup: (A, B)) {
  match tup {
    (x, y) => x
//  ^^^^^^
  }
}

Patterns are different from conditions. The easiest way to show this is by implementing the if then else construct using patterns.

def ifThenElse(cond, ifTrue, ifFalse):
  match cond:
#       ^^^^  <-- "condition"
    case True:
#        ^^^^  <-- "pattern"
      return ifTrue()
    case False:
#        ^^^^^  <-- "pattern"
      return ifFalse()

Patterns are not adequately covered by branches, since "branch" selects the entire branch, which may match on multiple patterns.

and ::  Bool -> Bool -> Bool
and True True = True
--  ^^^^^^^^^^^^^^^^  <-- branch
--  ^^^^  <-- pattern
--       ^^^^  <-- pattern
and _    _    = False
--  ^^^^^^^^^^^^^^^^^ <-- branch
--  ^  <-- pattern
--       ^  <-- pattern

This makes it impossible to use "branch" to select, e.g., the first pattern in the second branch.

See related issues #579 and #1174.

@wenkokke
Copy link
Author

wenkokke commented Jan 17, 2024

@pokey wrote here that the patterns should be addressed by "name", "key", or "condition".

I've argued above that "condition" should refer to the "subject" and that its use for patterns is confusing.

The complexity of patterns makes it rather odd to refer to them by "name" or "key". That suggestion may have been due to the overly simplistic nature of the examples. For instance, see the following Haskell program, where I've annotated the top-most patterns. At very least, I hope that shows that "name" is inadequate?

someFunction x (y1 : y2 : ys) (a, b, (c, [d])) = rhs
--           ^  ^^   ^^   ^^   ^  ^   ^   ^    <-- names
--           ^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ <-- top-level patterns
--              ^^   ^^^^^^^   ^  ^^^^^^^^^^^  <-\ 
--                   ^^   ^^      ^  ^^^^^^^^  <-| sub-patterns
--                                    ^  ^^^   <-|
--                                        ^    <-/

Note: The program looks contrived, due to the fact I've copied it from a parser test, but patterns like these are extremely common in Haskell. Sometimes they use better names.

@pokey
Copy link
Member

pokey commented Jan 18, 2024

my thinking would be

someFunction x (y1 : y2 : ys) (a, b, (c, [d])) = rhs
--           ^ ^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^ <-- "arg"
--              ^^   ^^   ^^   ^  ^  ^^^^^^^^  <-\
--                                    ^  ^^^   <-| "item"
--                                        ^    <-/

@wenkokke
Copy link
Author

I'm not sure if item makes sense in the general case?

either                  :: (a -> c) -> (b -> c) -> Either a b -> c
either f _ (Left x)     =  f x
either _ g (Right y)    =  g y

Are the x and y on the left hand side still item?

@wenkokke
Copy link
Author

@pokey What do you think should be used in the JavaScript, Python and Rust examples?

@wenkokke
Copy link
Author

These aren't value because they're binding occurrences rather than values, so the use cases, domain and iteration scopes are very different.

These aren't key or item because they're not always in a list or map like context, and furthermore because they're binders rather than items in a list.

@wenkokke wenkokke changed the title Add scope type "pattern" Add scope type "pattern" Jan 18, 2024
@wenkokke wenkokke changed the title Add scope type "pattern" Add "pattern" scope type Jan 18, 2024
@pokey
Copy link
Member

pokey commented Jan 18, 2024

I'm not sure if item makes sense in the general case?

either                  :: (a -> c) -> (b -> c) -> Either a b -> c
either f _ (Left x)     =  f x
either _ g (Right y)    =  g y

Are the x and y on the left hand side still item?

no I think I'd call those "arg"?

@pokey
Copy link
Member

pokey commented Jan 18, 2024

we tend to use the same scope name whether it's constructing or deconstructing. makes it easier for the ol' lizard brain that just sees shapes

@pokey
Copy link
Member

pokey commented Jan 18, 2024

@pokey What do you think should be used in the JavaScript, Python and Rust examples?

I believe they're as follows, but easiest to paste them into a file, pull up the "bar cursorless" sidebar and click through the scopes to see what hits. Tho I'm guessing we don't support the tc39 one

// TypeScript
function fst<A, B>(tup: [A, B]) {
  const { x, y } = tup;
  //    ^^^^^^^^  <~~ name
  return x;
}
// Using tc39
function fst<A, B>(tup: [A, B]) {
  match (tup) {
    when ({ x, y }): return x;
    //    ^^^^^^^^ <~~ condition
  }
}
# Python
def fst(tup):
  match tup:
    case (x, y): return x
#        ^^^^^^ <~~ condition

@wenkokke
Copy link
Author

I'm not sure if item makes sense in the general case?

either :: (a -> c) -> (b -> c) -> Either a b -> c

either f _ (Left x) = f x

either _ g (Right y) = g y

Are the x and y on the left hand side still item?

no I think I'd call those "arg"?

So it's nested args all the way down? Because (Left x) is also "arg".

@pokey
Copy link
Member

pokey commented Jan 19, 2024

So it's nested args all the way down? Because (Left x) is also "arg".

In this case, yes, but note that in #2177 (comment) there's no "arg" nesting

@wenkokke
Copy link
Author

True, but item is inappropriate there, so those cases would also be "arg".

@pokey
Copy link
Member

pokey commented Jan 19, 2024

item is inappropriate there

can you elaborate?

@wenkokke
Copy link
Author

item is inappropriate there

can you elaborate?

If

foo x = undefined
--  ^ arg

and

foo (Left x) = undefined
--        ^ arg

then

foo (x : xs) = undefined
--   ^ arg

and

foo [x] = undefined
--   ^ arg

because they're all the same kind of thing.

@wenkokke
Copy link
Author

wenkokke commented Jan 22, 2024

One major use case for this, the overlapping iteration scopes between formal and actual arguments, might be fixed by separating @argument.formal and @argument.actual:

--            vvv argument.actual.iteration
--              v argument.actual
--            v argument.actual
--          v argument.actual
foo f x y = f x y
--  ^ argument.formal
--    ^ argument.formal
--      ^ argument.formal
--  ^^^^^^^^^^^^^ argument.formal.iteration

@pokey
Copy link
Member

pokey commented Jan 23, 2024

interesting. maybe let's discuss this one in meet-up?

@pokey pokey added the to discuss Plan to discuss at meet-up label Jan 23, 2024
@AndreasArvidsson AndreasArvidsson removed the to discuss Plan to discuss at meet-up label Jan 24, 2025
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

3 participants