LiveScript icon indicating copy to clipboard operation
LiveScript copied to clipboard

Pseudo pattern matching

Open apaleslimghost opened this issue 13 years ago • 38 comments

It'd be cool if we had a match statement, which ran its operand against the functions in its cases. For example:

console.log match x
  | is-NaN =>  "not a number"
  | is-finite => "not infinity"
  | otherwise => "something else"

apaleslimghost avatar Jul 13 '12 17:07 apaleslimghost

Cool.

console.log match x
  | (instanceof SomeClass) =>  "SomeClass"
  | (instanceof AnotherClass) => "AnotherClass"
  | otherwise => "something else"

michaelficarra avatar Jul 13 '12 18:07 michaelficarra

:+1: very good idea

paulmillr avatar Jul 13 '12 18:07 paulmillr

+1 on that

vendethiel avatar Jul 13 '12 18:07 vendethiel

And we can add something more sophisticated to it when there will be static type checking

http://kerflyn.wordpress.com/2011/02/14/playing-with-scalas-pattern-matching/

paulmillr avatar Jul 13 '12 18:07 paulmillr

console.log match x,
  is-NaN    => "not a number"
  is-finite => "not infinity"
  otherwise => "something else"

where:

match = (x, ...cases) ->
  for c, i in cases by 2
    return cases[i+1] if c x
  cases[i-1]

otherwise = -> true

satyr avatar Jul 13 '12 22:07 satyr

@satyr if the results have side effects, that won't work

gkz avatar Jul 14 '12 02:07 gkz

if the results have side effects, that won't work

It should accept functions as results for a real implementation. Makes for a good prelude addition I guess.

satyr avatar Jul 14 '12 02:07 satyr

If it does get done, this would be a good use for pure function annotations

adrusi avatar Jul 14 '12 05:07 adrusi

I added some more features. You can use it with no subject at all, or multiple arguments.

# no subject
false-func = -> false
true-func  = -> true
match
| false-func => ok 0
| true-func  => ok 1
| otherwise  => ok 0

# multiple arguments
x = 1
y = 2
match x, y
| (==) => ok 0
| (>)  => ok 0
| (<)  => ok 1
| _    => ok 0

gkz avatar Jul 14 '12 09:07 gkz

Dude, that's awesome. Any ETA on 0.9.13/0.10?

apaleslimghost avatar Jul 14 '12 22:07 apaleslimghost

Some time in the next week.

gkz avatar Jul 14 '12 22:07 gkz

imo it should work when var defs are located after newline after where too

quick-sort = ([x, ...xs]:list) ->
  | empty list  => []
  | otherwise   => (quick-sort left) +++ [x] +++ (quick-sort right) where
    [right, left] = partition (<= x), xs

paulmillr avatar Jul 15 '12 23:07 paulmillr

All hail our new match statement overlords!

take = (n, [x, ...xs]:list) -->
  match n, list
  | (<= 0), _  => []
  | _     , [] => []
  | otherwise  => [x] +++ take n - 1, xs

take 3, [1 to 10]  #=> [1, 2, 3]

also

match 1, 2
| odd,  odd  => ok 0
| even, even => ok 0
| odd,  even => ok 1
| otherwise  => ok 0

also

x = [1 2 3]
y = 'haha'
z = {+foo, moo: 2, g: {hi: \?}}
match x, y, z
| [1 2 3], /^ha/g, {foo: true, moo: 2, g: {hi: \!}} => ok 0
| [1 2 3], /^ha/g, {foo: true, moo: 2, g: {hi: \?}} => ok 1
| otherwise                                         => ok 0

etc.

gkz avatar Jul 18 '12 21:07 gkz

@gkz how about making this a main matching syntax in function declarations instead of current switch?

take = (n, [x, ...xs]:list) -->
  | (<= 0), _  => []
  | _, []      => []
  | otherwise  => [x] +++ take n - 1, xs

paulmillr avatar Jul 18 '12 21:07 paulmillr

@paulmillr: + a lot

michaelficarra avatar Jul 18 '12 22:07 michaelficarra

I was thinking about that too. I think I'll need to think more about the consequences of it before going ahead.


