I’d rather introduce a new primitive into the lang...
# compose
e
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
Obvious ones (but probably conflict with the language): ->, > and =>
k
Could
.
work?
function.x
never means anything right now does it?
m
there may be a member
x
e
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
Ah
function
could be an ordinary class with an
invoke
operator.
f
What if you used an infix instead of a language construct?
logged and transactional
m
btw, I'd like functions to be delegatable.
fun foo() by logged { transactional(::someFunc) }
l
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
IMO the less un-google-able symbols the better, if you ask me I would even say
extends
is better than
:
😄 1
l
lol that double negative confused me
yeah googlability is something to consider
an infix might suffice here
e
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
What about
using>
? It is the only unambiguous thing I can think that is abvious while reading
m
>
? it's already ambiguous in the existing language 🙂
2
e
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
Could you explain a case in which using a non-keyword would be ambiguous?
m
btw, bug fixes are way greater than a possibility to write
func <smth> expr
instead of
func { expr }
😅 1
r
What about a
\
?
f
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
@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
I see, thanks @elizarov
I vouch for using
&
for this then,
and
for the bitwise operation is good enough
r
I'll take bitwise operators over anything else :)
10
☝️ 1
👏 4
f
It's not like they don't exist, you can even make it one letter (like
a
) if It's too long
b
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
@Fudge They’re not too long, they’re hard to read
👍 1
r
Also, because they are infix functions instead of operators, they don't follow the common precedence from other languages.
👍 3
t
@bloder there is also a reverse pipe operator in F# called
<|
b
yeah I was looking for a implementation in some language and I thought in <| too lol, thank you
t
@elizarov just make a way to declare a precendence for infix functions and it should work
👎 1
😱 3
e
Thanks, but no. Other people had been there, done that. We know how it ends.
t
Yet you want to break precendence for a concrete usecase instead of making a generic solution ;)
👌 1
m
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
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
Should be the first one unless you put brackets What does the second one even mean?
e
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
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
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
following this example should we have something like:
foo().bar().5
?
f
5
?
b
something like foo(bar(5))
f
It's specifically for components
b
oh I see, not a general solution to composition
f
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
You don’t need
around
. Dot is enough in compose context.
f
So
Copy code
Padding(color=my).Column {
   Text("Hello")
}
?
e
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
As much as I dislike them, I can sure see how Rust style macros would provide a sleek solution here.
m
Rust provides four types of macros 🙂
r
Fair enough 🙂
r
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
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
Oh, I misunderstood. Nevermind!
Thanks for clearing that up 🙂
e
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
that’s neat.
also this play.kotlinlang.org site is new to me
l
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
yeah the old school version was the one i knew about. this one feels nice 🙂