Emir Kası ifadelerinin ve diğer döngü kontrollerinin işlevsel eşdeğerleri nelerdir?


36

Diyelim ki, ben aşağıdaki mantığı var. İşlevsel Programlamaya nasıl yazılır?

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

Blogların çoğundaki örnekler, makaleler ... Görüyorum ki basit bir basit ileri matematik fonksiyonunun 'Sum' yazdığını basitçe açıklıyor. Ancak, Java'da yazılmış olanlara benzer bir mantığa sahibim ve bunu Clojure'da işlevsel koda geçirmek istiyorum. Yukarıdakileri FP'de yapamazsak, FP için yapılan promosyonlar bunu açıkça ifade etmez.

Yukarıdaki kodun tamamen zorunlu olduğunu biliyorum. Gelecekte FP'ye geçirme öngörüsü ile yazılmamıştır.


1
Kombinasyonunun breakve return answerbir returniç döngü ile değiştirilebileceğini unutmayın . FP size örneğin bkz devamlılık kullanarak bu erken dönmek uygulamak en.wikipedia.org/wiki/Continuation
Giorgio

1
@ Giorgio'nun sürekliliği burada çok büyük bir fazlalık olur. Zaten bir döngü, bir sonraki yinelemeyi çağırmak için bir kuyruk çağrısı yaparsınız, bu yüzden sizi kırmak için artık onu aramayın ve sadece cevabı geri gönderin. İçin iç içe kullanmak yeniden yapılandırmak için kodunuzu devamlılık yerine ağırlaşmasını kullanabilir nerede döngüler veya diğer karmaşık kontrol akışı, işte bu her zaman mümkün olmalı, ama aşırı karmaşık kod yapısına yol açabilir basit teknikle (yukarıda ki olur az çok anlaşılabilir bir devamı ve birden fazla çıkış noktası için kesinlikle onlara ihtiyacınız olacaktır.
Ness

8
Bu durumda: takeWhile.
Jonathan,

1
@WillNess: Sadece bunu söylemek istedim çünkü herhangi bir noktada karmaşık bir hesaplama yapmak için kullanılabilir. OP'nin somut örneği için muhtemelen en iyi çözüm bu değildir.
Giorgio

