Статьи

Подпись Reduce () в Цейлоне

Интерфейс Iterable определяет метод с именем fold() с этой сигнатурой:

1
2
Result fold<Result>(Result initial,
        Result accumulating(Result partial, Element elem))

Где Element — это тип элемента Iterable . Этот метод принимает начальное значение и функцию накопителя, которая применяется к каждому элементу итерируемого объекта по очереди. Например:

1
Integer sum = (1..10).fold(0, plus<Integer>);

Иногда нам не нужно начальное значение, так как мы можем просто начать накапливаться с первого элемента. Следуя соглашению, используемому Scala и F #, давайте назовем эту функцию reduce() . Тогда мы хотели бы иметь возможность написать:

1
Integer sum = (1..10).reduce(plus<Integer>);

Но какой должна быть подпись этого метода? Первый удар может дать нам:

1
Element reduce(Element accumulating(Element partial, Element elem))

Но эта подпись немного более строгая, чем должна быть. Совершенно разумно, чтобы тип результата reduce() был супертипом типа элемента. Scala обрабатывает это, используя ограничение типа нижней границы. Транслитерируя это на Цейлон, используя воображаемый синтаксис для нижних границ, это будет выглядеть так:

1
2
Result reduce<Result>(Result accumulating(Result partial, Element elem))
        given Result abstracts Element

Здесь ограничение нижней границы гарантирует, что первый элемент назначается первому параметру функции аккумулятора. Но у Цейлона нет ограничений типа нижней границы. Почему? Ну, потому что кажется, что на практике мы почти всегда можем использовать типы объединения для достижения того же эффекта. Итак, давайте попробуем это:

1
2
Result|Element reduce<Result>(
        Result accumulating(Result|Element partial, Element elem))

Теперь давайте попробуем реализовать эту подпись. Одна из возможностей будет:

1
2
3
4
5
6
7
8
9
Result|Element reduce<Result>(
        Result accumulating(Result|Element partial, Element elem)) {
    assert (!empty, is Element initial = first);
    variable Result|Element partial = initial;
    for (elem in rest) {
        partial = accumulating(partial, elem);
    }
    return partial;
}

Утверждение обрабатывает случай пустого Iterable , что приводит к AssertionException если у итерируемого объекта нет первого элемента.

В качестве альтернативы, мы могли бы предпочесть возвратить null в случае пустой Iterable , что предполагает следующую реализацию:

01
02
03
04
05
06
07
08
09
10
11
12
13
Result|Element|Null reduce<Result>(
        Result accumulating(Result|Element partial, Element elem)) {
    if (!empty, is Element initial = first) {
        variable Result|Element partial = initial;
        for (elem in rest) {
            partial = accumulating(partial, elem);
        }
        return partial;
    }
    else {
        return null;
    }
}

Возвращаясь к Scala, мы замечаем, что в Scala есть две версии reduce() , которые в точности аналогичны двум возможностям, которые мы только что видели. Первая версия выдает исключение в пустом случае, а вторая версия, reduceOption() , возвращает экземпляр класса-оболочки Option .

Но на Цейлоне мы можем добиться большего. В Цейлоне Iterable имеет слегка загадочно выглядящий параметр второго типа, названный Absent , с верхней границей, given Absent satisfies Null . Iterable<T,Null> , который мы обычно пишем {T*} , является, возможно, пустым итерируемым. Iterable<T,Nothing> , который мы обычно пишем {T+} , — это итерация, которую мы знаем как непустую.

Таким образом, мы приходим к следующему определению reduce() :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
Result|Element|Absent reduce<Result>(
        Result accumulating(Result|Element partial, Element elem)) {
    value initial = first;
    if (!empty, is Element initial) {
        variable Result|Element partial = initial;
        for (elem in rest) {
            partial = accumulating(partial, elem);
        }
        return partial;
    }
    else {
        return initial;
    }
}

Теперь для «составного» выражения диапазона, такого как 1..n , которое непусто, мы получаем ненулевой тип возвращаемого значения:

1
Integer sum = (1..n).reduce(plus<Integer>);

С другой стороны, для «сегментированного» выражения диапазона, такого как 1:n , которое возможно пусто, мы получаем необязательный тип возвращаемого значения:

1
Integer? sum = (1:n).reduce(plus<Integer>);

Лучше всего, это никогда не бросает исключения. Это, я смиренно предполагаю, Довольно Черт, Ницца.

Обратите внимание, как много типов профсоюзов делают для нас здесь. По сравнению с Scala reduceOption() / reduceOption() , они позволяют нам исключить:

  • ограничение типа нижней границы,
  • вторая, эффективно перегруженная, версия метода и
  • Option класса оболочки.

Я добавил это определение reduce() в Iterable , и оно будет доступно в следующем выпуске Ceylon.