Skip to content
This repository has been archived by the owner on Apr 13, 2023. It is now read-only.

switch on tuple #4127

Closed
CeylonMigrationBot opened this issue Jul 13, 2014 · 57 comments
Closed

switch on tuple #4127

CeylonMigrationBot opened this issue Jul 13, 2014 · 57 comments

Comments

@CeylonMigrationBot
Copy link

[@gavinking] We still don't have any real plans to support genuine pattern matching, however one small thing that could I guess make some sense would be to allow tuple expressions in a case, for example:

switch(tuple)
case ([0,false]) { ... }
case ([1,true]) { ... }
case ([1,false]) { ... }
else { ... }

This would only be for tuples of types that we already allow in cases.

[Migrated from ceylon/ceylon-spec#1021]

@CeylonMigrationBot
Copy link
Author

[@akberc] +1

@CeylonMigrationBot
Copy link
Author

[@gavinking] In light of #3626, this feature amounts to a sort of pattern matching, for example:

switch (pair)
case ([0.0,y]) { .... }
case ([x, 0.0]) { .... }
else case ([x,y]) { .... }

@CeylonMigrationBot
Copy link
Author

[@guai] mixing destructuring with values is awful.
and cover only primitive cases - tests for equality.
pls consider explicit condition after destructuring:
switch(pair) {
case([x,y]) when x=0 {...}
case([x,y]) when x>y {...}
}

@CeylonMigrationBot
Copy link
Author

[@gavinking]
@guai I don't see how this:

switch (pair)
case ([x,y]) when x=0 {...}
case ([x,y]) when x>y {...}

is better than this:

switch(pair)
case ([x,y]) {
    if (x==0) { ... }
    else if (x>y) { ... }
}

The second way has fewer tokens and less repetition.

Indeed, it seems to me that arbitrary boolean conditions like when x>y necessarily undermines the whole semantics of switch, which is that the individual cases are disjoint. The compiler can't statically prove that y>=0.

@CeylonMigrationBot
Copy link
Author

[@guai] SomeObj z = SomeObj();
switch(pairOrTrinity){
case([x,y]) when x==z {//compare to z variable}
case([x,y,z]) {//destructure hide z variable}
}
indeed not much difference to ifs. And that is my point. With ifs its obvious where there is destructuring and where is equaluty checks. In my variant too. First type check + destructure, then compare.
If they are mixed then it is suitable only for primitive values or else indestinguishable from each other

@CeylonMigrationBot
Copy link
Author

[@guai] and also if condition check is part of switch statement, then compiler can require else statement [in cases other then someCheck(a) along with !someCheck(a) for pure functions]

@CeylonMigrationBot
Copy link
Author

[@gavinking] @guai Well, y'know, I've also often questioned the value of pattern matching, but people keep telling me it's the greatest thing ever. It doesn't seem quite that great to me, but I feel like I must be missing something...

@CeylonMigrationBot
Copy link
Author

[@guai] Gavin, do you plan to make switch an "assert that intersection of all cases always true" statement or live it like in java just kinda alias for if-elseif-elseif?

@CeylonMigrationBot
Copy link
Author

[@gavinking] In Ceylon, switches are always exhaustive (they must cover all cases of the switched expression) and all cases are mutually disjoint. They're almost nothing like a switch in Java. Even the syntax is different.

@gavinking
Copy link
Contributor

In 909a74e I have partially implemented this. That is, I have implemented support for the kind of destructuring we already allow in other syntactic locations. I have not implemented what is described in the original issue description where patterns are allowed to contain literals. Of course that's an obvious and pretty natural extension to this.

Anyway, it's enough to make stuff like the following work:

    Person|Org whatever = ... ;

    switch (tuple = whatever.destructured)
    case ([String name, Person[] employees]) {
        ...
    }
    case ([String name, Integer age]) {
        ...
    }

That's still not "true" pattern matching, but boy is it ever close!

@quintesse
Copy link
Contributor

Just to be sure: the whatever.destructured returns the fields of the object as a tuple or something like that?

@gavinking
Copy link
Contributor

@quintesse you need this useful interface:

interface Struct<T> 
        satisfies Identifiable
        given T satisfies Anything[] {
    shared formal T destructured;
    shared actual default Boolean equals(Object that) {
        if (is Struct<out Anything[]> that) {
            return 
                //type(that).declaration==type(this).declaration
                className(this)==className(that)
                && destructured==that.destructured;
        }
        else {
            return false;
        }
    }
    shared actual default Integer hash => destructured.hash;
    shared actual default String string => 
            "``className(this)``(``destructured``)";
}

And then Person and Org are just:

class Person(shared String name, Integer age) 
        satisfies Struct<[String,Integer]> {
    destructured = [name, age];
}

class Org(String name, Person* employees) 
        satisfies Struct<[String,Person[]]> {
    destructured = [name, employees];
}

@gavinking gavinking modified the milestones: 1.3.1, 1.4 Sep 12, 2016
@gavinking
Copy link
Contributor

gavinking commented Sep 12, 2016

I plan to work on this for 1.3.1. Supporting purely-literal tuples is trivial, so we should at least implement that. Mixing literals with pattern variables is a bit more complicated but we can take a look at that, too, and see how hard it is.

@welopino
Copy link

welopino commented Oct 6, 2016

When mixing literals with typed variables in patterns, types will depend on values. It is certainly possible to implement flow sensitive type narrowing that takes values into account. But does it help the user? Flow sensitive typing is meant to reduce cognitive complexity. But when in a multi-dimensional tuple types depend on values this might be harder to unterstand than the present implementation: First match on types, then on each value dimension of a tuple using nested switches.

Why not implementing different flavors of pattern matching and unification like clojure' s core.logic as library exploiting meta language features of ceylon. Then it would be desirable to have symbol literals instead of enumerated objects.

@gavinking
Copy link
Contributor

@welopino TBH, it's not really a feature I even miss, but it is one that languages with pattern matching offer, and that people seem to like. Anyway, I don't think we'll do it immediately. For now I think we should just support literal tuples and entries as switch cases.

@gavinking
Copy link
Contributor

Supporting purely-literal tuples is trivial, so we should at least implement that.

I've made a good start on the typechecker side of this.

@jvasileff
Copy link
Contributor

I assume the program below should print "fallback", but it might be confusing. (Or, is the intention to match any List using ==? But then, pattern variables would not be possible.)

shared void run() {
    void process([String*] strings) {
        switch (strings)
        case (["a", "b"]) {
            print("matched");
        }
        else {
            print("fallback");
        }
    }
    process({"a", "b"}.sequence());
}

@gavinking
Copy link
Contributor

Wow, @jvasileff, that's a great point, and unbelievably it simply had not occurred to me to consider plain Lists that aren't Sequences.

Or, is the intention to match any List using ==?

Yeah, now that you mention it, that would probably be best. It's what this syntax means in all other languages, and value case matching is certainly supposed to align with ==.

@lucaswerkmeister
Copy link
Contributor

I don’t understand, why would that print “fallback”? I don’t see the catch. Is it that {"a", "b"}.sequence() is just a String[] and not a [String,String]?

@jvasileff
Copy link
Contributor

A gotcha here is that for case ([]) the type test needs to be for Empty, not Tuple. Unless, @gavinking, the typechecker should disallow this in favor of case (is []).

@jvasileff
Copy link
Contributor

@chochos I noticed this test fails on the JS backend due to the [] issue:

shared void run() {
    function f([String*] strings)
        =>  switch(strings)
            case ([])   "empty"
            case (["a", "b"]) "ab"
            else "other";

    assert (f([]) == "empty"); // this one
    assert (f(["a", "b"]) == "ab");
    assert (f(["c", "d"]) == "other");
}

@gavinking
Copy link
Contributor

I think it would be weird and irregular to not allow case([]).

@chochos
Copy link
Contributor

chochos commented Oct 17, 2016

so what should case([]) do? Just test for emptiness, or check that its type is actually Empty?

@gavinking
Copy link
Contributor

@chochos [] is a unit type with the singleton value [].

chochos added a commit that referenced this issue Oct 17, 2016
chochos added a commit that referenced this issue Oct 17, 2016
@gavinking
Copy link
Contributor

@chochos @tombentley can I merge this branch now?

@chochos
Copy link
Contributor

chochos commented Oct 18, 2016

fine by me

@tombentley
Copy link

Can I implement it first?

On 18 Oct 2016 3:42 a.m., "Enrique Zamudio" notifications@github.com
wrote:

fine by me


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
#4127 (comment), or mute
the thread
https://github.com/notifications/unsubscribe-auth/AA1rf1ehONupYM9QRTh2_nXVv2-_m-Mfks5q1DIAgaJpZM4H52Jy
.

@gavinking
Copy link
Contributor

Can I implement it first?

Please :-)

@tombentley
Copy link

Struggling to see exactly what I need to do here.

  • Adding support for tuple literals in case
  • It matches when the selector is a Tuple (or Empty) (it doesn't match other Sequential)
  • We use .equals() to compare the elements?
  • That is, there's no combining this with destructuring: case(["", x])

Is that right?

@gavinking
Copy link
Contributor

  • Adding support for tuple literals in case

Right.

  • It matches when the selector is a Tuple (or Empty) (it doesn't match other Sequential)

Right (for now).

  • We use .equals() to compare the elements?

No: use .equals() to compare the whole tuple.

  • That is, there's no combining this with destructuring: case(["", x])

Right.

tombentley added a commit that referenced this issue Oct 18, 2016
tombentley added a commit that referenced this issue Oct 18, 2016
@tombentley
Copy link

This should now be working on the JVM

@gavinking
Copy link
Contributor

Excellent, lets merge it!

Sent from my iPhone

On 18 Oct 2016, at 13:43, Tom Bentley notifications@github.com wrote:

This should now be working on the JVM


You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub, or mute the thread.

@gavinking
Copy link
Contributor

Alright, let's close this for now, I will open little issues if I run into bugs.

Thanks everyone!

@jvasileff
Copy link
Contributor

@tombentley I didn't test it, but it looks like the [] case might match anything that returns true for equals

@gavinking
Copy link
Contributor

@tombentley I didn't test it, but it looks like the [] case might match anything that returns true for equals

Depends how precisely it's written. If it's written as if (empty.equals(val)) then that can't be spoofed, because we control the definition of empty.equals(). (It's === FTR.)

@gavinking
Copy link
Contributor

Depends how precisely it's written. If it's written as if (empty.equals(val)) then that can't be spoofed, because we control the definition of empty.equals(). (It's === FTR.)

OMG, that's total nonsense. empty inherits equals() from List. My bad!

Then we should do it as a type test instead, @chochos, being equivalent to is [].

@chochos
Copy link
Contributor

chochos commented Oct 18, 2016

I had implemented it with === comparison

@tombentley
Copy link

I now do an instanceof Empty.

@gavinking
Copy link
Contributor

=== is fine, I guess.

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

No branches or pull requests

8 participants