@Giorgio haklısın, genel olarak en kapsamlı olanı. Aslında bu soru çok geniş, IYKWIM (yani, SO'da bir kalp atışıyla kapatılacak).
Ness

Yanıtlar:


45

İşlevsel dillerin çoğunda bir dizi üzerinde döngü oluşturmaya en yakın eşdeğer bir foldişlevdir, yani dizinin her değeri için kullanıcı tarafından belirlenen bir işlevi çağıran ve zincir boyunca biriken bir değeri geçen bir işlevdir. Birçok fonksiyonel dilde, foldbazı durumlar ortaya çıktığında erken durma seçeneği de dahil olmak üzere, ekstra özellikler sağlayan çeşitli ek işlevlerle genişletilir. Tembel dillerde (örneğin Haskell) erken durmak, liste boyunca daha fazla değerlendirme yapmamak suretiyle başarılabilir; bu, asla ek değerlerin üretilmemesine neden olur. Bu nedenle, örneğinizi Haskell'e çevirmek, şöyle yazarım:

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

Haskin'in sözdizimini bilmediğiniz takdirde, bu satır satır satır aşılması, şunun gibi çalışır:

doSomeCalc :: [Int] -> Int

Fonksiyonların türünü, bir girdi listesini kabul edip tek bir int döndürerek tanımlar.

doSomeCalc values = foldr1 combine values

Fonksiyonun ana gövdesi: verilen argüman values, argümanlarla foldr1çağrılan dönüş combine(aşağıda tanımlayacağımız) ve values. foldr1kat ilkel bir çeşididir listenin birinci değere akümülatör seti ile başlar (dolayısıyla 1işlev adı), o zaman, genellikle bir denir soldan sağa fonksiyon belirtilen kullanıcı (kullanarak birleştiren doğru kat , dolayısıyla rişlev adına girilir). Yani foldr1 f [1,2,3]eşdeğerdir f 1 (f 2 3)(veya f(1,f(2,3))daha geleneksel C benzeri sözdiziminde).

  where combine v1 v2 | v1 == 10  = v1

combineYerel işlevi tanımlama : iki argüman alır v1ve v2. v110 olduğunda , sadece döner v1. Bu durumda, v2 asla değerlendirilmez , bu nedenle döngü burada durur.

                      | v1 == 150 = v1 + 100 + v2

Alternatif olarak, v1 150 olduğunda, buna fazladan 100 eklenir ve v2 eklenir.

                      | otherwise = v1 + v2

Ve bu koşullardan hiçbiri doğru değilse, sadece v1 ila v2 ekler.

Şimdi, bu çözüm Haskell'e özgüdür, çünkü birleştirme işlevinin ikinci argümanı değerlendirmemesi durumunda sağ katlamanın sona ermesi Haskell'in tembel değerlendirme stratejisinden kaynaklanmaktadır. Clojure'u tanımıyorum, ancak katı değerlendirme kullandığına inanıyorum, bu nedenle foldstandart kütüphanesinde erken sonlandırma için özel destek içeren bir işlevi olmasını bekliyorum . Bu genellikle denir foldWhile, foldUntilya da benzer.

Clojure kütüphane dokümantasyonuna hızlı bir bakış, adlandırmadaki çoğu işlevsel dilden biraz farklı olduğunu ve foldaradığınız şey olmadığını (paralel hesaplamayı amaçlayan daha gelişmiş bir mekanizma olduğunu), ancak reducedaha doğrudan olduğunu göstermektedir. eşdeğer. reducedİşlev, birleştirme işlevinizde çağrılırsa erken sonlandırma gerçekleşir . % 100 sözdizimini anladığımdan emin değilim, ancak aradığınız şeyin şunun gibi olduğundan şüpheleniyorum:

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

Not: Her iki çeviri, Haskell ve Clojure, bu özel kod için tam olarak doğru değildir; ancak bunun genel anlamını aktarırlar - bu örneklerle ilgili belirli problemler için aşağıdaki açıklamalarda tartışmaya bakınız.


11
isimler v1 v2kafa karıştırıcı: v1"diziden bir değer", ancak v2birikmiş sonuçtur. ve çeviriniz hatalı, inanıyorum ki, OP'nin döngüsü birikmiş (soldan) değer dizideki bazı öğelere değil, 10'a ulaştığında çıkar . 100'lük artışlarla aynı. Burada kıvrımlar kullanılıyorsa, erken çıkışlı sol kıvrımı kullanın, foldlWhile burada bazı değişiklikler olabilir .
Ness,

2
En yanlış cevabın SE ile ilgili en çok oy alanını alması çok komik… .... hata yapmak sorun değil, sen de iyi bir insansın :) . Ancak SO / SE'deki bilgi keşif mekanizması kesinlikle bozuluyor.
Ness

1
Clojure kodu neredeyse doğrudur, ancak (= v1 150)kullanım koşulu önceki v2(aka e) değeri ona göre toplanır.
NikoNyrh

1
Breaking this down line by line in case you're not familiar with Haskell's syntax-- Kahramanımsın. Haskell benim için bir gizemdir.
Kaptan Adam,

15
@WillNess Yükseltildi, çünkü bu en kolay anlaşılır çeviri ve açıklama. Bunun yanlış olması utanç verici ama göreceli olarak önemsiz çünkü burada küçük hatalar cevabın başka türlü yardımcı olduğu gerçeğini reddetmiyor. Ancak elbette düzeltilmesi gerekiyor.
Konrad Rudolph

33

Kolayca özyinelemeye dönüştürebilirsiniz. Ve güzel kuyruk optimize özyinelemeli çağrı var.

Sahte kod:

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

14
Evet. Bir döngünün işlevsel eşdeğeri kuyruk özyinelemelidir ve bir koşullu için işlevsel eşdeğer hala bir koşulludur.
Jörg W Mittag

4
@ JörgWMittag Kuyruk özyinelemenin işlevsel eşdeğer olduğunu söyleyebilirim GOTO. (Çok kötü değil ama yine de oldukça garip.) Jules'un dediği gibi bir döngünün eşdeğeri uygun bir kıvrımdır.
leftaroundabout

