Почти каждый, кто изучает Scala, может запутаться из-за слова, которое используется в контексте:
- Частичные функции
- Частично примененные функции
Давайте посмотрим на оба.
Частично примененные функции
Scala получает свои функциональные идеи от классических языков, таких как Haskell (Haskell 1.0 появился в том же году, что и Depeche Mode, «
Enjoy the Silence», а « Groove» от Dee Lite
в сердце в 1990 году). В функциональных языках функция, которая принимает два параметра и возвращает один параметр, может быть выражена как функция, которая принимает один из входных параметров и возвращает функцию, которая принимает другой входной параметр и возвращает тот же выходной параметр.
f(x1, x2) = y f(x1) = f(x2) = y
Подходящей аналогией было бы путешествие во времени в 1990-е годы и поиск себя в Juxebox. Положите деньги на два выбора и сначала выберите Depeche Mode, а затем Dee Lite, отойдите и бросьте несколько фигур, пока они играются одна за другой. Или, вложив свои деньги на два выбора, выберите Depeche Mode, а затем не делайте другого выбора. Пока не уходи. Хорошо спроектированный Juxebox должен предложить вам другой выбор (дать вам другую функцию), а затем вы можете выбрать Dee Lite (передать второй параметр). Таким образом, конечный результат в обоих случаях — одна и та же музыка в одном и том же порядке.
В Scala, когда в функцию передаются только некоторые параметры для создания другой функции, она называется
частичным применением этой функции.
Итак, рассмотрим функцию:
def minus(a: Int, b: Int) = "answer=" + (a-b)
Теперь давайте частично применим эту функцию, передав некоторые параметры и сделав другую функцию.
val minus50 = (a: Int) => minus(a, 50);
В этом случае
minus50
являетсячастичным применением минуса.
Мы сможем:
minus50(57); // outputs 7.
val minus50 = minus(_:Int, 50);
Частичные функции
Частичная функция — это функция, которая действует только для подмножества значений тех типов, которые вы можете передать в нее. Например, рассмотрим математическую функцию, где x задается из всех чисел от 1 до 100:
f (x) = x + 5;
Функция называется частично примененной, если функция применяется только к подмножеству в наборе элементов x.
Так что, если мы хотим определить только функцию
f(x) = x + 5
для чисел 1,2,3,4,5,6,7, но не для 8,9,10, … — мы определяем частичную функцию.
f(x')=x+5
где x ‘= {1,2,3,4,5,6,7}
В Scala функция PartialFunction наследуется от Function и добавляет два интересных метода:
isDefinedAt
— это позволяет нам проверить, определено ли значение для частичной функции.orElse
— это позволяет объединять частичные функции. Таким образом, если значение не определено для функции, оно может быть передано другой функции. Это похоже на шаблон цепочки ответственности GoF .
Итак, откройте Scala REPL и создайте следующую частичную функцию, которая добавит 5 к целому числу, если целое число меньше 7.
val add5Partial : PartialFunction[Int, Int] = { case d if (d > 0) && (d <= 7) => d + 5; }
Если вы попробуете это для значения, меньшего или равного 7, вы увидите результат без проблем
scala > add5Partial(6); res1: 11
Когда вы пробуете значение больше 7, вы не получите хороший чистый ответ.
scala> myPartial(42); scala.MatchError: 42 (of class java.lang.Integer) at $anonfun$1.apply$mcII$sp(<console>:7) at .<init>(<console>:9) at .<clinit>(<console>) at .<init>(<console>:11) at .<clinit>(<console>) at $print(<console>) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:601) at scala.tools.nsc.interpreter.IMain$ReadEvalPrint.call(IMain.scala:704) at scala.tools.nsc.interpreter.IMain$Request$$anonfun$14.apply(IMain.scala:920) at scala.tools.nsc.interpreter.Line$$anonfun$1.apply$mcV$sp(Line.scala:43) at scala.tools.nsc.io.package$$anon$2.run(package.scala:25) at java.lang.Thread.run(Thread.java:722)
Использование
isDefinedAt()
должно теперь стать очевидным. В этом случае мы могли бы сделать:
add5Partial.isDefinedAt(4) res3: Boolean = true scala> add5Partial.isDefinedAt(42) res4: Boolean = false
Хорошо, а что
orElse
? Давайте определим еще одну частичную функцию, которая работает с числами больше 7 и меньше 100. В таких случаях давайте просто добавим 4.
val add4Partial : PartialFunction[Int, Int] = { case d if (d > 7) && (d <= 100) => d + 5; }
Теперь мы можем просто сделать:
scala> val addPartial = add5Partial orElse add4Partial; addPartial : PartialFunction[Int,Int] = <function1> scala> addPartial(42); res6: Int = 46
Хорошо, давайте посмотрим, как все это может быть реализовано в Java с использованием шаблона Chain of Responsibility. Во-первых, давайте определим интерфейс обработчика и реализацию add5 и add4, которая будет его реализовывать.
//Handler public interface AdditionHandler { //reference to the next handler in the chain public void setNext(AdditionHandler handler); //handle request public void handleRequest(int number); } public class Add5Handler implements AdditionHandler { private AdditionHandler nextAdditionHandler = null; public void setNext(AdditionHandler hander) { this.nextAdditionHandler = handler; } public int handleRequest(int number) { if ((number > 0) && (number <= 7)) { return number + 5; } else { return nextAdditionHandler.handleRequest(number); } } } public class Add4Handler implements AdditionHandler { private AdditionHandler nextAdditionHandler = null; public void setNext(AdditionHandler hander) { this.nextAdditionHandler = handler; } public int handleRequest(int number) { if ((number > 7) && (number <= 100)) { return number + 4; } else { return nextAdditionHandler.handleRequest(number); } } }
Теперь давайте создадим класс, который будет связывать обработчики.
public class AdditionProcessor { private AdditionHandler prevHandler; public void addHandler(AdditionHandler handler){ if(prevHandler != null) { prevHandler.setNext(handler); } prevHandler = handler; } }
И, конечно же, клиент, который на самом деле вызывает функциональность:
public class AdditionClient { private AdditionProcessor processor; public AdditionClient(){ createProcessor(); } private void createProcessor() { processor = new AdditionProcessor(); processor.addHandler(new Add5Handler()); processor.addHandler(new Add4Handler()); } public void addRule(AdditionHandler handler) { processor.addHandler(handler); } public void requestReceived(int value){ System.out.println("value=" + processor.handleRequest(value)); } public static void main(String[] args) { AdditionClient client = new AdditionClient(); } }
Так что у Scala есть некоторые явные преимущества. Или, конечно, люди скажут «
ах, но на Java вы просто делаете …»
public int addFunction(int value) { if ((value > 0) && (value <= 7)) { return value + 5; } else if ((value > 7) && (value < 100)) { return value + 4; } else { // ... } }
И да, для этого конкретного случая это будет работать. Но что, если ваши функции / команды станут более сложными. Вы собираетесь торчать на
if / else
земле? Возможно нет. «До следующего раза, береги себя.