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

Pattern matching to-do #335

Open
11 of 14 tasks
edemaine opened this issue Feb 5, 2023 · 9 comments
Open
11 of 14 tasks

Pattern matching to-do #335

edemaine opened this issue Feb 5, 2023 · 9 comments

Comments

@edemaine
Copy link
Collaborator

edemaine commented Feb 5, 2023

(transcribing these from Discord)

  • Empty array pattern []
  • Implicit return:
    function categorize(x)
      switch x
        1
          'one'
  • Expression form:
    y :=
      switch x
        1
          'one'
  • Multiple use of same property makes duplicate const:
    switch x
      [{type: "text"}, {type: "image"}]
        x
  • Use of keyword class makes invalid const:
    switch x
      {class: 'hi'}
        x
  • Aliasing matches destructuring, pinning
    switch x
      {a: b}
        b
      {a: ^b}
        a
      [a, ^b]
        a
  • instanceof:
    switch x
      <? ArrayBuffer
      instanceof ArrayBuffer
      <? "string"
      instanceof "string"
  • Other binary operators like comparisons:
    switch x
      < 5
  • Multiple simple patterns:
    switch x
      "hi", "hello"
        console.log "greeting"
  • Multiple patterns with newline after comma (from What to do when regexp in switch exceeds (desired) length of line? #831)
    switch x
      "hi", 
      "hello"
        console.log "greeting"
  • Multiple binding patterns
switch p
  [x, y], {x, y}
    ...
  • Positional pins should allow expressions
    switch x
      [^foo(y)]
  • Optional properties (similar to TypeScript and JS destructuring):
    switch x
      {foo?, bar}
  • Exact object types (similar to Flow, but not necessarily with that notation):
    switch x
      {| |}        // empty object
      {| foo |}    // must have exactly one property
    // or
    switch x
      ={}        // empty object
      ={foo}    // must have exactly one property
@edemaine edemaine added bug Something isn't working and removed bug Something isn't working labels Feb 5, 2023
@edemaine edemaine changed the title Pattern matching bugs Pattern matching to-do Feb 6, 2023
@STRd6
Copy link
Contributor

STRd6 commented Feb 15, 2023

For class and multiple of the same keys we should have some way of aliasing but I'm not sure of the best syntax for it yet.

@edemaine
Copy link
Collaborator Author

edemaine commented Feb 15, 2023

Perhaps {class: klass === "hi"} or {class: klass is "hi"}? Eh, maybe that looks like an expression that should be evaluated and passed in as the class value to match.

I think we also need to improve the default behavior when there are duplicate or keyword names. Some options:

  1. Keyword names could get an automatic _ or 1 suffix or something.
  2. Just the first instance of a name could get a variable.
  3. First instance gets a 1 suffix, second gets a 2 suffix, etc.
  4. Name gets assigned an array of values (but this changes substantially between between one and multiple instances of the name). [as you've suggested in Discord]
  5. Name with suffix of 0 gets an array of matches, like how in Hera $0 is [$1, $2, ...].

I also feel like the most common case of duplicate names is when we have bound them to constants already. Something that would help in this sort of scenario is to not actually bind variables if they don't get used in the scope of the match. This would also pair nicely with Option 5 above; we'd like to build this array only if it gets used.

@edemaine
Copy link
Collaborator Author

#375 implemented Option 4 above, which I agree seems like a good choice.

A simple workaround for class and other keywords, at least for now, would be to just not bind those (or use a Ref that the user can't access). At least then the code would compile.

STRd6 added a commit that referenced this issue Feb 23, 2023
Allow pattern matching for reserved word keys by binding to inaccessible refs.

Link: #335
@STRd6 STRd6 mentioned this issue Feb 23, 2023
STRd6 added a commit that referenced this issue Feb 23, 2023
Allow pattern matching for reserved word keys by binding to inaccessible refs.

Link: #335
@eevleevs
Copy link

What about adding the same functionality as in #444? To allow something like:

switch a
  m := /(\d)/
    return m[1]

@JUSTIVE
Copy link

JUSTIVE commented Nov 27, 2023

any plans for guard clauses other than simple binary operators?

@edemaine
Copy link
Collaborator Author

edemaine commented Nov 27, 2023

@JUSTIVE Yes! I don't think we've settled on a notation yet, but I also may be forgetting a proposal. One obvious thing to allow would be & shorthand functions:

switch x
  & > 0        // equivalent to just "> 0"
  isCool(&)    // lets you invoke an arbitrary function

The latter is assuming something like #480 gets implemented, so f(&)/f & is shorthand for $ => f($).

If we just want to write a function condition, we need to somehow distinguish from a regular identifier. For example, [isCool] already has a meaning: array of length 1, where the element gets named isCool. Maybe there would be another notation to denote "needs to match the guard of this function". Let us know if you have ideas.

@JUSTIVE
Copy link

JUSTIVE commented Nov 28, 2023

Great to hear that! I just needed the latter one (invoking a function), and the isCool(&) notation looks fine.
I'm not that familiar with the civet syntax(Just tried it yesterday), so It's not very appropriate for me to suggest an idea, but the array-like syntax looks very exotic. Does Civet's design towards to point-free style? if so, how about just leave 'isCool` alone?

switch x
  isCool(&)
    "Something"
  isCool         //equivalent to above
     "Nothing"

@edemaine
Copy link
Collaborator Author

The trouble is that a pattern of just isCool is ambiguous. It could mean:

  1. Equality to another variable isCool in a higher scope (e.g. global). We currently write this as ^isCool.
  2. Whether isCool(x) returns true. I'm proposing writing this as isCool(&).
  3. Always match (like else), assigning a new name isCool to x. (This is potentially useful if x is a complex expression.)

You're arguing for option (2). If you'd asked me, I would have guessed that we already implemented (3), as it's what's symmetric with the current meaning of [isCool] for example. In fact, there's currently no meaning, and given the ambiguity, that might be best...?

@JUSTIVE
Copy link

JUSTIVE commented Sep 19, 2024

Came back to this thread, about a year later. Been using the Civet lightly with joy so far, and I'm writing a post introducing the Civet in my language now, by comparing its features against alternatives (raw js, and typescript with ts-pattern).

The trouble is that a pattern of just isCool is ambiguous. It could mean:

  1. Equality to another variable isCool in a higher scope (e.g. global). We currently write this as ^isCool.
  2. Whether isCool(x) returns true. I'm proposing writing this as isCool(&).
  3. Always match (like else), assigning a new name isCool to x. (This is potentially useful if x is a complex expression.)

You're arguing for option (2). If you'd asked me, I would have guessed that we already implemented (3), as it's what's symmetric with the current meaning of [isCool] for example. In fact, there's currently no meaning, and given the ambiguity, that might be best...?

Sorry for forgetting to reply, I was asking for option 2 then, but now I see why it's difficult to do. I was naively thinking 'What if the matching symbol's type is a function, why not let it apply the value?'. and of course, just assumed that 'The function would be a predicate function'. isCool(&) and match when evaluated value is truthy/falsy seems pretty legit to me now.

back to my writings, while comparing with ts-pattern, the Civet's pattern matching also lacks some complicated guard clause, just like F# and ts-pattern. Maybe sounds familiar as old comments above, but I'm imagining something like this:

/* just an option type */
type some<T> = { __type: "some", value: T }
type none = { __type: "none" }
type option<T> = some<T> | none

const grade = 
  (score: option<number>) =>
    switch score
      { __type: "some", value } if value > 80
        "A" 
      { __type: "some", value } if value > 60
        "B"
      { __type: "some", value } if value > 40
        "C"
      { __type: "some", value } 
        "E"
      { __type: "none" }
        "F"

the point is, having some conditional expression next to the pattern to narrow down

the example might be inappropriate since the type variant is just some and none, so here's a better one.

const fruitQualifyingResult  = 
  (value: Fruits) =>
    switch value
      { __type : "Apple", weigth } if weight > 500
      { __type : "Banana", length } if length > 15
      { __type : "WaterMelon", sweetness } if sweetness > 16
        { __type : "Ok", value }
      else
        { __type : "No", value }

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

4 participants