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

Pseudo pattern matching #109

Open
apaleslimghost opened this issue Jul 13, 2012 · 38 comments
Open

Pseudo pattern matching #109

apaleslimghost opened this issue Jul 13, 2012 · 38 comments
Labels

Comments

@apaleslimghost
Copy link
Contributor

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"
@michaelficarra
Copy link
Contributor

Cool.

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

@paulmillr
Copy link
Contributor

👍 very good idea

@vendethiel
Copy link
Contributor

+1 on that

@paulmillr
Copy link
Contributor

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/

@satyr
Copy link
Contributor

satyr commented Jul 13, 2012

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

@gkz
Copy link
Owner

gkz commented Jul 14, 2012

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

@gkz gkz closed this as completed in 3729921 Jul 14, 2012
@satyr
Copy link
Contributor

satyr commented Jul 14, 2012

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.

@adrusi
Copy link

adrusi commented Jul 14, 2012

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

@gkz
Copy link
Owner

gkz commented Jul 14, 2012

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

@apaleslimghost
Copy link
Contributor Author

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

@gkz
Copy link
Owner

gkz commented Jul 14, 2012

Some time in the next week.

@gkz gkz reopened this Jul 15, 2012
@paulmillr
Copy link
Contributor

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

@gkz
Copy link
Owner

gkz commented Jul 18, 2012

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.

@paulmillr
Copy link
Contributor

@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

@michaelficarra
Copy link
Contributor

@paulmillr: + a lot

@gkz
Copy link
Owner

gkz commented Jul 18, 2012

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

@vendethiel
Copy link
Contributor

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

@gkz
Copy link
Owner

gkz commented Jul 19, 2012

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

@vendethiel
Copy link
Contributor

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

@gkz
Copy link
Owner

gkz commented Jul 20, 2012

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.

@vendethiel
Copy link
Contributor

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
Copy link
Contributor

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

@naturalethic
Copy link

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

@vendethiel
Copy link
Contributor

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

@cocodrino
Copy link

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..."

@vendethiel
Copy link
Contributor

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.

@neersighted neersighted mentioned this issue Jan 16, 2013
11 tasks
@dy
Copy link

dy commented May 19, 2013

How to check typeof?

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

@vendethiel
Copy link
Contributor

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

@gkz
Copy link
Owner

gkz commented May 19, 2013

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

@dy
Copy link

dy commented May 19, 2013

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

@paulmillr
Copy link
Contributor

yeah, to get type of anything just do toString.call(item). toString.call(document.querySelector('body')) // => HTMLBodyElement

@gkz
Copy link
Owner

gkz commented May 19, 2013

is-type uses the typeof! operator, so yes

is-type 'HTMLBodyElement', document.query-selector 'body' #=> true

@jjmason
Copy link

jjmason commented May 31, 2013

How about scala style extractors? See http://www.scala-lang.org/node/112 for an example.

@joneshf
Copy link

joneshf commented Jul 15, 2013

Hmm, seems to have some issues with functions.

This is an error

num = -> match it
| \1 => \one

num \1 #=> it is not defined

but this works

num = -> switch it
| \1 => \one

num \1 #=> 'one'

Seems the match example is compiling to this:

var num;
num = function(){
  var ref$;
  switch (ref$ = [it], false) {
  case '1' !== ref$[0]:
    return 'one';
  }
};
num('1');

@Richiban
Copy link

It doesn't need to be pseudo pattern matching... If we use the current patterns that livescript can already use for destructuring assignments, then we could have something like this (each example is some made-up livescript followed by the JavaScript I think it should compile to. The JavaScript works):

data = [1, 2, 3, 4, 5]

print match data with
| [] -> "Data is empty"
| [head, ...tail] -> "The first element of data is {head} and there are {tail.length} more items"
| else -> "data is not an array"

print(function($arg) {
    if (Object.prototype.toString.call( someVar ) === '[object Array]' && typeof($arg[0]) === "undefined") {
        return "data is empty";
    } else if (Object.prototype.toString.call( someVar ) === '[object Array]' && typeof($arg[0]) !== "undefined") {
        var head = $arg[0];
        var tail = $arg.slice(1);

        return "The first element of data is " + head + " and there are " + tail.length + " more items";
    }
    else {
        return "data is not an array";
    }
}(data));

person = name: "John", age: 41

print match person with
| {name, age} -> "Person is called {name} and is {age} years old"
| {name} -> "Person is called {name} but has not specified their age"
| else -> "There is no one there"

var person = {name: "John", age: 41}

print(function($arg){
    var name, age;

    if (typeof(name = $arg.name) !== "undefined" && typeof(age = $arg.age) !== "undefined") {
        return "Person is called " + name + " and is " + age + " years old";
    } else if (typeof($name = $arg.name) !== "undefined"){
        return "Person is called " + name + " but has not specified their age";
    } else {
        return "There is no one there";
    }
}(person));

This is much closer to how functional languages actually use match expressions, i.e. you have a pattern (that could be used as an assignment, for example) and in each branch you test against it. If the test succeeds then you destructure and execute the given code block, otherwise you continue onto the next pattern and try to match that.

This could be used for some duck typing to test whether objects have a certain shape before you act on them.

@robotlolita
Copy link
Contributor

Sparkler is fairly interesting: https://github.com/natefaubion/sparkler

@danielo515
Copy link

Was this implemented at the end ?

@ghost
Copy link

ghost commented Jan 28, 2021

Yes. It got even updated in v.1.6.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests