Почти каждый, кто изучает Scala, может запутаться из-за слова, которое используется в контексте:
- Частичные функции
- Частично примененные функции
Давайте посмотрим на оба.
Частично примененные функции
Scala получает свои функциональные идеи от классических языков, таких как Haskell (Haskell 1.0
появился в том же году, что и Depeche Mode « Наслаждайся тишиной», а « 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, когда в функцию передаются только некоторые параметры для создания другой функции, она называется частичным применением этой функции. Итак, рассмотрим функцию:
|
1
|
def minus(a: Int, b: Int) = 'answer=' + (a-b) |
Теперь давайте частично применим эту функцию, передав некоторые параметры и сделав другую функцию.
|
1
|
val minus50 = (a: Int) => minus(a, 50); |
В этом случае minus50 является частичным применением минус. Мы можем:
|
1
|
minus50(57); // outputs 7. |
Примечание: мы также можем частично подать заявку, используя нотацию _ и сэкономить немного отпечатков пальцев.
|
1
|
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
где х ‘= {1,2,3,4,5,6,7}
В Scala PartialFunction наследует от Function и добавляет два интересных метода:
-
isDefinedAt— это позволяет нам проверить, определено ли значение для частичной функции. -
orElse— это позволяетorElseчастичные функции. Таким образом, если значение не определено для функции, оно может быть передано другой функции. Это похоже на шаблон цепочки ответственности GoF.
Итак, откройте Scala REPL и создайте следующую частичную функцию, которая добавит 5 к целому числу, если целое число меньше 7.
|
1
2
3
|
val add5Partial : PartialFunction[Int, Int] = { case d if (d > 0) && (d <= 7) => d + 5;} |
Если вы попробуете это для значения, меньшего или равного 7, вы увидите результат без проблем
|
1
2
|
scala > add5Partial(6);res1: 11 |
Когда вы пробуете значение больше 7, вы не получите хороший чистый ответ.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
|
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)</console></console></clinit></console></init></console></clinit></console></init></console> |
Использование isDefinedAt() теперь должно стать очевидным. В этом случае мы могли бы сделать:
|
1
2
3
4
5
|
add5Partial.isDefinedAt(4)res3: Boolean = truescala> add5Partial.isDefinedAt(42)res4: Boolean = false |
Хорошо, так что насчет
orElse ? Давайте определим еще одну частичную функцию, которая работает с числами больше 7 и меньше 100. В таких случаях давайте просто добавим 4.
|
1
2
3
|
val add4Partial : PartialFunction[Int, Int] = { case d if (d > 7) && (d <= 100) => d + 5;} |
Теперь мы можем просто сделать:
|
1
2
3
4
|
scala> val addPartial = add5Partial orElse add4Partial;addPartial : PartialFunction[Int,Int] = <function1>scala> addPartial(42);res6: Int = 46 |
Хорошо, давайте посмотрим, как все это может быть реализовано в Java с использованием шаблона Chain of Responsibility. Во-первых, давайте определим интерфейс обработчика и реализацию add5 и add4, которая будет его реализовывать.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
//Handlerpublic 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); } } } |
Теперь давайте создадим класс, который будет связывать обработчики.
|
1
2
3
4
5
6
7
8
9
|
public class AdditionProcessor { private AdditionHandler prevHandler; public void addHandler(AdditionHandler handler){ if(prevHandler != null) { prevHandler.setNext(handler); } prevHandler = handler; } } |
И, конечно же, клиент, который на самом деле вызывает функциональность:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
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 вы просто делаете…»
|
1
2
3
4
5
6
7
8
9
|
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 земля? Возможно нет. «До следующего раза, береги себя.
Ссылка: Scala: Вы частично это понимаете? от нашего партнера JCG Алекса Стейвли в блоге Techlin в Дублине .