Thread
#feed
    louiscad

    louiscad

    1 year ago
    Just published my first article, I hope you like it! https://blog.louiscad.com/how-to-return-2-values-with-0-allocation-in-kotlin
    Fun
    fact: I took over 50 photos before I settled for the cover picture
    :blob-upside-down:
    e

    elect

    1 year ago
    really nice, kudos Louis
    Stephan Schroeder

    Stephan Schroeder

    1 year ago
    1. clever solution 👍 2. probably too clever 😐 It's really hard to understand this code 😔 Just use a
    Pair
    if you want to return to different types
    fun getNameAndAge(): Pair<String, Int> = "Bob" to 42
    val (name: String, age: Int) = getNameAndAge() // no danger of accidentally mixing up the order since it'd be a compiletime error
    if the two types are the same, there's a danger of not knowing which is which
    getUserNameAndPassword(): Pair<String, String> = "bob79" to "p4ssw0rd"
    val (password, username) = getUserNameAndPassword() // oh no, wrong order
    In that case either use a
    data class
    but it's still possible to mess up order when using destructoring
    class UsernamePassword(val username: String, val password: String)
    getUserNameAndPassword() = UsernamePassword(
      username="bob79",
      password="p4ssw0rd",
    )
    val usernamePassword = getUserNameAndPassword()
    val (password, username) = usernamePassword // oh no still wrong
    // though long for is way less likely to be mistaken
    val username = usernamePassword.username
    val password = usernamePassword.password // way less likely to mess up
    or you might use
    @JvmInline value class
    to generate typesafe zero cost wrappers
    @JvmInline value class Username(val value: String)
    @JvmInline value class Password(val value: String)
    fun getUserNameAndPassword(): Pair<Username, Password> = Username("Bob") to Password("p4ssw0rd")
    
    val (username: Username, password: Password) = getUserNameAndPassword() // impossible to confuse (would be a compiletime error), zero overhead for the wrappers, single allocation of Pair, very readable
    Readability basically always >> minimization of allocations
    e

    elect

    1 year ago
    Readability basically always >> minimization of allocations
    Not always, sorry. Anyway, it will be nice if Kotlin could provide a language construct for that
    louiscad

    louiscad

    1 year ago
    I personally don't find it that hard to understand the code, but I am clearly biased. Just so you know, I don't use that trick often, but I found it helpful for the use case you can see at the end of the article. Readability is important, this article is not questioning that.
    About your "basically always" statement, I simply cannot agree, we're surrounded by resource constrained software (especially embedded systems). I already covered the matter in the article for code run at high frequencies.
    Stephan Schroeder

    Stephan Schroeder

    1 year ago
    on embedded systems use Rust or Zig. Kotlin is a fantastic hammer, but not as good a screwdriver.
    louiscad

    louiscad

    1 year ago
    Then you have mobile devices, which in a way are a kind of embedded system. There's also wearables. Anyway, I've seen enough slow and janky apps to know that being mindful about resources, especially for hotspots, is important.
    y

    Youssef Shoaib [MOD]

    11 months ago
    The lovely thing about this trick is that it can sort of be extended to provide even more power if the compiler had the ability to inline lambdas that are returned by inline functions. For example, the example in your article would be:
    fun main(){
        val (stuff, bonus) = getStuff()
    }
    
    inline fun getStuff(): ZeroCostPair<Stuff, Bonus> {
        val stuff = grabStuffFromCargoBikeBasket()
        val bonus = inspirationElixir()
        return ZeroCostPair(stuff, bonus)
    }
    
    //Implementation details
    typealias ZeroCostPair<F, S> = (PairCall, F?, S?) -> Any?
    
    enum class PairCall {
      First,
      Second
    }
    
    // Mimicking a constructor for the type.
    inline fun <F, S> ZeroCostPair(first: F, second: S): ZeroCostPair<F, S> =
        { call, _, _ -> 
            when (call) {
              PairCall.First -> first
              PairCall.Second -> second
            }
        }
    
    // Again, the parameters are useless, so just pass in null for them since during runtime the JVM won't actually know what
    // F and S are since they get erased.
    // We can safely cast the result of invoking the function as F or S because we know that ZeroCostPairs created using
    // the factory function always follow the pattern of returning an F if PairCall.First is passed in and likewise for S.
    inline val <F, S> ZeroCostPair<F, S>.first get() = this(PairCall.First, null, null) as F
    inline val <F, S> ZeroCostPair<F, S>.second get() = this(PairCall.Second, null, null) as S
    And the idea is that the returned lambda would just be fully inlined at compile time. I currently have a ticket for this and a prototype compiler plugin