y

    Youssef Shoaib [MOD]

    2 years ago
    Is the source code for the keep-87 plugin available anywhere? cuz IIRC it's based on Arrow Meta, right? I wanted to draw some inspiration from it to make a compiler plugin myself, but the only plugins in the arrow meta plugin package seem to be the higherkind, proof, optics, etc. ones, but not the typeclasses/extension interfaces one.
    t

    tginiotis

    2 years ago
    it is on the "keep-87" branch and looks like it was patches straight to the compiler - https://github.com/arrow-kt/kotlin/commits/keep-87
    t

    thanerian

    2 years ago
    keep-87 plugin become a plugin using arrow-meta
    simon.vergauwen

    simon.vergauwen

    2 years ago
    Yes, it was indeed patches done straight into the compiler. That was done a bit over 2 years ago iirc, about a year before the start of Arrow Meta.
    The use-cases of keep-87 can be covered by what the proof system. Which @raulraja discusses in this talk

    https://www.youtube.com/watch?v=ETn_6LmMjho

    raulraja

    raulraja

    2 years ago
    @Youssef Shoaib [MOD] as @simon.vergauwen mentioned type classes are implemented just with the proofs plugin. If you have a particular use case I may be able to show you how you can use proofs for that or copy whatever part of the internals you need for what you are trying to achieve.
    y

    Youssef Shoaib [MOD]

    2 years ago
    Thanks so much for all the responses.@raulraja I will take a look at the proofs plugin for sure. What I was trying to make is actually rather simple when compared to the great work that you all are doing. I was just trying to make a plugin that allows for suspend properties lol, and I wanted to look at how ide integration works for stuff like autocompletion and jump to declaration and the like. I've already started messing around with the actual IR backend itself and just wanted to see some of the entry points to certain steps of the compiler (e.g. how to add a new function to a class). I will navigate thru the proofs plugin source code and will definitely ask here if I have any questions.
    raulraja

    raulraja

    2 years ago
    Hi Youssef. Suspend properties will be a major undertake because suspend functions desugars into a CPS compiler generates loop that unfolds the continuation. You would not easily be able to support all property usecases since the suspension system has no no notion of supporting backing fields nor it would be able IMO to implement them with all atomic concerns continuations imply that is they can resume in arbitrary threads and are single shot.
    All suspend functions desugar into regular two argument functions in which the last argument is the Continuation
    If I were you and really wanted this I would focus on just a simple subset of just immutable properties and then translate those in IR to regular functions. Still open questions is what would the compiler do when you suppress in analysis the compiler error. Internal codegen may not be ready for it and there may not be much you can do about it. The CPS generated loop has no spec or way to extend it AFAIK.
    It's performed in the IR lowering for the JVM since IR still has the notion of suspension in it's AST
    y

    Youssef Shoaib [MOD]

    2 years ago
    I meant a suspend property as syntatic sugar for a suspend getter and setter
    those accessors would arbitrarily do whatever they want to fetch data or post it to a remote network server for example
    I did actually think about it a bit, and I think it can work with a Coercion that basically turns an inline class that holds a
    (Boolean, T?) -> T?
    lambda where the Boolean just denotes a branch for whether this is a get or set (this would also be accompanied by a simple inline factory method that has 2 crossinline parameters for the getter and the setter that just calls the getter if the boolean is true and calls the setter if it isn't. This inline class would then just be used as a property delegate (with the appropriate inline
    getValue
    and
    setValue
    operators that just return that same inline class and perform the setting operation respectively), but then there would also be the appropriate
    @Coercion
    suspend functions that coerce that inline class into its type param (i.e. by calling its lambda with true) and also another coercion that coerces any arbitrary
    T
    into an inline class of that
    T
    (this is the only part where I'm not entirely sure whether or not it would be supported by the proofs plugin, but I think that it should hopefully work).
    (Note: I'm not thinking about vals for now just to make this less complicated. I'm also using null here to denote what the setter returns. This is all just rough code and it's definitely not pretty, but it's just a PoC) The use site would then be quite simple. Say for example that the factory function is this:
    // Probably should use Optional for a lot of these nullables, but just as a PoC I'll use nullables
    inline class SuspendProperty<T> internal constructor(internal val lambdaOrValue: Union2<suspend (Boolean, T?) -> T?, T>) {
        // Impl details
        operator fun getValue(...) = this
        operator fun setValue(..., value) {
            val lambda: (suspend (Boolean, T?) -> T?)? = lambdaOrValue
            val actualValue: T? = value.lambdaOrValue
            lambda?.invoke(false, actualValue)
        }
    }
    fun <T> suspendProperty(getter: suspend () -> T, setter: suspend (T) -> Unit): SuspendProperty<T> = 
        SuspendProperty { isGet, value ->
            if(isGet) getter() 
            else {
                setter(value!!)
                null
            }
        }
    Then with these Coercions:
    //Probably should also use a totally different datatype other than the delegate itself for this, but eh whatever
    @Coercion suspend fun <T> SuspendProperty<T>.value(): T {
        val lambda: (suspend (Boolean, T?) -> T?)? = lambdaOrValue
        return lambda!!.invoke(true, null)!!
    }
    @Coercion suspend fun <T> T.toSuspendProperty(): SuspendProperty<T> = SuspendProperty(this)
    The use site would look completely natural:
    class Example {
        var someRemoteString by suspendProperty(
            getter = { TODO("Remote request for the string"),
            setter = { TODO("Send the new value to the server or something) }
    }
    
    fun main() {
        runBlocking {
            val example = Example()
            val str: String = example.someRemoteString
            example.someRemoteString = str + ", or is it?"
            funThatHasAStringParameter(example.someRemoteString)
        }
    }
    (Again this code is very rough and totally untested, but it should convey the basic approach that I'm taking here)
    There is also the possibility of optimizing this even more using a compiler plugin that gets rid of that whole lambda if it is statically known at compile time and just turns it into 2 functions on that same class that then get called instead of calling the lambda My initial go at this without the proofs plugin was basically to just generate 2 methods in the class (called
    getXXXSuspend
    and
    setXXXSuspend
    ), and then just replace every call to an accessor of the property with one of these functions and giving the user an error if it was called in a non-suspending context (basically the same as a regular function), but the proofs plugin really does make this easier albeit in a slightly convoluted way.
    raulraja

    raulraja

    2 years ago
    I’m not sure if this has changed in 1.4 but property getValue delegates could not suspend so running suspension there would have to b done with startCoroutine. Yes this looks like a more elaborate scheme that can work if you handle suspension manually. I thought at first you wanted just the ability to modify any property as suspend including support for their autogenerated backing fields. 🙂
    y

    Youssef Shoaib [MOD]

    2 years ago
    yeah I see what you mean. Support for backing fields could also happen tho, just by having a regular class for the delegate instead of an inline one and having a property that acts as that backing field. Synchronization could then also be achieved using a Mutex, but yeah that is really unnecessary for the use case of just accessing the value of a Deffered or whatever with less syntax clutter. I started down that path because I was trying to port a popular js library (Comlink) to Kotlin MP, and part of that was trying to create an idiomatic experience, which in turn needed either suspend properties or syntactic clutter of get and set functions.