Changes I made to make the above possible - feel free to discuss

  • added deep equal, based off of underscore.js's isEqual, to compare objects and arrays. eg. a === b, a <== b, a <<= b etc. adds a huge ass util func
  • == when one of the operands is a regex literal does .exec, (allowing use of that for results), != compiles to .test
  • binary logic is callable, eg. (f or g) x is equal to (f x or g x) - also does deepEq or other stuff as necessary
  • added xor operator, ie exclusive or, is true when one but not the other operand is true, evaluates to the one that is true
  • and more stuff check the last ~16 commits

gkz avatar Jul 18 '12 22:07 gkz

Woah, woah, woah. A little harsh for me !

take = (n, [x, ...xs]:list) -->
  match n, list
  | (<= 0), _  => []
  | _     , [] => []
  | otherwise  => [x] +++ take n - 1, xs

take 3, [1 to 10]  #=> [1, 2, 3]

Really took me quite time to understand that "_" are used here to say "whatever for this value"

match 1, 2
| odd,  odd  => ok 0
| even, even => ok 0
| odd,  even => ok 1
| otherwise  => ok 0

erm ... does the following still works ?

sortBy = (prop, list) -->
  list.sort (a, b) -> match a[prop], b[prop]
    | (<) => -1
    | (>) => 1
    | otherwise => 0

vendethiel avatar Jul 18 '12 23:07 vendethiel

No, the multiple arguments thing was dropped instead for multiple simultaneous checks.

gkz avatar Jul 19 '12 18:07 gkz

not sure I completely agree with that. I think I prefer to write nested switch, because the reading of the match / the cases becomes too messy really easily. I'll have to look "ok, so the comma is here, the 4 is to test against the b, and ..." -1 for this, especially with the implicit functions

vendethiel avatar Jul 19 '12 20:07 vendethiel

I don't think I'll make match the default because match is not as stable (may change in the future), is more complex, and has a messier compilation.

gkz avatar Jul 20 '12 02:07 gkz

Isn't it possible to add back the "multiple arguments" when match arglist's length > case arglist's length, allowing the following possible

match iA, iB
| (==) => "Equal int"
| (>) => "A gt B"
| odd, even => "a odd and b even"

The rule would be simple : if we have a/some missing case(s), then it's/they're argument(s) for the prev case. I.e. :

match a, b, c
| test => 'called test(a, b, c)'
| test, tryndie => 'called test(a) and tryndie(b, c)'
| foo, _, _ => 'called foo(a)'

I don't know if that'd be possible without creating some slow/ugly, but I think that'd bring the best of both worlds.

vendethiel avatar Jul 22 '12 23:07 vendethiel

Talking about #123 (yay nice number), would you consider moving this to "switch" ?

x = [1 2 3]
y = 'haha'
z = {+foo, moo: 2, g: {hi: \?}}
switch x, y, z
| [1 2 3], /^ha/g, {foo: true, moo: 2, g: {hi: \!}} => ok 0
| [1 2 3], /^ha/g, {foo: true, moo: 2, g: {hi: \?}} => ok 1
| otherwise                                         => ok 0

Since it isn't a call in any way

vendethiel avatar Jul 26 '12 02:07 vendethiel

Possible ideas to rip off https://github.com/natefaubion/matches.js

naturalethic avatar Sep 13 '12 17:09 naturalethic

currently, match a, b case foo => 1 will call foo([a, b]). Is it working as intended ? shouldn't it be foo(a, b) ?

vendethiel avatar Nov 15 '12 21:11 vendethiel

george would be possible do destructuring with pattern matching n=[1,2,3,4] match n, list | [a,,,b] => "first is #a last is #b long: 4" | otherwise => "this is not an useful pattern matching..."

cocodrino avatar Dec 19 '12 21:12 cocodrino

Except match n.0, n.1, n.2, n.3 I don't see how that'd work anyway

"this is not an useful pattern matching..."

Oh yeah, totally not. right.

vendethiel avatar Dec 19 '12 22:12 vendethiel

How to check typeof?

a = "abc"
match a
| (typeof a is "string") => 1 #doesn’t work
| _ => 2

dy avatar May 19 '13 10:05 dy

(typeof) >> (is "string"), or simply (-> typeof it is "string")

vendethiel avatar May 19 '13 10:05 vendethiel

in the new prelude.ls we have is-type http://preludels.com/#is-type

gkz avatar May 19 '13 23:05 gkz

Is there possibility to check another types, like function, date, regexp, element etc?

dy avatar May 19 '13 23:05 dy