Является ли правильный подход к преобразованию Groovy замыканий в форму, которую может использовать Rx Java - PullRequest
0 голосов
/ 13 марта 2020

Я только начал смотреть на использование Rx Java V3 с Groovy v3. Естественно, я склоняюсь к тому, чтобы начать с Closures.

Но метод Rx Java subscribe () не принимает их. Поэтому я реализовал конкретный класс FunctionalClosure, который действует как преобразование из замыкания в Rx java Consumer, или Function или MethodClosure, как показано ниже.

В чем я не уверен, так это в том, что это идиоматически Лучший способ решить мою проблему. Частично проблема заключается в том, что Groovy Closure является абстрактным классом, поэтому вы должны расширить его. Во-вторых, единственный способ, которым я мог найти «хранение» представленного замыкания, - это иметь ссылку на Замыкание внутри этой конкретной реализации.

Что я не могу понять, так это когда вы пишете Closure myClos = {xx -> ...} в редакторе для создания замыкания, где хранится этот исполняемый код? Неявным образом я хотел бы сохранить свой клон входного замыкания в том же месте - но, глядя на код абстрактного класса Closure, я не могу понять, где это.

Вы должны реализовать doCall () метод в вашем конкретном классе, который я сделал, и он просто вызвал внутреннюю переменную закрытия действия

import groovy.transform.InheritConstructors
import io.reactivex.rxjava3.functions.Consumer
import io.reactivex.rxjava3.functions.Function
import org.codehaus.groovy.runtime.MethodClosure

/**
 * class to wrap a closure and convert it into a RxJava Consumer
 * @param <T>  expected type of the arg that that the closure will be called with
 */
@InheritConstructors
class FunctionalClosure<T, R> extends Closure implements Consumer<T>, Function<T,R> {

    private Closure action = {}

    //maximumNumberOfParameters = 1
    //parameterTypes = EMPTY_CLASS_ARRAY

    FunctionalClosure() {
        super(null)
    }

    FunctionalClosure (final Closure clos) {
        //setup the abstract closure with the owner of the closure
        //super(clos?.owner)
        super (clos.clone())

        maximumNumberOfParameters = clos.getMaximumNumberOfParameters()
        action = clos.clone()
   }

    //implement doCall to direct the call() to the action closure
    protected Object doCall(Object arguments) {
        return action(arguments)
    }

    Closure<T> leftShift (final Closure clos) {
        action = clos.clone()

    }

    /**
     * as we have an embedded action closure, make sure when setting the closure delegate
     * that this is set on the action.
     * @param delegate - the object you want to provide the context for the action
     */
    //
    void setDelegate (Object delegate) {
        action.delegate = delegate
    }

    /**
     * implements the RxJava Consumer contract, takes a generic arg of type T,
     * an invokes the closure call () with the arg
     * @param arg
     */
    void accept (T arg) {
        call (arg)
    }

    /**
     * implements the RxJava Function contract, takes a generic arg of type T,
     * an invokes the closure call () with the arg, and returns the result of the call
     * @param arg
     */
    R apply (T arg) {
        return call (arg)
    }


        /**
     * static from method, accepts a closure and assigns a clone of it
     * and returns result as Consumer<T>
     * @param clos pass some closure to convert to Functional type
     * @return Consumer<T>
     */
    static <T> Consumer<T>  consumerFrom (Closure clos ) {
        assert clos

        if (clos.maximumNumberOfParameters == 0){
            throw new IncorrectClosureArgumentsException("from: closure must accept at least one argument")
        }
        Closure cons = new FunctionalClosure<>(clos.clone())
        cons
    }

    /**
     * static from method, accepts a closure and assigns a clone of it
     * and returns result as Function<T, R>
     * @param clos pass some closure to convert to Functional type
     * @return Consumer<T>
     */
    static <T,R> Function<T, R>  functionFrom (Closure clos ) {
        assert clos

        if (clos.maximumNumberOfParameters == 0){
            throw new IncorrectClosureArgumentsException("from: closure must accept at least one argument")
        }
        Closure cons = new FunctionalClosure<>(clos.clone())
        cons
    }

    /**
     * static from method, accepts a closure and assigns a clone of it
     * and returns result as Consumer<T>
     * @param clos pass some closure to convert to Functional type
     * @return Consumer<T>
     */

    static MethodClosure asMethodClosure (Closure clos ) {
        assert clos

        Closure cons = new FunctionalClosure<>(clos.clone())
        cons::accept
    }

}

Кажется, все это работает, то есть я могу написать такой скрипт, и он напечатает все числа, когда я подпишусь на одного из потребителей, или я использую метод stati c FunctionalClosure.asMethodClosure {println it}.

Consumer cons = new FunctionalClosure ()
cons << {println it}

Flowable pl = Flowable.fromIterable([1,2,3])

//Function pc = {num -> println num} as Function

pl.map{num -> num*2}.subscribe(cons)

Я надеюсь, что кто-то скажет: это правильный 'способ решения этой проблемы, или же на самом деле это лучший идиоматический c Groovy способ сделать это.

1 Ответ

3 голосов
/ 14 марта 2020

Давайте сначала возьмем рабочий пример кода без FunctionalClosure:

@Grapes(
    @Grab(group='io.reactivex.rxjava3', module='rxjava', version='3.0.0')
)
import io.reactivex.rxjava3.functions.*
import io.reactivex.rxjava3.core.*

Flowable pl = Flowable.fromIterable([1,2,3])
pl.map {num -> num*2}.subscribe {num -> println num}

При этом использовался Groovy 3.0.1, и было бы прекрасно принять аргумент Closure для метода подписки. Я бы также назвал это идиоматическим c способом.

Если это действительно сдвиг, который вы хотите сохранить:

@Grapes(
    @Grab(group='io.reactivex.rxjava3', module='rxjava', version='3.0.0')
)
import io.reactivex.rxjava3.functions.*
import io.reactivex.rxjava3.core.*

def mul2 = {num -> num*2}
def add1 = {num -> num+1}
def add1AndDouble = add1>>mul2

Flowable pl = Flowable.fromIterable([1,2,3])
pl.map (add1AndDouble).subscribe {it -> println it}

или используйте mul2<<add1, если он должен быть левым shift.

По состоянию на "где хранится этот исполняемый код?" Основной ответ заключается в том, что Groovy создаст внутренний класс, который расширяет Closure. Этот внутренний класс вашего текущего класса или сценария (конечно, Closure не будет изменен) будет иметь метод doCall, который будет вызываться, если вы вызываете метод «call» в Closure. Если вы используете «MethodClosure», то код сохраняется в методе, на который вы ссылаетесь, а Closure является только внешним интерфейсом для вызова метода. В Groovy 3 все больше и больше кода будут использовать способ java 8+ для реализации этого и генерировать вызов invokedynami c. Затем код сохраняется в методе текущего класса / скрипта.

Если вы пишете код в Groovy, вам обычно вообще не нужно расширять Closure. Только если вы хотите вызвать что-то в Groovy, что требует закрытия, и ваш код вызова не Groovy, тогда вы можете сделать это.

...