https://kotlinlang.org logo
#compose
Title
# compose
e

elizarov

06/05/2019, 3:41 PM
I’d rather introduce a new primitive into the language (😱) that provides for this kind of composition but without nesting of curly braces. We run into it all the time even without compose. Instead of:
Copy code
fun foo() = logged { transactional { ... } } // (1)
Padding(color=my) { OtherWrapper { Component() } } // (2)
It would be preferable to be able to write:
Copy code
fun foo() = logged <compose> transactional { ... } // (1)
Padding(color=my) <compose> OtherWrapper <compose> Component() // (2)
The trick is what
<compose>
sequence of characters is. It is extremely hard to find a syntactic form that is readable and understandable, looks nicely, and is not ambiguous with all the current and potential future extensions of Kotlin language. Ideas are welcome, use a thread please 🧵
4
r

romainguy

06/05/2019, 3:43 PM
Obvious ones (but probably conflict with the language): ->, > and =>
k

karelpeeters

06/05/2019, 3:44 PM
Could
.
work?
function.x
never means anything right now does it?
m

miha-x64

06/05/2019, 3:44 PM
there may be a member
x
e

elizarov

06/05/2019, 3:45 PM
The only unambiguous one here is
=>
but it stylistically conflict with the rest of Kotlin and is not really obvious to the novice reader (
logged => transactional
WTF?)
k

karelpeeters

06/05/2019, 3:45 PM
Ah
function
could be an ordinary class with an
invoke
operator.
f

Fudge

06/05/2019, 3:45 PM
What if you used an infix instead of a language construct?
logged and transactional
m

miha-x64

06/05/2019, 3:45 PM
btw, I'd like functions to be delegatable.
fun foo() by logged { transactional(::someFunc) }
l

Leland Richardson [G]

06/05/2019, 3:46 PM
you could go with the haskell bind operator:
>>=
😂 1
😱 5
💪 2
or the (proposed) js pipeline operator
|>
8
it would be pretty neat if the operator implied essentially a currying of the LHS
in the case where the remaining parameter is a lambda, it behaves like the example roman had above
f

Fudge

06/05/2019, 3:49 PM
IMO the less un-google-able symbols the better, if you ask me I would even say
extends
is better than
:
😄 1
l

Leland Richardson [G]

06/05/2019, 3:50 PM
lol that double negative confused me
yeah googlability is something to consider
an infix might suffice here
e

elizarov

06/05/2019, 3:51 PM
Indeed. A infix word there is more googlable and readable, but I don’t see how it can be done unless it is already a reserved word (it will produce too ambiguous grammar)
However a symbol + word might work.
e

Eric Martori

06/05/2019, 3:53 PM
What about
using>
? It is the only unambiguous thing I can think that is abvious while reading
m

miha-x64

06/05/2019, 3:54 PM
>
? it's already ambiguous in the existing language 🙂
2
e

elizarov

06/05/2019, 3:56 PM
From the looks of it I like
|>
the most, but the problem is that in JS it does completely different thing. It subsumes Kotlin’s scoping function
let
and Kotlin’s extensions in general:
Copy code
5 |> increment // JS
5.increment() // Kt (1)
5.let(::increment) // Kt (2)
One choice that reads nicely even being a ascii symbol is
&
, but using it for composition forever bars us from adding it as a bit-wise operator.
👍 3
Some existing keywords can be abused for this composition in a non-ambiguous way. An obvious candidate is
for
. But even though it reads and parses, it is in conflict with its other meaning.
f

Fudge

06/05/2019, 4:12 PM
Could you explain a case in which using a non-keyword would be ambiguous?
m

miha-x64

06/05/2019, 4:13 PM
btw, bug fixes are way greater than a possibility to write
func <smth> expr
instead of
func { expr }
😅 1
r

Ruckus

06/05/2019, 4:32 PM
What about a
\
?
f

Fudge

