elizarov

    elizarov

    3 years ago
    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:
    fun foo() = logged { transactional { ... } } // (1)
    Padding(color=my) { OtherWrapper { Component() } } // (2)
    It would be preferable to be able to write:
    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 :thread-please:
    r

    romainguy

    3 years ago
    Obvious ones (but probably conflict with the language): ->, > and =>
    karelpeeters

    karelpeeters

    3 years ago
    Could
    .
    work?
    function.x
    never means anything right now does it?
    miha-x64

    miha-x64

    3 years ago
    there may be a member
    x
    elizarov

    elizarov

    3 years ago
    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?)
    karelpeeters

    karelpeeters

    3 years ago
    Ah
    function
    could be an ordinary class with an
    invoke
    operator.
    Fudge

    Fudge

    3 years ago
    What if you used an infix instead of a language construct?
    logged and transactional
    miha-x64

    miha-x64

    3 years ago
    btw, I'd like functions to be delegatable.
    fun foo() by logged { transactional(::someFunc) }
    l

    Leland Richardson [G]

    3 years ago
    you could go with the haskell bind operator:
    >>=
    or the (proposed) js pipeline operator
    |>
    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
    Fudge

    Fudge

    3 years ago
    IMO the less un-google-able symbols the better, if you ask me I would even say
    extends
    is better than
    :
    l

    Leland Richardson [G]

    3 years ago
    lol that double negative confused me
    yeah googlability is something to consider
    an infix might suffice here
    elizarov

    elizarov

    3 years ago
    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

    3 years ago
    What about
    using>
    ? It is the only unambiguous thing I can think that is abvious while reading
    miha-x64

    miha-x64

    3 years ago
    >
    ? it's already ambiguous in the existing language 🙂
    elizarov

    elizarov

    3 years ago
    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:
    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.
    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.
    Fudge

    Fudge

    3 years ago
    Could you explain a case in which using a non-keyword would be ambiguous?
    miha-x64

    miha-x64

    3 years ago
    btw, bug fixes are way greater than a possibility to write
    func <smth> expr
    instead of
    func { expr }
    r

    Ruckus

    3 years ago
    What about a
    \
    ?
    Fudge

    Fudge

    3 years ago
    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
    elizarov

    elizarov

    3 years ago
    @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.
    Fudge

    Fudge

    3 years ago
    I see, thanks @elizarov
    I vouch for using
    &
    for this then,
    and
    for the bitwise operation is good enough
    r

    romainguy

    3 years ago
    I'll take bitwise operators over anything else 😃
    Fudge

    Fudge

    3 years ago
    It's not like they don't exist, you can even make it one letter (like
    a
    ) if It's too long
    bloder

    bloder

    3 years ago
    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

    3 years ago
    @Fudge They’re not too long, they’re hard to read
    r

    Ruckus

    3 years ago
    Also, because they are infix functions instead of operators, they don't follow the common precedence from other languages.
    themishkun

    themishkun

    3 years ago
    @bloder there is also a reverse pipe operator in F# called
    <|
    bloder

    bloder

    3 years ago
    yeah I was looking for a implementation in some language and I thought in <| too lol, thank you
    themishkun

    themishkun

    3 years ago
    @elizarov just make a way to declare a precendence for infix functions and it should work
    elizarov

    elizarov

    3 years ago
    Thanks, but no. Other people had been there, done that. We know how it ends.
    themishkun

    themishkun

    3 years ago
    Yet you want to break precendence for a concrete usecase instead of making a generic solution 😉
    miha-x64

    miha-x64

    3 years ago
    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

    3 years ago
    Could this cause ambiguity?
    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?
    Fudge

    Fudge

    3 years ago
    Should be the first one unless you put brackets What does the second one even mean?
    elizarov

    elizarov

    3 years ago
    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

    3 years ago
    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.
    elizarov

    elizarov

    3 years ago
    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:
    Padding(color=my).OtherWrapper().Component()
    Is it a readable shortcut to writing it with curly braces?
    bloder

    bloder

    3 years ago
    following this example should we have something like:
    foo().bar().5
    ?
    Fudge

    Fudge

    3 years ago
    5
    ?
    bloder

    bloder

    3 years ago
    something like foo(bar(5))
    Fudge

    Fudge

    3 years ago
    It's specifically for components
    bloder

    bloder

    3 years ago
    oh I see, not a general solution to composition
    Fudge

    Fudge

    3 years ago
    I think Roman means something like this
    Padding(color=my).around().Column {
       Text("Hello")
    }
    In which case you case just use
    infix
    ...
    Padding(color=my) around Column {
       Text("Hello")
    }
    elizarov

    elizarov

    3 years ago
    You don’t need
    around
    . Dot is enough in compose context.
    Fudge

    Fudge

    3 years ago
    So
    Padding(color=my).Column {
       Text("Hello")
    }
    ?
    elizarov

    elizarov

    3 years ago
    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:
    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
    So it is ultimately for compose team to decide how they’d like their DSL to look like.
    r

    Ruckus

    3 years ago
    As much as I dislike them, I can sure see how Rust style macros would provide a sleek solution here.
    miha-x64

    miha-x64

    3 years ago
    Rust provides four types of macros 🙂
    r

    Ruckus

    3 years ago
    Fair enough 🙂
    Robert Menke

    Robert Menke

    3 years ago
    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.
    fun pascalCase() = snakeCase andThen upperCase
    //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))
    }
    elizarov

    elizarov

    3 years ago
    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.
    Robert Menke

    Robert Menke

    3 years ago
    Oh, I misunderstood. Nevermind!
    Thanks for clearing that up 🙂
    elizarov

    elizarov

    3 years ago
    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)
    fun foo() = (logged + transactional) { ... }
    Implementation sketch here: https://pl.kotl.in/NDME-I0LX
    l

    Leland Richardson [G]

    3 years ago
    that’s neat.
    also this play.kotlinlang.org site is new to me
    louiscad

    louiscad

    3 years ago
    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]

    3 years ago
    yeah the old school version was the one i knew about. this one feels nice 🙂