Статьи

Скала: Ты частично понимаешь это?

Почти каждый, кто изучает 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 = true
 
scala> 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
//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);
         }
    }   
}

Теперь давайте создадим класс, который будет связывать обработчики.

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 в Дублине .