Imperative break statements va boshqa pastadir tekshiruvlarining funktsional ekvivalentlari qanday?

Aytaylik, quyida mantiq bor. Funktsional dasturlashda buni qanday yozish mumkin?

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

Ko'pgina bloglarda, maqolalarda misollar ... Men faqat bitta tekis oldinga matematik funktsiyaning oddiy holatini "Sum" deb aytadi. Ammo yuqorida yozilgan Java-ga o'xshash bir mantiq bor va bu Clojure-da ishlab kodni ko'chirmoqchi. FPda yuqorida keltirilgan ma'lumotlarni qila olmasak, unda FP uchun aktsiyalarning bunday turlari aniq ko'rsatilmaydi.

Yuqorida keltirilgan kodning to'liq bajarilishini bilaman. Kelgusida uni FP ga ko'chirish haqida oldindan o'ylab yozilmagan.

35
@WillNess: Men buni eslatmoqchi edim, chunki u har qanday nuqtada murakkab hisobni qoldirish uchun ishlatilishi mumkin. Ehtimol, OPning aniq misoli uchun eng yaxshi echim bo'lmasligi mumkin.
qo'shib qo'ydi muallif Giorgio, manba
break va return javob kombinatsiyasini loop ichida return bilan almashtirilishi mumkinligini unutmang. FPda bu davomiylikni ishlatishingiz mumkin, masalan, masalan. en.wikipedia.org/wiki/Continuation
qo'shib qo'ydi muallif Giorgio, manba
Bunday holda: takeWhile .
qo'shib qo'ydi muallif John Washburn, manba
@Giorgio siz haqsiz, umuman olganda, bu eng keng qamrovli. Aslida, bu savol juda keng, IYKWIM (ya'ni, yurak otishni o'rganish paytida SOda yopiq bo'ladi).
qo'shib qo'ydi muallif Rex Space, manba
@Giorjio davomiyligi bu erda juda katta mag'lub bo'lishi mumkin. Bu sizning oldingi ierarxiyangizga qo'ng'iroq qilish uchun, sizning quyruq chaqirig'ini chaqirishingiz uchun, sizni sinchkovlik bilan chaqirish uchun javob bermang. Yuqoridagi oddiy usullardan foydalanish uchun kodingizni qayta tuzish o'rniga, davomiylikni ishlatishingiz mumkin (bu har doim ham bo'lishi mumkin, lekin juda murakkab kodga olib kelishi mumkin), yoki ichki loops yoki boshqa murakkab boshqaruv oqimi uchun ko'pincha aniqroq ni davom ettiradigan struktura va bir nechta chiqish nuqtasiga albatta kerak bo'lasiz).
qo'shib qo'ydi muallif Rex Space, manba

6 javoblar

Ko'pgina funktsional tillarda qatorga loopning eng yaqin ekvivalenti fold funktsiyasidir, ya'ni zanjir bo'ylab to'plangan qiymatdan o'tib, qatorning har bir qiymati uchun foydalanuvchi tomonidan belgilangan funktsiyani chaqiradigan funksiya. Ko'pgina funktsional tillarda fold qo'shimcha funktsiyalarni taqdim etuvchi turli xil qo'shimcha funktsiyalar bilan kuchaytiriladi, jumladan, ba'zi shartlar paydo bo'lganda erta to'xtatish imkoniyati. Dajjolli tillarda (masalan, Haskell) erta turishni oddiygina ro'yxat bo'yicha hech qanday baholashni amalga oshirmasdan erishish mumkin, bu esa qo'shimcha qiymatlarni hech qachon yaratib bo'lmaydi. Shuning uchun, sizning misolingizni Haskellga tarjima qilib, uni quyidagi kabi yozaman:

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

Agar siz Haskell sintaksisiga tanish bo'lmagan bo'lsangiz, bu pastga qarab chiziqni kesishingiz mumkin, bu shunday ishlaydi:

doSomeCalc :: [Int] -> Int

Funksiyaning turini belgilaydi, ints ro'yxatini qabul qiladi va bitta int ni qaytaradi.

doSomeCalc values = foldr1 combine values

Funktsiyaning asosiy qismi: values ​​ berilgan argument, foldr1 argumentli birlashtiramiz (quyida biz belgilaydigan) va qiymatlari . foldr1 , ro'yxatning birinchi qiymatiga (ya'ni, funktsiya nomidagi 1 ) o'rnatilgan akkumulyator bilan boshlangan qavs ibtidosining bir versiyasidir va keyin uni foydalanuvchidan belgilangan funksiya chapdan o'ngga (odatda, o'ng qavat deb nomlanadi, shuning uchun funktsiya nomidagi r ). f1 (f 2 3) (yoki f (1, f (2,3)) uchun foldr1 f [1,2,3] </</code>).

  where combine v1 v2 | v1 == 10  = v1

birlashtiramiz lokal funktsiyasini aniqlash: ikkita argument oladi: v1 va v2 . v1 10 bo'lsa, u faqat v1 funksiyasini qaytaradi. Bunday holda v2 hech qachon baholanmaydi , shuning uchun loop bu erda to'xtaydi.

                      | v1 == 150 = v1 + 100 + v2

Shu bilan bir qatorda, v1 150 bo'lsa, unga qo'shimcha 100 qo'shimchalar va v2 qo'shimchalar.

                      | otherwise = v1 + v2

Va agar bu shartlarning hech biri to'g'ri bo'lmasa, v1 dan v2 ga qo'shiladi.

Endi bu echim Haskell uchun biroz o'ziga xosdir, chunki agar birlashtiruvchi funktsiyaning ikkinchi dalilini baholasa, o'ng qavatning tugashi haqiqatdir. Bu Haskellning dangasa baholash strategiyasidan kelib chiqadi. Men Clojureni bilmayman, lekin men qattiq baholashni qo'llayman, deb o'ylayman, shuning uchun standart kutubxonada erta bekor qilish uchun maxsus qo'llab-quvvatlashni o'z ichiga oladigan fold funktsiyasiga ega bo'lishini kutmoqdaman. Bu ko'pincha foldWhile , foldUntil yoki shunga o'xshash deb ataladi.

Clojure kutubxonasi hujjatlariga tezkorlik bilan qarash, u ko'p funktsional tillardan nomlashda bir oz farq qiladi va fold siz izlayotgan narsa emas (bu yanada rivojlangan mexanizm parallel hisoblash), lekin reduce yanada to'g'ridan-to'g'ri ekvivalentdir. Erta bekor qilish, reduced funktsiyasi sizning kombinatsiyalash funktsiyasi ichida chaqirilganda paydo bo'ladi. Men sintaksisni tushunganimdan 100% ishonchim komil emas, lekin siz qidirayotgan narsangiz shubhali:

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

Eslatma: Har ikkala tarjima Haskell va Clojure ham ushbu maxsus kod uchun juda to'g'ri emas; ammo ular umumiy nuqtai nazarini keltiradilar - ushbu misollar bilan muayyan muammolar uchun quyidagi izohlarda munozarani ko'rsatinglar.

45
qo'shib qo'ydi
@WillNess Bu juda tezda tushunarli tarjima va tushuntirish, chunki u yuqoriga ko'tarildi. Bu noto'g'ri ekanligi sharmandalikdir, lekin bu yerda nisbatan kamroq nisbatan ahamiyatsizdir, chunki engil xatolar javobning foydali bo'lishi haqiqatini inkor etmaydi. Lekin, albatta, tuzatish kerak.
qo'shib qo'ydi muallif Mark Ransom, manba
@WillNess: Men Haskell haqida hech qanday ma'lumotim yo'q, lekin bu javobni nurli deb topdim. Agarda xato bo'lsa, uni yaxshiroq qilish uchun tahrirlash tugmasidan foydalaning.
qo'shib qo'ydi muallif olive_tree, manba
@WillNess - davom eting va tahrir qiling. Sizning sharhlaringiz yaxshi, lekin hozirda xatoni tuzatish uchun vaqtim yo'q.
qo'shib qo'ydi muallif Jules, manba
Ha, "eng to'g'ri javob" juda to'g'ri emas edi.
qo'shib qo'ydi muallif Rex Space, manba
@AndrewT. Ha, men aytganimdek: bilimlarni kashf qilish mexanizmlari. Bu, albatta, bu erda muhokama qilish uchun mos emas; Ehtimol, metada bo'lsa (ya'ni, SE ning nima ekanligini bilib olsangiz). HNQda bormi? Bu men uchun ko'rsatma bermaydi. --- Bundan tashqari, umumiy tushuntirish OK; kod faqat OPning maxsus kodining to'g'ri tarjimasi emas. BTWni pastga tushirmadim.
qo'shib qo'ydi muallif Rex Space, manba
@Jules OK Men faqat yarim haqiqatni aytdim (lekin buni nazarda tutgan edi) - bu o'zgarish juda katta; juda ko'p ish. Menga yangi javob yozish kabi bo'lardi. nega men uni o'z ismim ostida joylashtirmayman? :) Clojure kodidagi kichik xato allaqachon pastda javobda tuzatilgan. --- Yaxshi, pastki qismida kichik bir eslatma belgilangan. --- Siz Jon McCarthy haqidagi linkni o'qidingizmi? Bu yaxshi o'qilgan.
qo'shib qo'ydi muallif Rex Space, manba
masalan. break mos break sinov funktsiyasiga ega. $! bu yerda yanada samarali baholashni talab qiladi. (lekin bu Haskellga xos). Clojure ning kamaytirish aslida chapdan o'ngga to'g'ri keladi va u erda yig'iladigan qiymatga ishora qiluvchi v1 ; hali ham sizning Clojure tarjimasida xatolik bor. - Birinchi izoh Haskell tarjimasiga foldr1 bilan tarjima qilingan.
qo'shib qo'ydi muallif Rex Space, manba
@MooingDuck Men uni butunlay qayta yozishim kerak edi. Buni amalga oshirishi mumkin, lekin men buni bajarishim uchun juda ko'p narsaga ega bo'laman deb o'ylayman. Ishoq Nyutonning bu mashhur gapi menimcha, u qandaydir muammoni qanday ochib berganini va odamlar uni tushunishning umumiy tuyg'usi sababli, ular uchun "ularni tushuntirdilar", chunki u ular uchun echimini tushuntirganini his qilgan. Lekin, aslida, u faqat muammo haqida gapirdi. Xususiyatlari esimda yo'q.
qo'shib qo'ydi muallif Rex Space, manba
eng yaxshi noto'g'ri javobni SEda eng ko'p ko'tarilganligi qanday kulgili ... .... xato qilish uchun OK, siz yaxshi kompaniya :) . Ammo SO/SEda bilimlarni topish mexanizmi mutlaqo buzilgan.
qo'shib qo'ydi muallif Rex Space, manba
v1 v2 nomlari shubhali: v1 - bu "qiymatdan qator", lekin v2 - yig'ilgan natija. sizning tarjimangiz noto'g'ri deb hisoblayman, shuni hisobga olgan holda, yig'ilgan (chapdan) qiymatining 10 soniga o'tsa, OX ning loopi chiqadi. Agar siz bu erdan kataklarni ishlatsangiz, chap qavatni erta chiqishda foydalaning, ba'zi hollarda foldl bu yerda .
qo'shib qo'ydi muallif Rex Space, manba
Haskellning sintaksisi bilan tanish bo'lmagan taqdirda, bu pastga chiziqni kesib o'tishingiz mumkin - Siz mening qahramonimsansiz. Haskell men uchun sir.
qo'shib qo'ydi muallif markhorrocks, manba
fold da erta turganda biroz ishlov berish aslida qo'shimcha ishlamasdan ishlaydi. fold sizning dangasa bo'lishi kerak, ishlaydigan funksiya dangasa bo'lishi kerak. Yoki yaxshiroq aytadigan bo'lsak, sizda ba'zi monoidlarga ko'paytiriladigan ishchi funktsiyani talab qilish kerak, u ro'yxatdagi biror joyda monoidning yutish elementini topadi va buni bajarish keyingi ro'yxat elementlariga ko'proq ehtiyoj sezmaydi. Haqiqatan ham bu haqda qog'oz yozdim: mathematik.uni-marburg .de/~ lobachev/qog'ozlar/lobachev-flops12. & zwnj; pdf
qo'shib qo'ydi muallif ineedtosleep, manba
@WillNess bu savolning ko'pchiligi faqatgina yuqoriga ko'tarilishi mumkin bo'lgan (assotsiatsiya bonusidan) bo'lgan, lekin pastga tushmagan HNQda ekanligini unutmang. Bundan tashqari, Haskell yoki Clojure yozmaydigan kishi sifatida, bu javobda batafsil tushuntirish men uchun bu ma'lumotni to'g'ri yoki noto'g'ri deb bilish uchun etarlicha ishonchga ega bo'lar edi (men ushbu Q & A-da hech qanday postlarni ovoz berganim yo'q) .
qo'shib qo'ydi muallif Suraj, manba
Clojure kodi deyarli to'g'ri, lekin (= v1 150) sharti v2 (aka. e ) oldin qiymatdan foydalanadi u.
qo'shib qo'ydi muallif fileyfood500, manba

Uni osongina rekursiyaga aylantira olasiz. Va u yaxshi quyruq optimallashtirilgan reküruviya chaqiruviga ega.

Pseudocode:

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);
}
31
qo'shib qo'ydi
@leftaroundabout Quyidagi kabi ba'zi savollar tug'iladi: GOTO nima uchun yomon deb o'ylaysiz? (Tushunish uchun, men bu erda yaxshi yoki yomonmi deb shikoyat qilmayman, faqat sizning fikringizni bilmoqchiman.) Nimaga siz quyruq rekursionini ajratib turasiz va umuman, takrorlashni o'z ichiga olmaydi, umuman quyruq signallari yoki Umuman, funktsiyalarni chaqiradi (yoki siz buning bir qismini yoki barchasini qo'shasizmi?)?
qo'shib qo'ydi muallif steve, manba
Yep. Bir loopning funktsional ekvivalenti quyruq-recursion hisoblanadi va shartli funksional ekvivalent hali ham shartli hisoblanadi.
qo'shib qo'ydi muallif Lawrence B. Crowell, manba
@ JörgWMittag Aytmoqchimanki, quyruq recursioni GOTO funktsional ekvivalenti. (Juda ham yomon emas, lekin hali ham juda g'alati.) Jyulning aytishicha, aylana ekvivalentligi mos keladigan kattalikdir.
qo'shib qo'ydi muallif zarose, manba
@ 8bittry Men quyruqdagi rekursionni ajratishni niyat qilmagan edim. J_mie6 ta'kidlaganidek, umumiy nusxa olish GOTO ga o'xshash bo'lishi kerak. Ammo, bu erda men bu yerdan kelganman, yozma ravishda non-tail - yinelemeli kod bunday "patologik jihatdan rekursiv" emas. Aksincha, funktsional dasturchilar o'z kodlarini belgilashni istaydilar, shuning uchun belgilaydigan tenglamalar deyarli indikatorlarni tushuntiradi. Ammo, bu o'z-o'zidan ravshanki mexanizm orqali amalga oshirilmaydi (dasturchi bajarishi mumkin bo'lgan bema'nilik miqdorini cheklab qo'yadigan looplardan farqli o'laroq).
qo'shib qo'ydi muallif zarose, manba
@ J_mie6 GOTO kabi quyruqli rekursiya deb hisoblaganimning sababi, aslida o'zingizning maqsadingizga muvofiq tarzda harakat qilishini ta'minlash uchun qanday holatda yineluvchi chaqiruvga o'tilayotganligini aniqroq hisobga olishingiz kerak. Buni bir xil darajaga yozib qo'yish kerak emas: yozma imperativ looplarda (agar o'zgaruvchining o'zgaruvchilari nima ekanligini va ularning har bir itarishda qanday o'zgarishini aniq tushunadigan bo'lsa) yoki naif rekvrsiyada (ko'pincha dalillar bilan emas, aksincha, natija juda intuitiv ravishda yig'iladi). ...
qo'shib qo'ydi muallif zarose, manba
... Qatlamlarga kelsak: siz haqsiz, an'anaviy qatlam (katamorfizm) juda o'ziga xos aylana turidir, ammo bu retsurssiya sxemalari umumlashtirilishi mumkin (ona/apo-/gilomorfizmlar); birma-bir bu imo-lotli looplar uchun IMO-ning to'g'ri almashtirilishi.
qo'shib qo'ydi muallif zarose, manba
@ J_mie6 Sizning "an'anaviy forint" larning o'z-o'zidan takrorlangan ekvivalenti ham boshlanishi, oxiri va deterministik qadamiga ega va bu to'xtab qolishi uchun indüksiyon orqali ko'rsatish oson.
qo'shib qo'ydi muallif Doval, manba
Mumkin: Bu degani, bir qatlamning izdoshlari soni ko'payganligini bildiraman. Har qanday ro'yxat uchun, siz "pastadir" qancha yineleme borligini aniq bilasiz. Shunday qilib, bir qavat an'anaviy loopga o'xshaydi (uning boshlanishi, oxiri va deterministik bosqichi bor) va quyruqning rekursiv funktsiyasi vaqtinchalik loopga o'xshaydi. Keyinchalik xomashtlik energiyasi jihatidan cheklovsiz rekursiya haqiqiy GOTO hisoblanadi
qo'shib qo'ydi muallif J_mie6, manba
To'g'risini aytganda, aslida rozi emasman. Aytmoqchimanki, quyruqning o'zidan o'tishga va faqatgina quyruq holatiga borib, quyruqdagi yozuvni qabul qilishdan ko'ra ko'proq cheklash kerak. Bu loop tuzilishga asosan tengdir. Aytganimdek, umuman "recursion" kodi GOTO ga teng. Qanday bo'lmasin, quyruqning rekursiyasini kompilyatsiya qilganingizda, ko'pincha faqat while (true) pastki qaytib, faqat break iborasi bo'lgan funktsiya tanasi bilan pastga qaytib tushadi. Bir qavat, bu sizning to'g'ringizda to'g'ri bo'lsa-da, aslida loop tuzilishdan ko'ra ko'proq cheklangan; bu har bir forma-ga o'xshaydi
qo'shib qo'ydi muallif J_mie6, manba

Men haqiqatan ham Julesning javobiga javob berishni yaxshi ko'raman, lekin men odamlarga tez-tez dangasa funktsional dasturlash haqida sog'indim. har bir narsa "loop ichida" bo'lishi shart emas. Misol uchun:

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

Mantiqning har bir qismini bir-biriga biriktirilgan alohida funksiyada hisoblash mumkinligini ko'rishingiz mumkin. Bu, odatda, muammolarni bartaraf etishda ancha oson bo'lgan kichik funktsiyalarni bajarishga imkon beradi. Sizning o'yinchingiz uchun misol uchun, ehtimol buning ortidan ko'ra murakkabroqlikni keltirib chiqarmoqda, lekin haqiqiy dunyo kodida split funktsiyalar odatda butunlay osonroqdir.

12
qo'shib qo'ydi
mantiq bu yerga tarqaldi. bu kodni saqlash oson bo'lmaydi. stopAt10 emas yaxshi iste'molchi hisoblanadi. sizning javobingizdan yaxshiroq, chunki asosiy ishlab chiqaruvchi scanl (+) 0 qiymatlarini to'g'ri ajratib turadi. ularning iste'moli to'g'ridan-to'g'ri nazorat mantig'ini o'z ichiga olishi kerak, faqat ikkita oralig'i va last bilan aniq amalga oshiriladi. original kod strukturasini va mantig'ini diqqat bilan kuzatib boradi va saqlab qolish oson bo'ladi.
qo'shib qo'ydi muallif Rex Space, manba

Ko'p ro'yxatli ishlov berish namunalari ro'yxatda butun ro'yxatda ishlaydigan map , filter , sum va hokazolar kabi funktsiyalardan foydalanishingiz mumkin. Sizning holatlaringizga ko'ra shartli erta chiqishingiz bor - odatiy ro'yxat operatsiyalari tomonidan qo'llab-quvvatlanmaydigan juda noyob naqsh. Shunday qilib, obstruktsiya darajasini pastga tushirish va takrorlashni qo'llash kerak - bu ham majburiy misolning qanday paydo bo'lishiga yaqinroq.

Bu Clojure-ga juda to'g'ridan-to'g'ri (ehtimol, odatiy bo'lmagan) tarjima:

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

Tartibga solish: Jules Clojure do da kamaytirish ni erta chiqishni qo'llab-quvvatlang. Buning yordamida yana oqlangan:

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

Har holda, majburiy tillarda mumkin bo'lganidek, funktsional tillarda ham hamma narsani qilishingiz mumkin, lekin siz odatda oqilona echim topish uchun o'z fikringizni biroz o'zgartirishi kerak. Majburiy kodlashda siz ro'yxatni asta-sekin qayta ishlashni o'ylaysiz, lekin funktsional tillarda ro'yxatdagi barcha elementlarga qo'llash uchun operatsiyani izlaysiz.

6
qo'shib qo'ydi
@Jules: Cool - ehtimol bu odatiy echim.
qo'shib qo'ydi muallif JacquesB, manba
@jcast - takeWhile esa umumiy amaliyotdir, bu holda, ayniqsa, foydali emas, chunki siz to'xtash yoki hal qilmaslikka qaror qilishingizdan oldin sizning ishlashingiz natijalari kerak. Bu dangasa tilda bu muhim emas: siz skanerlash natijalari bo'yicha scan va takeWhile foydalanishingiz mumkin (Qarang: Karl Bielefeldtning javobidan foydalanmang TakeWhile ni qayta yozish uchun osongina yozish mumkin edi), lekin bu jiddiy til uchun, bu butun ro'yxatni qayta ishlashni va undan keyin natijalarni bekor qilishni anglatadi. Jeneratör vazifalari buni hal qilishi mumkin, va men ularni qo'llab-quvvatlaydi.
qo'shib qo'ydi muallif Jules, manba
mening javobimga qo'shilgan tahrirni qarang: Clojure ning reduce operatsiyasi erta chiqishni qo'llab-quvvatlaydi.
qo'shib qo'ydi muallif Jules, manba
Noto'g'ri - yoki takeWhile "umumiy amaliyot" emas?
qo'shib qo'ydi muallif John Washburn, manba
Clojure-da @Jules take-while dangasa ketma-ketlikni ishlab chiqaradi (hujjatlarga muvofiq). Bunga erishishning yana bir yo'li transduserlar bilan (ehtimol, eng yaxshi) bo'lishi mumkin.
qo'shib qo'ydi muallif Rex Space, manba

Boshqa javoblar bilan qayd etilganidek, Clojure chegirma berishni to'xtatish uchun kamaydi

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

Bu muayyan vaziyatingiz uchun eng yaxshi echimdir. xarita </​​code>, xarita </​​code> dan transduserlardan foydalanishga imkon beruvchi transmit bilan azaltılmış ni birlashtirishdan juda ko'p kilometr olishingiz mumkin > va hokazo. Biroq, bu sizning umumiy savolingiz to'liq javob emas.

Qochishga davom ettirish buzilish va qaytarishning umumlashtirilgan versiyasidir bayonotlar. Ular to'g'ridan-to'g'ri ba'zi tartiblarda ( call-with-escape-continuation ), Umumiy Lisp ( blok + return ), catch + throw ) va hatto C ( setjmp + longjmp ). Bundan tashqari, standart sxemada yoki Haskell va Scala-da davomiy monastrlar sifatida belgilangan umumiy chegaralangan yoki nomaqbul uzilishlar qochishning davomiyligi sifatida ham qo'llanilishi mumkin.

Misol uchun, Racketda quyidagi kodni ishlatishingiz mumkin: 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)))

