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) }
Стоило оно того или нет – это вопрос, но знать в любом случае полезно.
Leave a Reply to existen Cancel reply