Scala Function.tupled и Function.untup эквивалент для переменной arity, или, вызов функции переменной arity с кортежем

Я пытался сделать что-то вчера вечером, приняв и назвав общую функцию (т.е. тип известен на сайте вызова, но потенциально может варьироваться в разных сайтах, поэтому определение должно быть общим по всем направлениям).

Например, предположим, что у меня есть функция f: (A, B, C, ...) => Z. (На самом деле существует много таких f s, о которых я не знаю заранее, поэтому я не могу исправить типы и количество A, B, C, ..., Z.)

Я пытаюсь добиться следующего.

  • Как мне вызвать f в общем случае с экземпляром (A, B, C, ...)? Если подпись f была известна заранее, я мог бы сделать что-то, включающее Function.tupled f или эквивалентный.

  • Как определить другую функцию или метод (например, метод object apply) с той же подписью, что и f? То есть, как я могу определить g, для которого тип g(a, b, c, ...) проверяет, следует ли и только если тип t212 > проверяет тип? Для этого я искал Shapeless HList. Из того, что я могу сказать до сих пор, HList, по крайней мере, решает проблему "представления произвольного аргумента arties", а также Shapeless решит преобразование в и из кортежа. Тем не менее, я все еще не уверен, что понимаю, как это будет вписываться в функцию общей арности, если это вообще возможно.

  • Как определить другую функцию или метод с соответствующей сигнатурой типа в f? Самый большой пример, который приходит сейчас на ум, - это немного h: (A, B, C, ...) => SomeErrorThing[Z] \/ Z.

Я помню, как некоторое время назад я смотрел презентацию конференции о Shapeless. Пока ведущий явно не продемонстрировал эти вещи, то, что они продемонстрировали (различные методы, связанные с абстрагированием/обобщением кортежей vs HList s), побудило бы меня поверить, что подобные вещи, как указано выше, возможны с помощью тех же инструментов.

Спасибо заранее!

Ответ 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, он становится более или менее интуитивным, и мы, конечно, будем рады ответить на более конкретные вопросы о ваш прецедент.