Каковы функциональные эквиваленты императивных операторы Break и другие проверки петли?

Позвольте'ы сказать, Я'вэ указанных ниже логики. Как пишут, что в функциональном программировании?

    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;
    }

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

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

Комментарии к вопросу (5)

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

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

Разорвать этот вниз на одну строку в случае, если вы'повторно не знакомы с Haskell'ы синтаксис, это работает так:

doSomeCalc :: [Int] -> Int

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

doSomeCalc values = foldr1 combine values

На корпусе функция: данный параметр значения, возвращает foldr1 вызываться с аргументами объединить (мы'МР определение ниже) и ценности. foldr1 есть вариант сложить примитивны, что начинается с аккумулятором установлен на первое значение списка (отсюда и 1 в имени функции), а затем объединить их, используя указанный пользователем функции слева направо (что обычно называют права раза, отсюда и Р в имени функции). Так foldr1 Ф [1,2,3]эквивалентноФ 1 (Ф 2 3)(илиF(1,ф(2,3)) в обычный C-подобный синтаксис).

  where combine v1 v2 | v1 == 10  = v1

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

                      | v1 == 150 = v1 + 100 + v2

Кроме того, когда v1-это 150, добавляет дополнительные 100 до него, и добавляет В2.

                      | otherwise = v1 + v2

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

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

Быстрый взгляд в Clojure библиотека документация предполагает, что это немного отличается от большинства функциональных языков в именовании, и что " раз " это'т то, что вы'вновь ищет (он's более продвинутый механизм, направленный на обеспечение параллельных вычислений), но уменьшить является более прямого эквивалента. Досрочное расторжение происходит, если снижается функция называется в вашей сочетающий в себе функции. Я'м не уверен на 100% я понимаю синтаксис, но я подозреваю, что вы'вновь ищу что-то вроде этого:

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

Примечание: оба перевода, на Haskell и Clojure, не совсем верно для этого конкретного кода, но они передают общую суть -- см. обсуждение в комментариях ниже, для конкретных проблем с этими примерами.

Комментарии (14)

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

Псевдокод :

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);
}
Комментарии (9)

Мне очень нравится Джулс' ответ, но я хотел бы дополнительно отметить, что люди часто не хватает ленивого функционального программирования, который заключается в том, что все не'т должны быть "внутри цикла.&я, например:

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

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

Комментарии (1)

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

Это достаточно прямой (наверное, не идиоматические) перевод в 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)))

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

Комментарии (5)

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

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

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

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

Например, в Racket можно использовать пусть/ЕС такой:

(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)))

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

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

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

(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))))))
Комментарии (2)

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

Здесь представлена функциональная осуществления общего цикла :

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))

В нем :

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

Функция итерации является немного более сложным, чем в общем цикле, эта версия позволяет использовать текущий элемент массива. [В карри форма.]

Такие функции обычно назвал"сложить &quot и;.

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

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

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

Теперь у нас есть все инструменты, которые есть в языке, в повелительном случае, мы можем перейти к реализации специфической функциональности вашему примеру.

Переменная в цикле-это пара ('ответить' логический, который кодирует ли продолжать).

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'. Это потому, что в функциональном программировании не может изменить значение уже инициализированного "и переменной, что". Я не беспокоюсь о производительности, компилятор может сделать для повторного использования памяти 'ответить' и#39;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

(>>>) является функцией состава : (>>>) : (а -> б) -> (б> в) -> (а -> с)

Это то же самое, только в "Башня" и параметр оставить с обеих сторон в определяющие уравнения.

Одна последняя вещь : проверка в случае (массив == значение null). В лучше разработаны языки программирования, но даже в плохо продуманный язык с некоторыми основными дисциплина одна, а использует тип, чтобы выразить небытие. Это не имеет много общего с функциональным программированием, что вопрос в конечном итоге, таким образом, я не бороться с ним.

Комментарии (0)

Во-первых, переписать петля слегка, такой, что каждой итерации цикла либо рано-выходы, или мутирует "ответ" ровно один раз:

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

Это должно быть ясно, что поведение этой версии точно так же, как и раньше, но теперь это'ы гораздо проще преобразовать в рекурсивный стиль. Здесь'с прямым переводом на Haskell:

doSomeCalc :: [Int] -> Int
doSomeCalc = recurse 0
  where recurse :: Int -> [Int] -> Int
        recurse answer [] = answer
        recurse answer (e:array)
          | answer + e == 10 = answer + e
          | answer + e == 150 = recurse (answer + e + 100) array
          | otherwise = recurse (answer + e) array

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

import Control.Monad (foldM)

doSomeCalc :: [Int] -> Int
doSomeCalc = either id id . foldM go 0
  where go :: Int -> Int -> Either Int Int
        go answer e
          | answer + e == 10 = Left (answer + e)
          | answer + e == 150 = Right (answer + e + 100)
          | otherwise = Right (answer + e)

В этом контексте, "влево" рано-выходы с его значением, и "право" продолжает рекурсии с его значением.


Это сейчас можно было бы упростить немного больше, как это:

import Control.Monad (foldM)

doSomeCalc :: [Int] -> Int
doSomeCalc = either id id . foldM go 0
  where go :: Int -> Int -> Either Int Int
        go answer e
          | answer' == 10 = Left 10
          | answer' == 150 = Right 250
          | otherwise = Right answer'
          where answer' = answer + e

Это лучше как финальная кода на Haskell, но это's теперь немного менее ясно, как это карты обратно в исходный Java.

Комментарии (0)