tl;dr
Метод так вызвать нельзя, но можно сконвертировать его в функцию, а у них есть метод tupled.
То же самое, но длинно
Потребовался нам в проекте генератор тестовых данных. Модель данных уже есть, типы и ограничения прописаны, можно спокойно брать и генерить. Нашлась даже подходящая библиотека с набором методов для генерации примитивов – DataFactory от товарища Andy Gibson’а.
Получилось всё легко и просто, выглядит при этом в таком духе:
field match { case f:Field.NumberField => df.getNumberBetween(0, 1000) case f:Field.StringField => genRandomString(100, 200) // и так далее }
Для пущей правдоподобности полезно соблюсти границы значений, которые прописаны в модели. Правда, указаны они, понятно, не у всех полей, поэтому и границы по умолчанию следует указать. Причём, в разных местах они отличаются, поэтому в класс самого поля жёстко их зашивать не хотелось бы. Не проблема совершенно – пишется простенький метод, который принимает дефолтные границы, перебирает коллекцию валидаторов, и если находит подходящий – возвращает границы из него, а если нет, то дефолтные. Возвращает естественно в виде tuple (слово «кортеж» меня коробит).
def limits(min:Int, max:Int):(Int, Int) = { field.validators.collectFirst( { case v:Validator.Length => (v.min, v.max) case v:Validator.Range => (v.from, v.to) }).getOrElse((min, max)) }
Только вот методы DataFactory принимают параметры по отдельности, и правильно делают. Не говоря уже о том, что это вообще жабья библиотека, и понятием наскальных туплов она не владеет.
Затруднение с этим тоже не великое: наскальные экстракторы работают замечательно, вполне можно написать:
val (min, max) = limits(0, 10)
Но это же надо будет в каждом кейсе писать… Аж по две переменные два промежуточных значения… Или по одному, но с уродливыми ._1 и ._2. Неэстетично-с…
Именно для таких случаев у наскальных функций имеется метод tupled, который позволяет функцию от нескольких параметров вызвать с одним tuple.
Процесс преобразования метода в функцию в скале принято называть звучным термином из лямбда-исчисления «eta expansion». По сути это означает, что компилятор сгенерирует вложенный класс-обёртку, унаследованный от FunctionN (в нашем случае Function2), который в методе apply вызывает нужный метод.
Синтаксически это оформляется подчёркиванием после имени метода. Кстати, это совпадает с синтаксисом частичного применения функций (curring), что есть весьма консистентно.
С использованием вышеперечисленного, вызов – тема этого поста выглядит следующим образом:
(df.getNumberBetween _).tupled(limits(0, 1000))
Можно пойти ещё дальше и часть этого кода обобщить. Например, в виде такого метода:
def limited[A](f:(Int, Int) => A)(min:Int, max:Int):A = f.tupled(limits(min, max))
Более того, в случае передачи метода в качестве параметра типа «функция», компилятор способен произвести eta expansion самостоятельно безо всяких подчёркиваний. Конечный результат может выглядеть так:
field match { case f:Field.NumberField => limited(df.getNumberBetween)(0, 1000) case f:Field.StringField => limited(genRandomString)(100, 200) }
Стоило оно того или нет – это вопрос, но знать в любом случае полезно.
3 responses to “Scala: вызов метода с tuple вместо набора параметров”
Это просто праздник какой-то – в Караганде есть компании, которые используют скалу в коммерческих целей. 🙂
Используем. Года три уже, вполне успешно. Хотя, конечно, не самый распространённый в наших краях навык. Технически все получается хорошо, а вот с поиском кадров у нас из-за этого проблема.
Да, наверное, готовых кадров в наших краях не сыскать.
Но я так понял, разработчикам, которые знакомы с инфраструктурой джавы и с функциональным стилем прогнаммирования, не составляет труда перейти на скалу. По крайней мере, в одной из относително крупных фирм в нашем городе предпринимаются попытки перехода на скалу.