Ответ 1
Да, Shapeless может вам абсолютно помочь. Предположим, например, что мы хотим взять функцию произвольной arity и превратить ее в функцию одной и той же арности, но с типом возврата, завернутым в Option
(я думаю, что это затронет все три точки вашего вопроса).
Чтобы все было просто, я просто скажу, что Option
всегда Some
. Это занимает довольно плотные четыре строки:
import shapeless._, ops.function._
def wrap[F, I <: HList, O](f: F)(implicit
ftp: FnToProduct.Aux[F, I => O],
ffp: FnFromProduct[I => Option[O]]
): ffp.Out = ffp(i => Some(ftp(f)(i)))
Мы можем показать, что он работает:
scala> wrap((i: Int) => i + 1)
res0: Int => Option[Int] = <function1>
scala> wrap((i: Int, s: String, t: String) => (s * i) + t)
res1: (Int, String, String) => Option[String] = <function3>
scala> res1(3, "foo", "bar")
res2: Option[String] = Some(foofoofoobar)
Обратите внимание на соответствующие статические типы возврата. Теперь о том, как это работает:
Класс типа FnToProduct
показывает, что некоторый тип F
является FunctionN
(для некоторого N
), который может быть преобразован в функцию из некоторого HList
в исходный тип вывода. Функция HList
(a Function1
, если быть точным) является членом типа Out
экземпляра или параметром второго типа помощника FnToProduct.Aux
.
FnFromProduct
делает обратное - это свидетельствует о том, что некоторый F
является Function1
от HList
к некоторому типу вывода, который может быть преобразован в функцию некоторой arity для этого типа вывода.
В нашем методе wrap
мы используем FnToProduct.Aux
для ограничения Out
экземпляра FnToProduct
для F
таким образом, чтобы мы могли ссылаться на список параметров HList
и O
> тип результата в типе нашего экземпляра FnFromProduct
. Реализация тогда довольно проста - мы просто применяем экземпляры в соответствующих местах.
Это может показаться очень сложным, но как только вы уже некоторое время работали с таким общим программированием в Scala, он становится более или менее интуитивным, и мы, конечно, будем рады ответить на более конкретные вопросы о ваш прецедент.