Boshqa ko'plab tillar ham davom ettirishdan qochib qutulishdi - masalan, istisnolarni qo'llash shaklida. Haskell-da siz foldM bilan turli xil xato monastlardan birini qo'llashingiz mumkin. Ular, avvalambor, erta qaytib kelishi uchun istisnolar yoki xato monadlari yordamida xatolarni tuzatish konstruktsiyalari bo'lgani uchun odatda madaniy jihatdan qabul qilinmaydi va ehtimol ancha sekin.

Bundan tashqari, yuqori darajadagi vazifalardan quyruqli qo'ng'iroqlarga tushishingiz mumkin.

Looplarni ishlatganda, pastadir tanasining oxiriga yetganingizda keyingi iteratsiyani avtomatik ravishda kiritasiz. continue bilan keyingi iteratsiyani erta kiritishingiz mumkin yoki break (yoki return ) bilan pastadirdan chiqishingiz mumkin. Tail-qo'ng'iroqlarni (yoki quyruqli recursionni taqqoslaydigan Clojure's loop tuzilmasidan) foydalanganda, keyingi iteratsiyaga kirish uchun har doim aniq qo'ng'iroq qilish kerak. Pastadirni to'xtatish uchun faqat o'zingizning takrorlovchi qo'ng'iroqni qilmang, lekin to'g'ridan-to'g'ri qiymat bering:

(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))))))
3
qo'shib qo'ydi
Haskellda xato monastlardan foydalanganda, bu erda hech qanday haqiqiy jazo yo'qligiga ishonmayman. Ular istisnosiz ishlov berish yo'nalishlari bo'yicha fikr yuritishga moyil bo'ladilar, lekin ular xuddi shunday ishlamayaptilar va stakka yurishning zaruriy sharti yo'q, shuning uchun bunday usulda foydalanilgan taqdirda, albatta, muammo bo'lmasligi kerak. Bundan tashqari, MonadError kabi biror narsani ishlatmaslik uchun madaniy sabab bo'lsa ham, asosan ekvivalent yoki faqat xatoliklarni ko'rib chiqishga nisbatan bunday turg'unlikka ega emas. almashtirish.
qo'shib qo'ydi muallif Jules, manba
@Jules, chapga qaytib, bu qatlam butun ro'yxatni (yoki boshqa ketma-ketlikni) ko'rishga to'sqinlik qilmaydi. Haskell standart kutubxona internals bilan yaxshi tanish emas.
qo'shib qo'ydi muallif Nachiket Mehta, manba