06/05/2019, 4:33 PM
I would read that either as
division
or
or
, so I don't think it's a very good candidate
I'm still not sure why we can't use a soft keyword
e

elizarov

06/05/2019, 4:35 PM
@Fudge Assume you have an “infix” function
then
to write
Padding(color=my) then Component()
. But now it can be parsed as one-argument call to
Padding
, application of infix
then
with
Component
or a two-argument call to
Padding
with a second argument being lambda encapsulating
Compose()
. It is not just syntactically ambiguous, you’ll have to do call resolution twice (for one- and two- argument
Padding
) to see which one was meant here — this is something that should not be done.
@miha-x64 That is not something that is feasible in the near future, because the whole team is currently busy with bug-fixes and performance improvements to compiler.
👍 3
f

Fudge

06/05/2019, 4:39 PM
I see, thanks @elizarov
I vouch for using
&
for this then,
and
for the bitwise operation is good enough
r

romainguy

06/05/2019, 4:59 PM
I'll take bitwise operators over anything else :)
10
☝️ 1
👏 4
f

Fudge

06/05/2019, 5:01 PM
It's not like they don't exist, you can even make it one letter (like
a
) if It's too long
b

bloder

06/05/2019, 5:02 PM
I really like
|>
and
>>=
ideas for that and I see what we want here like an opposite solution of Js / Elixir pipe operator (
() -> T
instead of
(T) -> Unit
), but it still have the same principle of
|>
from JS and Elixir then I don't see problems, in the end we have the same operator that compose things.
r

romainguy

06/05/2019, 5:03 PM
@Fudge They’re not too long, they’re hard to read
👍 1
r

Ruckus

06/05/2019, 5:05 PM
Also, because they are infix functions instead of operators, they don't follow the common precedence from other languages.
👍 3
t

themishkun

06/05/2019, 5:13 PM
@bloder there is also a reverse pipe operator in F# called
<|
b

bloder

06/05/2019, 5:14 PM
yeah I was looking for a implementation in some language and I thought in <| too lol, thank you
t

themishkun

06/05/2019, 5:17 PM
@elizarov just make a way to declare a precendence for infix functions and it should work
👎 1
😱 3
e

elizarov

06/05/2019, 5:19 PM
Thanks, but no. Other people had been there, done that. We know how it ends.
t

themishkun

06/05/2019, 5:25 PM
Yet you want to break precendence for a concrete usecase instead of making a generic solution ;)
👌 1
m

miha-x64

06/05/2019, 5:26 PM
One more thing to think about. If
func <operator> expr
desugars to
func { expr }
, it makes function boxing (anonymous class generation) even more implicit.
r

Ruckus

06/05/2019, 5:36 PM
Could this cause ambiguity?
Copy code
fun foo(op: () -> Unit): () -> Unit
fun bar(op: () -> Unit)
fun bar(): Unit
fun baz(): Unit

foo <...> bar <...> baz
// Is that equal to
foo { bar { baz() } }
// or
foo { bar() } { baz() }
Or am I just confusing myself?
f

Fudge

06/05/2019, 5:36 PM
Should be the first one unless you put brackets What does the second one even mean?
e

elizarov

06/05/2019, 5:43 PM
Yes. Design is always tradeoff. On one side you have implicit lambdas (boo - scary), on the other side deeply nested curly braces (ugly and verbose).
SwiftUI went for the 3rd choice - reusing regular dot-call. It works for them, but results in inverted order of application. Now it becomes
{ .... }.transactional().logged()
r

Ruckus

06/05/2019, 5:53 PM
How deeply nested should we expect these braces to get, and is it really much of an issue? In TornadoFX, there's a fair amount of nesting for both the layout and CSS DSLs, and it hasn't really been an issue. If the nesting gets too severe, you just pull it out to a separate function.
☝️ 2
e

elizarov