6
@leftaroundabout Ben aslında katılmıyorum. Kuyruk özyinelemesinin kendine gitmekten daha fazla kısıtlı olduğunu söyleyebilirim, kendine atlamak ve sadece kuyruk konumunda olmak. Temelde bir döngü yapısına eşdeğerdir. Genel olarak özyinelemenin eşdeğer olduğunu söyleyebilirim GOTO. Her durumda, kuyruk özyinelemesini derlediğinizde, çoğunlukla while (true)erken dönüşün sadece bir breakifade olduğu işlev gövdesiyle bir döngü oluşturur . Bir kıvrım, bir döngü olduğu konusunda haklıyken, genel bir döngü yapısından daha gerçektir; her bir döngü için daha fazla
J_mie6

1
@ J_mie6 kuyruk özyinelemesini daha fazla düşünmemin nedeni GOTO, gerçekte amaçlandığı şekilde davrandığından emin olmak için hangi durumdaki hangi argümanların özyinelemeli çağrıya iletildiğinin argümanlarını doğru bir şekilde tutmanız gerekir. Düzgünce yazılmış zorunlu döngülerde (durumsal değişkenlerin ne olduğu ve her bir yinelemede nasıl değiştiği oldukça açık), ne de özyinelemeli (bu genellikle argümanlarla pek bir şey yapılmadığında) sonuç oldukça sezgisel bir şekilde birleştirilir). ...
leftaroundabout'ta

1
… Kıvrımlara gelince: haklısın, geleneksel bir kat (katamorfizm) çok özel bir döngü türüdür, ancak bu özyinelemeler genelleştirilebilir (ana / apo- / hilomorfizmalar); topluca bunlar IMO zorunlu döngüler için uygun yerine
leftaroundabout'ta

13

Jules'un cevabını gerçekten seviyorum , ancak ek olarak, insanların tembel işlevsel programlama hakkında sık sık özledikleri bir şeye işaret etmek istedim; bu, her şeyin "döngü içinde" olması gerekmez. Örneğin:

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

Mantığınızın her bir parçasının sonradan oluşan ayrı bir fonksiyonda hesaplanabileceğini görebilirsiniz. Bu, sorun gidermede genellikle daha kolay olan daha küçük fonksiyonlara izin verir. Oyuncak örneğiniz için, belki de bu, çıkardığından daha fazla karmaşıklık ekler, ancak gerçek dünya kodunda, bölünmüş işlevler genellikle bir bütün olarak çok daha basittir.


mantık her tarafa dağılmış durumda. bu kodun bakımı kolay olmayacak. stopAt10olduğu değil iyi bir tüketici. Cevabınız , temel değer üreticisini doğru bir şekilde izole ettiği için verdiğiniz cevaptan daha iyidir scanl (+) 0. Tüketicilerin doğrudan kontrol mantığını içermesi gerekir, ancak sadece iki spansaniye ve bir lastile açıkça uygulanır. Bu da orijinal kod yapısını ve mantığını yakından takip eder ve bakımı kolaydır.
Ness

6

Çoğu liste işleme örnekler gibi kullanım fonksiyonlarını göreceksiniz map, filter, sumvb bir bütün olarak listede faaliyet gösterdikleri. Ancak sizin durumunuzda, şartlı bir erken çıkışa sahipsiniz - normal liste işlemleri tarafından desteklenmeyen oldukça nadir bir desen. Bu yüzden bir soyutlama seviyesini düşürmeniz ve özyinelemeyi kullanmanız gerekiyor - bu da zorunlu örneğin nasıl göründüğüne daha yakın.

Bu, Clojure'a doğrudan (muhtemelen aptalca değil) yapılan bir çeviridir:

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

Düzenleme: dışarı Jules noktası reduceClojure içinde yapılacak erken veda destekler. Bunu kullanmak daha zarif:

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

Her durumda, zorunlu dillerde yapabileceğiniz gibi fonksiyonel dillerde her şeyi yapabilirsiniz, ancak zarif bir çözüm bulmak için zihniyetinizi biraz değiştirmek zorunda kalırsınız. Zorunlu kodlamada adım adım bir liste işlemeyi düşünürken, işlevsel dillerde listedeki tüm öğelere uygulanacak bir işlem ararsınız.


Cevabımı eklediğim düzenlemeye bakın: Clojure'un reduceoperasyonu erken çıkışı destekliyor.
Jules

@Jules: Cool - bu muhtemelen daha deyimsel bir çözümdür.
JacquesB,

Yanlış - ya da takeWhile'ortak bir işlem' değil mi?
Jonathan,

