Каковы функциональные эквиваленты обязательных операторов завершения и других проверок петли?

Скажем, я имею ниже логики. Как написать это в Функциональном
программировании?

```
    public int doSomeCalc(int[] array)
    {
        int answer = 0;
        if(array!=null)
        {
            for(int e: array)
            {
                answer += e;
                if(answer == 10) break;
                if(answer == 150) answer += 100;
            }
        }
        return answer;
    }

```

Примеры в большинстве блогов, статьях... Я вижу, просто
объясняет, что простой случай одной прямой математической функции
говорит 'Сумму'. Но, я имею логику, подобную вышеупомянутому,
написанному на Яве, и хотел бы мигрировать это к функциональному
коду в Clojure. Если мы не можем сделать вышеупомянутого в FP, то
вид продвижений для FP не указывает это явно.

Я знаю, что вышеупомянутый код полностью обязателен. Это не было
написано с предусмотрительностью перемещения его к FP в
будущем.

Самый близкий эквивалент перекручиванию по множеству на
большинстве функциональных языков - `сгиб` функция, т.е.
функция, которая вызывает определенную пользователями функцию для
каждой ценности множества, передавая накопленное значение вдоль
цепи. На многих функциональных языках, `сворачиваются`
увеличен множеством дополнительных функций, которые обеспечивают
дополнительные функции, включая выбор остановиться рано, когда
некоторое условие возникает. На ленивых языках (например, Хаскелл),
останавливаясь рано может быть достигнут просто, не оценив дальше
вдоль списка, который заставит дополнительные ценности никогда не
производиться. Поэтому переводя ваш пример Хаскеллу, я написал бы
его как:

```
doSomeCalc :: [Int] -> Int
doSomeCalc values = foldr1 combine values
  where combine v1 v2 | v1 == 10  = v1
                      | v1 == 150 = v1 + 100 + v2
                      | otherwise = v1 + v2

```

Разрушение этого линию за линией в случае, если вы не знакомы с
синтаксисом Хаскелла, это работает как это:

```
doSomeCalc :: [Int] -> Int

```

Определяет тип функции, принимая список ints и возвращая
единственный интервал.

```
doSomeCalc values = foldr1 combine values

```

Основная часть функции: учитывая аргумент `ценности`
, возвратите `foldr1` названный с аргументами
`объединение` (который мы определим ниже), и
`ценности` . `foldr1` является вариантом
сгиба, примитивного, который начинается с набора сумматора к первой
ценности списка (следовательно `1` на имя функции),
затем объединяет его, используя пользователя определенная функция
слева направо (который обычно называют *правильным сгибом* ,
следовательно `r` на имя функции). Так `foldr1 f
[1,2,3]` эквивалентно `f 1 (f 2 3)` (или `f
(1, f (2,3))` в более обычном подобном C синтаксисе).

```
  where combine v1 v2 | v1 == 10  = v1

```

Определение `объединение` местная функция: это
получает два аргумента, `v1` и `v2` . Когда
`v1` равняется 10, это просто возвращает `v1`
. В этом случае *v2 никогда не оценивается* , таким образом,
петля останавливается здесь.

```
                      | v1 == 150 = v1 + 100 + v2

```

Альтернативно, когда v1 равняется 150, добавляют дополнительные
100 к нему и добавляют v2.

```
                      | otherwise = v1 + v2

```

И, если ни одно из тех условий не верно, просто добавляет v1 к
v2.

Теперь, это решение несколько характерно для Хаскелла, потому
что то, что правильный сгиб заканчивается, если объединяющаяся
функция не оценивает свой второй аргумент, вызывается стратегией
отложенных вычислений Хаскелла. Я не знаю Clojure, но я полагаю,
что он использует строгую оценку, таким образом, я ожидал бы, что
он будет иметь `, сгиб` функционирует в его стандартной
библиотеке, которая включает определенную поддержку раннего
завершения. Это часто называют `foldWhile` ,
`foldUntil` или подобный.