Murakkab qism loop hisoblanadi. Keling, bundan boshlaylik. Ikkinchisi, odatda, bir funktsiya bilan iteratsiyaning ifodasini topib, funktsional uslubga aylantiriladi. Iteratsiya - aylana o'zgaruvchisi.

Bu erda umumiy ko'chadan ishlab chiqishda qo'llaniladi:

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

Bu (loop o'zgaruvchining boshlang'ich qiymati, aylana o'zgaruvchisini ifodalovchi funktsiya (pastadirni davom ettirish uchun shart) oladi.

Sizning namunangiz qatorda bir qatorda ishlatiladi, bu ham buziladi. Sizning majburiy tilingizdagi bu qobiliyat tilga aylantiriladi. Funktsional dasturlashda bunday imkoniyat odatda kutubxona darajasida amalga oshiriladi. Bu erda amalga oshirilishi mumkin bo'lgan dastur

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

Unda:

Men o'zimdan ((val, next_pos) juftini ishlataman, bu o'z navbatida tashqarida ko'rinadigan loop o'zgaruvchisini va bu funksiya yashirgan qatordagi pozitsiyani o'z ichiga oladi.

Iteratsiya funktsiyasi umumiy ko'chadan bir oz ko'proq murakkab, ushbu versiya qatorning joriy elementini ishlatishga imkon beradi. [Bu curried shaklida mavjud.]

Bunday funktsiyalar odatda "katlama" deb nomlanadi.

"I" qatoriga elementlarning to'planishi chap-birlashtiruvchi tarzda amalga oshirilishini ko'rsatish uchun "l" ni qo'ydim; pastdan yuqori indeksgacha bo'lgan ketma-ketlikni qaytarish uchun majburiy dasturlash tillarining odatiga taqlid qilish.

Men ushbu qavatning versiyasi erta to'xtatilsa va qachon nazorat qilinishini nazorat qiladigan shartni talab qiladigan nomga "c" qo'ydim.

Tabiiyki, bunday foydali funktsiyalar, ishlatiladigan funktsional dasturiy til bilan ta'minlangan bazaviy kutubxonada osonlikcha mavjud bo'ladi. Men ularni namoyish qilish uchun bu erga yozdim.

Keling, imperativ vaziyatda tilda mavjud bo'lgan barcha vositalarga egamiz, sizning namunangizning o'ziga xos funksiyasini amalga oshirishga qaytishimiz mumkin.

Sizning loopingizdagi o'zgaruvchilar juftlikdir ("javob", davom etish kerakmi yoki yo'qligini bildiruvchi 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)

