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

return more than one value from a for-comprehension #3966

Open
CeylonMigrationBot opened this issue Nov 26, 2013 · 14 comments
Open

return more than one value from a for-comprehension #3966

CeylonMigrationBot opened this issue Nov 26, 2013 · 14 comments

Comments

@CeylonMigrationBot
Copy link

[@FroMage] I know we can do that with something that flattens the resulting comprehension afterwards, but it could be useful to support "returning" more than one value from a comprehension:

assert({1, 10, 2, 20, 3, 30}, {for (i in 1..3) *{i, i*10}});

Where I reuse the * operator to spread more than one value in the comprehension.

[Migrated from ceylon/ceylon-spec#860]

@CeylonMigrationBot
Copy link
Author

[@gavinking]

I know we can do that with something that flattens the resulting comprehension afterwards

It's called expand() ;-)

but it could be useful to support "returning" more than one value from a comprehension:

Interesting. So basically you're providing flatMap() as part of the comprehension syntax. It looks pretty reasonable to me.

@CeylonMigrationBot
Copy link
Author

[@FroMage] I love how you can say expand and flatMap in the same comment without realising that expand should really be called flatten ;)

@CeylonMigrationBot
Copy link
Author

[@quintesse] Be careful with what you say, he might just rename flatMap to expandMap!

@CeylonMigrationBot
Copy link
Author

[@gavinking] Oh I realize that, but then I would have to find a new name for the arguably deeper and more basic operation that is currently called flatten().

@CeylonMigrationBot
Copy link
Author

[@gavinking] OK, so this issue us actually way more important than I had realized. For #3913, we need something almost exactly like this. Unfortunately, the syntax that makes sense for #3913 is this:

    Div {
        for (sect in sections) { 
            Title(sect.title), 
            Text(sect.text) 
        }
    }

Not, as proposed by @FroMage:

    Div {
        for (sect in sections) *{ 
            Title(sect.title), 
            Text(sect.text) 
        }
    }

But the first syntax has a huge problem. It naturally means a comprehension producing iterables! Now I'm regretting not having required an extra punctuation character in our comprehension syntax, for example:

    [ for (i in 1..10) : i^2 ]

That would have spared us from this ambiguity :-(

@CeylonMigrationBot
Copy link
Author

[@gavinking] FTR, today you can write this as:

Div {
    *expand {
        for (sect in sections) { 
            Title(sect.title), 
            Text(sect.text) 
        }
    }
}

But this involves a whole extra level of nesting, and two rather technical constructs, the *, and the expand() function.

@CeylonMigrationBot
Copy link
Author

[@FroMage] I think this will become part of a bigger debate over the syntax, because I presume people will also want to be able to do:

Div {
    for (sect in sections) { 
        Title(sect.title), 
        Text(sect.text) 
    },
    for (para in paragraphs) { 
        Title(para.title), 
        Text(para.text) 
    },
    if (needsSomething) {
      Text(something)
    }
}

@CeylonMigrationBot
Copy link
Author

[@gavinking] That's #3913 of which this is just a part.

@CeylonMigrationBot
Copy link
Author

[@gavinking] So there is a workaround, with what we have today. A framework could define a Fragment class that is essentially ignored, and is just a holder for children. Then you could write:

Div {
    for (sect in sections)
        Fragment {
            Title(sect.title), 
            Text(sect.text) 
        }
}

That's not perfect, but I think it's OK.

@CeylonMigrationBot
Copy link
Author

[@gavinking] Well, hrm, @drochetti is already way ahead of me here and he actually already supports almost the same idea in ceylon.html but he just uses Iterable, so you can write, simply:

Div {
    for (sect in sections) {
        Title(sect.title), 
        Text(sect.text) 
    }
}

The downside of that is that Iterables don't cleanly nest like Fragments do. So I can't add another nested for.

@CeylonMigrationBot
Copy link
Author

[@gavinking] A further alternative would be to put the * at the front of the comprehension. That's less intuitive, but perhaps it's easier on the eyes. Compare:

Div {
    *for (sect in sections) {
        Title(sect.title), 
        Text(sect.text) 
    }
}

With:

Div {
    for (sect in sections) *{
        Title(sect.title), 
        Text(sect.text) 
    }
}

@CeylonMigrationBot
Copy link
Author

[@gavinking] We should go with the prefix * because it avoid the appearance of irregularity between if comprehensions #3975 and if expressions #3609. I think it's also a little easier on the eyes.

@ghost
Copy link

ghost commented Aug 26, 2017

So, I've been thinking about this since I first found Ceylon, and I think I've figured out a pretty good, simple, intuitive, and regular syntax for this.

The idea is to have a syntax for both "expressions" and "multi-expressions". Consider:

comma-list:

  • (multi-expression ("," multi-expression)*)?

multi-parentheses:

  • "(" multi-expression ")"

spread:

  • "*" expression

for-comprehension:

  • "for" for-iterator multi-expression

if-comprehension:

  • "if" condition-list multi-expression

multi-expression:

  • multi-parentheses
  • expression
  • comma-list
  • spread
  • for-comprehension
  • if-comprehension

With this approach, you would be able to have a comprehension that returns multiple values by either using the spread syntax or by using parentheses. For example:

function expand<Element>({{Element*}*} streams) => {for(value stream in streams) *stream};
value div = Div{for(section in sections) (Title(section.title), Text(section.text))};

Now, as an addendum to the idea, something interesting that could be done is to allow someone to define condition lists in terms of multi-expressions. That is, consider the following extension to the syntax of multi-expression:

multi-expression:

  • is-condition
  • exists-condition
  • nonempty-condition
  • "!" multi-expression

And now look at how simple the syntax for condition-list becomes:

condition-list:

  • "(" multi-expression ")"

This also helps fix the problem posed in #4540, by simply allowing people to write the following:

if(!(exists foo, exists bar, hours < 24))
{
	return(null);
}

return(foo + bar + hours);

It's interesting to note that those conditions would still be only allowed inside if, assert, and while, but this restriction would be done by the typechecker, not the parser. That is, using prefix exists where an expression is expected would be syntactically incorrect, while using them where a multi-expression is expected (but outside a condition list) would be semantically incorrect.

value foo = exists bar; // syntax error
value foo = [exists bar]; // semantical error

@jvasileff
Copy link
Contributor

+1 for figuring this out. When I get a chance, I'll link to real world code examples where this would help.

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

3 participants