Why is `sequenceOf(foo(), bar())` not an alias for `sequence { yield(foo()); yield(bar()) }`?

I was very surprised to see that in sequenceOf(foo(), bar()) both foo() and bar() actually get called when constructing the sequence, which seems counter-intuitive to me. I know I can work around this by using the construct sequence { yield(foo()); yield(bar()) } instead, but that seems very verbose. So I’m wondering, why wasn’t the former designed to simply be an alias for the latter?

In a function call, each argument is evaluated before making the call.⠀So it doesn’t matter what the implementation of sequenceOf() contains; foo() and bar() will have been called before getting there.

The difference in the second case is that the argument to sequence() is a function; evaluating the parameter gives a lambda, but does not execute it yet.

You couldn’t make the one an alias for the other without making its arguments evaluated lazily.⠀(Which would be a huge change to the language, affect interoperability and backward compatibility, and be very surprising in general.)

You could probably write your own sequenceOf()-like function which took a list of lambdas (or function references), avoiding the need for explicit yield() calls when you call it.

5 Likes

Simplest solution is:

sequenceOf({ foo() }, { bar() }).map { it() }.find { it == "foo" }

or maybe

sequenceOf(::foo, ::bar).map { it() }.find { it == "foo" }

Is it more “intuitive”? Intuition is a strange thing.

3 Likes