Беглый взгляд на документацию библиотеки Clojure предполагает,
что это немного отличается от большинства функциональных языков в
обозначении, и что `, сгиб` не то, что вы ищете (это -
более современный механизм, нацеленный на предоставление
возможности параллельного вычисления), но [`уменьшить`](https://clojuredocs.org/clojure.core/%D1%83%D0%BC%D0%B5%D0%BD%D1%8C%D1%88%D0%B8%D1%82%D1%8C) более прямой
эквивалент. Раннее завершение происходит, если
`уменьшил` , функция вызвана в вашей функции
объединения. Я не на 100% уверен, что понимаю синтаксис, но я
подозреваю то, что вы ищете, что-то вроде этого:

```
(reduce 
    (fn [v1 v2]
        (if (= v1 10) 
             (reduced v1)
             (+ v1 v2 (if (= v1 150) 100 0))))
    array)

```

NB: оба перевода, Хаскелл и Клоджьюр, не совсем правильные для
этого определенного кода; но они передают общую суть его -
посмотрите обсуждение в комментариях ниже для определенных проблем
с этими примерами.



Вы могли легко преобразовать его в рекурсию. И у этого есть
хороший оптимизированный хвостом рекурсивный вызов.

Псевдокод:

```
public int doSomeCalc(int[] array)
{
    return doSomeCalcInner(array, 0);
}

public int doSomeCalcInner(int[] array, int answer)
{
    if (array is empty) return answer;

   //not sure how to efficiently implement head/tails array split in clojure
    var head = array[0]//first element of array
    var tail = array[1..]//remainder of array

    answer += head;
    if (answer == 10) return answer;
    if (answer == 150) answer += 100;

    return doSomeCalcInner(tail, answer);
}

```



Мне действительно нравится [Ответ
в июлях](https://softwareengineering.stackexchange.com/a/363320/3965), но я хотел дополнительно указать на что-то, что люди
часто пропускают о ленивом функциональном программировании, которое
является, что все не должно быть "в петле". Например:

```
baseSums = scanl (+) 0

offsets = scanl (\offset sum -> if sum == 150 then offset + 100 else offset) 0

zipWithOffsets xs = zipWith (+) xs (offsets xs)

stopAt10 xs = if 10 `elem` xs then 10 else last xs

result = stopAt10 . zipWithOffsets . baseSums

result [1..]         -- 10
result [11..1000000] -- 500000499945

```

Вы видите, что каждая часть вашей логики может быть вычислена в
отдельной функции, тогда составленной вместе. Это допускает меньшие
функции, которые обычно намного легче расследовать. Для вашего
игрушечного примера возможно это добавляет больше сложности, чем
это удаляет, но в реальном мире кодируют разделение, обособленно
функционирует, часто намного более просты, чем целое.



Большинство примеров обработки списков вы будете видеть функции
использования как `карта` , `фильтр` ,
`сумма` и т.д., которые воздействуют на список в целом.
Но в вашем случае у вас есть условный ранний выход - довольно
необычный образец, который не поддерживается обычными операциями по
списку. Таким образом, необходимо уронить уровень абстракции и
рекурсию использования - который также ближе к тому, как
обязательный пример смотрит.

Это довольно прямое (вероятно, не идиоматичный) перевод на
Clojure:

```
(defn doSomeCalc 
  ([lst] (doSomeCalc lst 0))
  ([lst sum]
    (if (empty? lst) sum
        (if (= sum 10) sum
            (let [sum (+ sum (first lst))]
                 [sum (if (= sum 150) (+ sum 100) sum)]
               (recur (rest lst) sum))))))) 

```

Править: Жюль указывает, что `уменьшает` в Clojure
*делают* , поддерживают ранний выход. Используя это более
изящно:

```
(defn doSomeCalc [lst]  
  (reduce (fn [sum val]
    (if (= sum 10) (reduced sum)
        (let [sum (+ sum val)]
             [sum (if (= sum 150) (+ sum 100) sum)]
           sum))
   lst)))

```

В любом случае можно сделать что-либо на функциональных языках,
как вы можете на императивных языках, но часто необходимо изменять
мышление несколько, чтобы найти изящное решение. В императиве,
кодирующем вас, думают об обработке списка шаг за шагом, в то время
как на функциональных языках вы ищете операцию, чтобы относиться ко
всем элементам в списке.



Как указано другими ответами, Clojure имеет
`уменьшенный` для остановки сокращений рано:

```
(defn some-calc [coll]
  (reduce (fn [answer e]
            (let [answer (+ answer e)]
               (case answer
                 10  (reduced answer)
                 150 (+ answer 100)
                 answer)))
          0 coll))

```

Это - лучшее решение для вашей конкретной ситуации. Можно также
добраться, большой пробег от объединяющегося `уменьшил`
с `преобразовывают` , который позволяет вам использовать
преобразователи из `карта` , `фильтр` и т.д.
Однако, это далеко от полного ответа до вашего общего вопроса.

Продолжения спасения - обобщенная версия разрыва и возвращения
заявления. Они непосредственно осуществляются в некоторых Схемах (
`требование с продолжением спасения` ), язык Common LISP
( `блок` + `возвращение` ,
`выгода` + `бросок` ) и даже C (
`setjmp` + `longjmp` ). Более общие
разграниченные или неразграниченные продолжения, как найдено в
стандартной Схеме или как монады продолжения в Хаскелле и Скале
могут также использоваться в качестве продолжений спасения.

Например, в Ракетке вы могли использовать `,
позволяют/ЕС` как это:

```
(define (some-calc ls)
  (let/ec break ; let break be an escape continuation
    (foldl (lambda (answer e)
             (let ([answer (+ answer e)])
               (case answer
                 [(10)  (break answer)] ; return answer immediately
                 [(150) (+ answer 100)]
                 [else  answer])))
           0 ls)))

```

У многих других языков также есть продолжение спасения - как
конструкции в форме обработки исключений. В Хаскелле вы могли также
использовать одну из различных ошибочных монад с `foldM`
. Поскольку они - прежде всего, конструкции обработки ошибок,
используя исключения, или ошибочные монады для ранней прибыли
обычно культурно недопустимое и возможно довольно медленный.

Можно также опуститься от функций высшего порядка до последних
вызовов.

Используя петли, вы входите в следующее повторение
автоматически, когда вы достигаете конца тела цикла. Можно войти,
следующее повторение рано с `продолжают` или выходят из
петли с `разрыв` (или `возвращение` ).
Используя последние вызовы (или Клоджьюр `петля`
конструкция, которая подражает хвостовой рекурсии), необходимо
всегда делать явный звонок, чтобы войти в следующее повторение.
Чтобы остановить перекручивание, вы просто не делаете рекурсивного
звонка, но даете стоимость непосредственно:

```
(defn some-calc [coll]
  (loop [answer 0, [e es :as coll] coll]
    (if (empty? coll)
      answer
      (let [answer (+ answer e)]
        (case answer
          10 answer
          150 (recur (+ answer 100) es)
          (recur answer es))))))

```



Запутанная часть - петля. Давайте начнем с этого. Петля обычно
преобразовывается в функциональный стиль, выражая повторение с
единственной функцией. Повторение - преобразование переменной
цикла.

Вот функциональное внедрение общей петли:

```
loop : v -> (v -> v) -> (v -> Bool) -> v
loop init iter cond_to_cont = 
    if cond_to_cont init 
        then loop (iter init) iter cond
        else init

```

Это берет (начальное значение переменной цикла, функция, которая
выражает единственное повторение [на переменной цикла]) (условие
продолжить петлю).

Ваш пример использует петлю на множестве, которое также
ломается. Эта способность на вашем императивном языке испеклась на
сам язык. В функциональном программировании такая способность
обычно осуществляется на уровне библиотеки. Вот возможное
внедрение

```
module Array (foldlc) where

foldlc : v -> (v -> e -> v) -> (v -> Bool) -> Array e -> v
foldlc init iter cond_to_cont arr = 
    loop 
        (init, 0)
        (λ (val, next_pos) -> (iter val (at next_pos arr), next_pos + 1))
        (λ (val, next_pos) -> and (cond_to_cont val) (next_pos < size arr))

```

В нем:

Я использую ((val, next\_pos)) пара, которая содержит переменную
цикла, видимую снаружи и положение во множестве, которое скрывает
эта функция.

Итеративная функция немного более сложна, чем в общей петле, эта
версия позволяет использовать текущий элемент множества. [Это
находится в [с приправой карри](https://en.wikipedia.org/wiki/Currying) форма.]

Такие функции обычно называют "сгибом".

Я поместил "l" в имя, чтобы указать, что накопление элементов
множества сделано левоассоциативным способом; подражать привычке к
императивным языкам программирования повторить множество от низко
до высокого индекса.

Я поместил "c" в имя, чтобы указать, что эта версия сгиба берет
условие, которое управляет если и когда петля, которая будет
остановлена рано.

Конечно, такие сервисные функции, вероятно, будут легко доступны
в основной библиотеке, отправленной с используемым языком
функционального программирования. Я написал им здесь для
демонстрации.

Теперь, когда у нас есть все инструменты, которые находятся на
языке в обязательном случае, мы можем повернуться, чтобы
осуществить определенную функциональность вашего примера.

Переменная в вашей петле - пара ('ответ', булево, которое
кодирует, продолжить ли).

```
iter : (Int, Bool) -> Int -> (Int, Bool)
iter (answer, cont) collection_element = 
  let new_answer = answer + collection_element
  in case new_answer of
    10 -> (new_answer, false)
    150 -> (new_answer + 100, true)
    _ -> (new_answer, true)

```

Обратите внимание, что я использовал новую "переменную"
'new\_answer'. Это вызвано тем, что в функциональном
программировании я не могу изменить ценность уже инициализированной
"переменной". Я не волнуюсь о работе, компилятор может добраться,
чтобы снова использовать память об 'ответе' для 'new\_answer' через
пожизненный анализ, если это думает, что это более эффективно.

Слияние этого в нашу функцию петли, развитую ранее:

```
doSomeCalc :: Array Int -> Int
doSomeCalc arr = fst (Array.foldlc (0, true) iter snd arr)

```

"Множество" здесь - имя модуля, которое экспортирует функцию
foldlc.

"кулак", "второй" стенд для функций, который возвращает первый,
второй компонент его параметра пары

```
fst : (x, y) -> x
snd : (x, y) -> y

```

В этом случае стиль "без пунктов" увеличивает удобочитаемость
внедрения doSomeCalc:

```
doSomeCalc = Array.foldlc (0, true) iter snd >>> fst

```

(>>>) is function composition : `(>>>) :
(a -> b) -> (b -> c) -> (a -> c)`

Это совпадает с выше, просто параметр "прибытия" оставляют из
обеих сторон уравнения определения.

Одна последняя вещь: проверка случай (выстраивают == пустой
указатель). На лучше разработанных языках программирования, но даже
на ужасно разработанных языках с некоторой основной дисциплиной
каждый скорее использует [дополнительный тип](https://en.wikipedia.org/wiki/Option_type) выражать небытие. Это
не имеет непосредственное отношение к функциональному
программированию, о котором вопрос в конечном счете, таким образом
я не имею дело с ним.