06/05/2019, 5:57 PM
Even a couple “wappers” like
padding
or
logged
start to use too much precious screen space and even more dear mental parsing capacity. My personal favorite is reverse pipe
<|
. It shows the direction of “embedding” and resembled an opening brace, but it is going to be tough for novices due to its utter unfamiliarity.
I don’t think we can pull it off before kotlin is top 3 language.
However we can put Kotlin language aside and explore compose-specific solutions. The possible space is much larger here. How about this syntax:
Copy code
Padding(color=my).OtherWrapper().Component()
Is it a readable shortcut to writing it with curly braces?
b

bloder

06/05/2019, 6:18 PM
following this example should we have something like:
foo().bar().5
?
f

Fudge

06/05/2019, 6:19 PM
5
?
b

bloder

06/05/2019, 6:19 PM
something like foo(bar(5))
f

Fudge

06/05/2019, 6:20 PM
It's specifically for components
b

bloder

06/05/2019, 6:21 PM
oh I see, not a general solution to composition
f

Fudge

06/05/2019, 6:22 PM
I think Roman means something like this
Copy code
Padding(color=my).around().Column {
   Text("Hello")
}
In which case you case just use
infix
...
Copy code
Padding(color=my) around Column {
   Text("Hello")
}
e

elizarov

06/05/2019, 6:23 PM
You don’t need
around
. Dot is enough in compose context.
f

Fudge

06/05/2019, 6:23 PM
So
Copy code
Padding(color=my).Column {
   Text("Hello")
}
?
e

elizarov

06/05/2019, 6:24 PM
This solution can be generalized to some extent outside of compose, but functions will have to opt-in into this kind of use.
Yes. It can be made to mean a shortcut to:
Copy code
Padding(color=my) {
    Column {
         Text("Hello")
    }
}
I’m not sure it helps, though, but obviously SwiftUI team thought they’d need to save space and nesting for simple and common wrappers like “padding”, etc
👍 1
So it is ultimately for compose team to decide how they’d like their DSL to look like.
r

Ruckus

06/05/2019, 7:21 PM
As much as I dislike them, I can sure see how Rust style macros would provide a sleek solution here.
m

miha-x64

06/05/2019, 7:32 PM
Rust provides four types of macros 🙂
r

Ruckus

06/05/2019, 7:35 PM
Fair enough 🙂
r

Robert Menke

06/05/2019, 9:08 PM
Why not just go with the Scala approach and include something like
infix fun andThen
into the stdlib? I’m not opposed to an operator syntax, but too many operators can obviously make code confusing.
Copy code
fun pascalCase() = snakeCase andThen upperCase
Copy code
//left to right
infix fun <A, B, C> ((A) -> B).andThen(right : (B) -> C) : (A) -> C = {
    arg -> right(invoke(arg))
}

//right to left
infix fun <A, B, C> ((B) -> C).compose(right : (A) -> B) : (A) -> C = {
    arg -> invoke(right(arg))
}
e

elizarov

06/05/2019, 9:36 PM
That works when both left and right parameters to
andThen
are expressions whose type is a functional type like
(A) -> B
. Neigher
Padding(color=my)
nor
Component()
expressions in our discussion have such a type.
r

Robert Menke

06/05/2019, 9:38 PM
Oh, I misunderstood. Nevermind!
Thanks for clearing that up 🙂
e

elizarov

06/05/2019, 9:42 PM
But for “function wrappers" like logged and transactional there is a solution along these lines (not mine invention, cannot remember where I’ve seen it)
👍 1
Copy code
fun foo() = (logged + transactional) { ... }
Implementation sketch here: https://pl.kotl.in/NDME-I0LX
👍 7
l

Leland Richardson [G]

06/06/2019, 9:07 AM
that’s neat.
also this play.kotlinlang.org site is new to me
l

louiscad

06/06/2019, 9:13 AM
There's a shortcut: play.kotl.in There's also an old school version: try.kotl.in (much less mobile friendly, quite an IDE feel to it)
l

Leland Richardson [G]

06/06/2019, 9:13 AM
yeah the old school version was the one i knew about. this one feels nice 🙂