jcast - takeWhilebu yaygın bir işlem olsa da, bu durumda özellikle yararlı değildir, çünkü durup durmamaya karar vermeden önce dönüşümünüzün sonuçlarına ihtiyacınız vardır. Tembel dilinde bu önemli değil: kullanabileceğiniz scanve takeWhiletarama sonuçlarına (kullanım gelmez ise Karl Bielefeldt yanıtını görmek takeWhilekolaylıkla bunu yapmak için yeniden yazılabilir), ancak Clojure gibi sıkı bir dil için bu olurdu tüm listeyi işleme koymak ve daha sonra sonuçları silmek demek. Jeneratör işlevleri bunu çözebilir ve klojürün onları desteklediğine inanıyorum.
Jules

@ Clojure'deki jule take-whiletembel bir sekans üretir (dokümanlara göre). Bununla baş etmenin başka bir yolu ise transdüserler (belki de en iyisi) olacaktır.
Ness

4

Diğer cevapların da belirttiği gibi Clojure, reducedindirimleri erkenden durdurmayı hedefliyor :

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

Özel durumunuz için en iyi çözüm budur. Ayrıca birleştirerek gelen kilometre bir sürü alabilirsiniz reducedile transducesize gelen dönüştürücüler kullandığınız sağlar, map, filterbu genel sorunuza tam bir cevap uzaktır Ancak vb.

Kaçış sürekliliği, break ve return ifadelerinin genelleştirilmiş bir sürümüdür. Doğrudan bazı Şemalar ( call-with-escape-continuation), Common Lisp ( block+ return, catch+ throw) ve hatta C ( setjmp+ longjmp) ile uygulanmaktadırlar. Standart şemada olduğu gibi Haskell ve Scala'daki devam monadları gibi daha genel sınırlandırılmış veya sınırlandırılmamış süreklilikler kaçış sürekliliği olarak da kullanılabilir.

Örneğin, Raket'te şöyle kullanabilirsiniz let/ec:

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

Diğer birçok dilde istisnaların işlenmesi şeklinde devam eden benzeri yapılar bulunmaktadır. Haskell'de ayrıca çeşitli hata monadlarından birini kullanabilirsiniz foldM. Öncelikle istisnalar ya da erken iadeler için hata monadlarını kullanan hata yönetimi yapıları oldukları için, genellikle kültürel olarak kabul edilemez ve muhtemelen oldukça yavaş.

Ayrıca, üst düzey işlevlerden kuyruk çağrılarına kadar düşürebilirsiniz.

