Напишите функцию, которая имеет те же значения по умолчанию, что и конструктор

#reactjs #kotlin #generics #kotlin-js

#reactjs #kotlin #общие #kotlin-js

Вопрос:

Абстрактная проблема:

Рассмотрим следующее:

 data class Data(val i: Int, val s: String = "")

fun <A1, A2, D> make(ctor: KFunction2<A1, A2, D>, sideEffect: (D) -> D) =
    { a1: A1, a2: A2 -> sideEffect(ctor(a1, a2)) }

val makeData = make(::Data) {
    it.also { println("Side effect: i=${it.i}, s=${it.s}") }
}
 
  

Я могу вызвать makeData со всеми аргументами конструктора, чтобы получить
экземпляр Data , а также вызвать некоторый побочный эффект. Однако я
не могу опустить параметры и вместо этого использовать значения конструктора по умолчанию:

 val d1 = makeData(42, "hi")
val d2 = makeData2(42) // Error: No value passed for parameter 'p2'
  

Как я могу переписать generic make таким образом, чтобы его вывод был функцией со значениями по умолчанию?

Чего я пытаюсь достичь:

С помощью Kotlin React я объявляю свойства следующим образом:

 external interface VideoListProps: RProps { var videos: List<Video> }
  

Чтобы использовать компонент в render(), я могу написать:

 child(VideoList::class) { attrs.videos = unwatchedVideos }
  

Мне это не нравится по трем причинам: (a) использование компонента намного более подробное, чем по сравнению с TypeScript / TSX,
(b) свойства должны быть объявлены как var, в то время как они должны быть доступны только для чтения (val), (c) нет принудительного выполнения того, что член фактически объявлен; props.videos может закончиться как null во время выполнения, даже если интерфейс Props не объявляет его как обнуляемый.

В руководстве по Kotlin React предлагается использовать лямбды с приемниками, например:

 fun RBuilder.videoList(handler: VideoListProps.() -> Unit): ReactElement {
    return child(VideoList::class) {
        this.attrs(handler)
    }
}
  

Теперь я могу использовать компонент следующим образом:

 videoList { videos = unwatchedVideos }
  

Хотя вызывающий сайт теперь выглядит намного более лаконичным, теперь мне приходится копировать / вставлять и адаптировать фрагмент лямбда-кода для каждого компонента, который я пишу. Это намного более подробно, чем в TypeScript, где мне не нужна эта конструкция. Более того, лямбда-файл videoList не имеет доступа к другим элементам RBuilder, поскольку this теперь указывает на RProps . Поэтому, если мне нужно определить реакцию key , я в растерянности и должен использовать синтаксис child(…) из предыдущего. И, наконец, проблема (c) сверху все еще существует.

Чтобы сделать это лучше, я написал следующие вспомогательные конструкции:

 fun <P : RProps, A1> RBuilder.childWithProps(
    klass: KClass<out Component<P, *>>,
    ctor: KFunction1<A1, P>
) =
    { a1: A1, handler: RHandler<P> -> child(klass.rClass, ctor(a1), handler) }

fun <P : RProps, A1, A2> RBuilder.childWithProps(
    klass: KClass<out Component<P, *>>,
    ctor: KFunction2<A1, A2, P>
) =
    { a1: A1, a2: A2, handler: RHandler<P> -> child(klass.rClass, ctor(a1, a2), handler) }

// more for KFunction3, KFunction4, ... up to a reasonable amount of members, e.g. 16
  

Я признаю, что это тоже некрасиво, но мне нужно написать это только один раз. Теперь я могу объявлять такие реквизиты (обратите внимание на значение val вместо var):

 data class VideoListProps(val videos: List<Videos>) : RProps
  

Дополнительный код для каждого компонента теперь намного более лаконичен:

 val RBuilder.videoList get() = childWithProps(VideoList::class, ::VideoListProps) 
  

И используйте ее следующим образом:

 videoList(watchedVideos) { /* you can set React key or children here if needed */ }   
  

Kotlin теперь помечает это как ошибку, если я забуду установить все свойства.
Теперь все выглядит лаконично, я пишу даже немного меньше кода, чем в TypeScript / TSX. Единственная оставшаяся проблема заключается в том, что теперь мне всегда приходится передавать все элементы, поскольку любые значения по умолчанию конструктора данных все равно будут сохраняться в оболочке. Это актуальный вопрос.

Комментарии:

1. Почему бы вам просто не использовать constucted в D качестве входных данных?

2. Потому что я хотел бы максимально упростить ее использование. sideEffect(Data1(a, b)) сложнее, чем makeData1(a, b) .

3. Я переписал вопрос, чтобы прояснить, чего я пытаюсь достичь.