Yangi "variable" "new_answer" dan foydalanishni unutmang. Buning sababi, funktsional dasturlarda men boshlangan "o'zgarmaydigan" ning qiymatini o'zgartira olmaydi. Men ishlashni tashvishlantirmayman, kompilyator, agar bu yanada samaraliroq deb hisoblasa, "new_answer" uchun "javob" xotirasini hayotda tahlil qilish orqali qayta ishlatishi mumkin.

Buni ilgari ishlab chiqilgan loop funktsiyasiga qo'shamiz:

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

"Array" funksiyasi bu funktsiyani eksport qiladigan modul nomidir.

"yumshoq", "ikkinchi" stend, juftlik parametrining birinchi, ikkinchi komponentini qaytaradigan funksiyalar

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

Bunday holda "nuqtali" uslubi doSomeCalc ilovasini o'qishni osonlashtiradi:

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

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

Yuqorida aytib o'tilganidek, faqat "arr" parametri ikkala tomondan aniqlanadigan tenglamadan tashqarida qoladi.

Eng so'nggi narsa: idishni tekshirish (array == null). Yaxshi mo'ljallangan dasturlash tillarida, lekin ba'zi bir asosiy intizomga ega bo'lgan juda yomon mo'ljallangan tillarda ham, ixtiyoriy turdagi yo'qligini ifoda etish. Bu funktsional dasturlash bilan bog'liq emas, chunki bu savol oxir-oqibat, shuning uchun u bilan shug'ullanmayman.

2
qo'shib qo'ydi