Döngüleri kullanırken, döngü gövdesinin sonuna ulaştığınızda otomatik olarak bir sonraki yinelemeye girersiniz. Bir sonraki yinelemeye erken girebilir continueveya break(veya return) ile döngüden çıkabilirsiniz . Kuyruk çağrıları kullanılırken (veya Clojure'un loopkuyruk yinelemeyi taklit eden yapısını) kullanırken, bir sonraki yinelemeye girmek için her zaman açık bir arama yapmanız gerekir. Döngüyü durdurmak için sadece özyinelemeli arama yapmaz, doğrudan değeri verirsiniz:

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

1
Haskell'de hata monadlarını kullanırken, burada herhangi bir gerçek performans cezası olduğuna inanmıyorum. İstisnaların ele alınması hatları üzerinde düşünmeye meyillidirler, ancak aynı şekilde çalışmazlar ve herhangi bir yığın yürüyüşü gerekmez, bu yüzden bu şekilde kullanılırsa gerçekten bir sorun olmamalıdır. Ayrıca, böyle bir şeyi kullanmamak için kültürel bir neden olsa bile MonadError, temelde eşdeğeri Eithersadece hata işlemeye karşı böyle bir önyargıya sahip değildir, bu yüzden kolayca bir yedek olarak kullanılabilir.
Jules

@Jules, Sola dönmenin katlamanın tüm listeyi (veya diğer dizileri) ziyaret etmesini engellemediğini düşünüyorum. Haskell standart kütüphane içindekiler ile yakından ilgili değil.
nilern

2

Karmaşık kısım döngüdür. Şununla başlayalım. Bir döngü genellikle, yinelemeyi tek bir fonksiyonla ifade ederek işlevsel stile dönüştürülür. Bir yineleme döngü değişkeninin bir dönüşümüdür.

İşte genel bir döngünün işlevsel bir uygulaması:

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

(Döngü değişkeninin başlangıç ​​değeri, tek bir yinelemeyi ifade eden fonksiyon [döngü değişkeninde]) alır (döngüye devam etme koşulu).

Örnekte, aynı zamanda sonlanan bir dizi üzerinde bir döngü kullanılıyor. Zorunlu dilin içindeki bu yetenek dilin kendisine aittir. İşlevsel programlamada, bu yetenek genellikle kütüphane seviyesinde uygulanır. İşte olası bir uygulama

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

İçinde :

Dışarıda görülebilen loop değişkenini ve bu fonksiyonun gizlediği dizideki pozisyonu içeren bir ((val, next_pos)) çifti kullanırım.

Yineleme işlevi genel döngüde olduğundan biraz daha karmaşıktır, bu sürüm dizinin geçerli öğesini kullanmayı mümkün kılar. [ Curried formunda]

Bu fonksiyonlara genellikle "katlama" adı verilir.

Dizideki elementlerin birikiminin soldan bağlantılı bir şekilde yapıldığını belirtmek için isminde bir "l" koyuyorum; zorunlu programlama dillerinin alışkanlıklarını taklit etmek için diziyi düşükten yükseğe dizine yineleme.

Bu kıvrım sürümünün, döngünün ne zaman durdurulup durdurulmayacağını ve ne zaman olacağını kontrol eden bir koşul aldığını belirtmek için bir "c" harfi koydum.

Elbette, bu tür faydalı fonksiyonların, kullanılan işlevsel programlama dili ile birlikte verilen temel kütüphanede hazır bulunması muhtemeldir. Onları gösteri için buraya yazdım.

Artık zorunlu durumda dilde olan tüm araçlara sahip olduğumuza göre, örneğinizin belirli işlevlerini yerine getirebiliriz.

Döngünüzdeki değişken bir çifttir ('cevap', devam edip etmeyeceğini kodlayan bir boolean).

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)

Yeni bir "değişken" "new_answer" kullandığımı unutmayın. Bunun nedeni, işlevsel programlamada önceden başlatılmış bir "değişken" in değerini değiştiremememdir. Performans hakkında endişelenmiyorum, derleyici daha etkili olduğunu düşünüyorsa, 'new_answer' için 'cevap' hafızasını tekrar tekrar kullanabilir.

Bunu daha önce geliştirilen loop fonksiyonumuza dahil etmek:

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

Burada "Array" işlevi, foldlc işlevini veren modül adıdır.

"fist", "second", pair parametresinin birinci, ikinci bileşenini döndüren işlevleri belirtir

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

Bu durumda "puansız" stil doSomeCalc uygulamasının okunabilirliğini arttırır:

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

(>>>) işlev bileşimidir: (>>>) : (a -> b) -> (b -> c) -> (a -> c)

Yukarıdakiyle aynıdır, sadece "arr" parametresi tanımlayıcı denklemin her iki tarafından bırakılmıştır.

Son bir şey: vaka kontrolü (array == null). Daha iyi tasarlanmış programlama dillerinde, ancak bazı temel disipline sahip kötü tasarlanmış dillerde bile, var olmayanları ifade etmek için isteğe bağlı bir tür kullanılır . Bunun, sorunun nihayetinde olduğu fonksiyonel programlama ile ilgisi yok, bu yüzden onunla ilgilenmiyorum.


0

İlk olarak, döngüyü hafifçe yeniden yazın, böylece döngünün her tekrarlaması erken çıkar veya answertam olarak bir kez mutasyon yapar :

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

Bu sürümün davranışının eskisi gibi olduğu açık olmalı, ancak şimdi, özyinelemeli stile dönüştürmek çok daha kolay. İşte doğrudan bir Haskell çevirisi:

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

Şimdi tamamen işlevseldir, ancak açık bir özyineleme yerine bir katlama kullanarak hem verimlilik hem de okunabilirlik açısından geliştirebiliriz:

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)

Bu bağlamda, Leftdeğeri ile erken çıkar ve Rightözyinelemeye değeri ile devam eder.


Bu şimdi biraz daha basitleştirilebilir, şöyle:

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

Bu, son Haskell kodu olarak daha iyidir, ancak şimdi orijinal Java ile nasıl eşleştiği biraz daha az belirgindir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.