kitchen-async: a promising (?) Promise library, or a poor man's core.async

E170ae2eb0adcf3d136ab0990f0ea671?s=47 OHTA Shogo
January 29, 2018

kitchen-async: a promising (?) Promise library, or a poor man's core.async

2018/01/29のLisp meetup #60の発表資料です。

E170ae2eb0adcf3d136ab0990f0ea671?s=128

OHTA Shogo

January 29, 2018
Tweet

Transcript

  1. LJUDIFOBTZODBQSPNJTJOH  1SPNJTFMJCSBSZ PSBQPPSNBO`TDPSFBTZOD -JTQNFFUVQ !BUIPT

  2. ࣗݾ঺հ ‣ 5XJUUFS!BUIPT ‣ (JU)VCBUIPT ‣ $MPKVSF 4DSJQU ॻ͍ͯ·͢ 

    3FBDU/BUJWFΞϓϦΛ$MPKVSF4DSJQUͰ  "MFYB4LJMMΛ$MPKVSF4DSJQUͰ
  3. 5-%3 ‣ $MPKVSF$MPKVSF4DSJQUͰඇಉظϓϩάϥϛϯάͱ͍͏ͱ DPSFBTZOD͕༗໊͕ͩৗʹ࠷ྑͷબ୒ࢶͱ͸ݶΒͳ͍ ‣ +BWB4DSJQU &$."4DSJQU ͸1SPNJTF΍BTZODBXBJU Λݴޠ࢓༷ʹऔΓೖΕ͍ͯͯɺ+4ͱͷJOUFSPQΛߟ͑Δͱ 1SPNJTFΛʮ͏·͘ʯѻ͑Δ͜ͱ΋ඞཁ

    ‣ 1SPNJTFͱ΋DPSFBTZODͱ΋͏·͘΍͍ͬͯͨ͘Ίͷϥ ΠϒϥϦLJUDIFOBTZODΛ࡞ͬͨɺͱ͍͏࿩
  4. ࠓ೔࿩͢͜ͱ ‣ എܠ  +BWB4DSJQUք۾ͷඇಉظϓϩάϥϛϯάࣄ৘  $MPKVSF4DSJQUք۾ͷඇಉظϓϩάϥϛϯάࣄ৘ ‣ $MPKVSF4DSJQUͰͷඇಉظϓϩάϥϯάͷ໰୊ ‣

    LJUDIFOBTZODͷ঺հ
  5. എܠ

  6. +BWB4DSJQUք۾ͷඇಉظࣄ৘ ‣ +BWB4DSJQUͷଟ͘ͷॲཧܥ͸γϯάϧεϨου ‣ *0ॲཧ౳ͷ׬ྃΛಉظతʹ଴ͪ߹Θͤͯ͠·͏ͱɺͦͷ εϨου͸ॲཧ׬ྃ·ͰԿ΋Ͱ͖ͳ͘ͳΔ ‣ *0ͷ׬ྃ଴ͪ౳ͷؒεϨουΛଞͷॲཧʹ໌͚౉͢͜ͱ ͰεϨουΛޮ཰తʹ࢖͑ΔΑ͏ʹͳΔ ˠඇಉظϓϩάϥϛϯά

  7. ίʔϧόοΫ ‣ ॲཧ͕׬ྃͨ͠ޙʹ࣮ߦ͢ΔॲཧΛ౉͢ ‣ +BWB4DSJQUͱͯ͠͸΋ͬͱ΋ݪ࢝తͳඇಉظ"1*ͷ࡞Γํ ‣ ඇಉظॲཧ͕ଟஈʹͳΔͱίʔϧόοΫ͕ωετͯ͠ίʔυͷ Մಡੑ͕ѱ͘ͳΔ ͍ΘΏΔʮίʔϧόοΫ஍ࠈʯ const

    fs = require(‘fs’) fs.readFile(‘input.txt’, (err, data) => { console.log(data) })
  8. 1SPNJTF ‣ 1SPNJTF͸ඇಉظͳܭࢉ݁ՌΛอ࣋͢Δσʔλܕ ‣ ίʔϧόοΫΛҾ਺ʹड͚Δ୅ΘΓʹ1SPNJTFΛฦ͢Α͏ ʹ͓ͯ͘͠ͱʜ const readFileAsync = (path)

    => { return new Promise((resolve, reject) => { fs.readFile(path, (err, data) => { if (err) return reject(err) resolve(data) }) }) }
  9. 1SPNJTF ‣ ॲཧͷ׬ྃޙʹ࣮ߦ͢ΔॲཧΛޙ͔ΒઃఆͰ͖Δ ‣ ϝιουνΣʔϯͰଟஈͷॲཧΛܨ͛ΒΕΔ readFileAsync(‘input.txt’) .then((text) => { return

    JSON.parse(text) }) .then((json) => { console.log(json) }) .catch((err) => { console.log(err) })
  10. BTZODBXBJU ‣ 1SPNJTFΛ࢖ͬͨॲཧΛ௚઀ܗࣜͰ͔͋ͨ΋ಉظతͳॲཧͷΑ͏ʹ هड़Ͱ͖Δ ‣ ଟ͘ͷ৔߹1SPNJTFΛ௚઀࢖͏ΑΓ੍ޚϑϩʔ͕෼͔Γ΍͘͢ͳΔ (async () => {

    try { const text = await readFileAsync(‘input.txt’) return JSON.parse(text) } catch (err) { console.log(err) } })()
  11. +BWB4DSJQUք۾ͷඇಉظࣄ৘ ‣ &$."4DSJQU &4 ͷ࢓༷΁ͷऔΓࠐΈ  &41SPNJTF  &4BTZODBXBJU ‣

    8FCඪ४ͷ"1*Ͱͷར༻  'FUDI"1*  8FC35$ͷϝσΟΞऔಘ  4FSWJDF8PSLFS FUDFUD ‣ αʔυύʔςΟͷOQNϞδϡʔϧͰͷར༻ +BWB4DSJQUͷ༷ʑͳ"1*ʹ1SPNJTF͕࢖ΘΕͭͭ͋Δ
  12. $MPKVSF4DSJQUք۾ͷඇಉظࣄ৘ ‣ ΄΅DPSFBTZODҰ୒ͳงғؾ ‣ DPSFBTZOD͸$41 ڠௐεϨουͱνϟωϧΛհͨ͠ϝο ηʔδύογϯά ΛϞσϧʹ͍ͯ͠Δ ‣ 1SPNJTFͱಉ༷ͷ࢖͍ํ΋Ͱ͖Δ͕ɺͭͷνϟωϧ͔Β

    ෳ਺ճ஋Λड͚औͬͨΓɺ஋ΛૹΓฦͤΔ෼ΑΓڧྗ
  13. DPSFBTZODͰͷඇಉظॲཧ (require ‘[clojure.core.async :as a]) (defn read-file [path] (let [ch

    (a/chan)] (fs.readFile path (fn [err data] (a/put! ch (or err data)))) ch)) (a/go (let [data (a/<! (read-file “input.txt”))] (js/console.log data)))
  14. ར༻ऀ͔Βͷ͓೰Έ ʮ$MPKVSF4DSJQU͔Βͩͱ1SPNJTFΛѻ͏ίʔυ Λ͖ͬ͢Γॻ͚·ͤΜʯ ୅உੑձࣾһ ˞͜Ε͸ݸਓͷײ૝Ͱײ͡ํʹ͸ݸਓ͕ࠩ͋Γ·͢

  15. ར༻ऀ͔Βͷ͓೰Έ ʮ$MPKVSF4DSJQU͔Βͩͱ1SPNJTFΛѻ͏ίʔυ Λ͖ͬ͢Γॻ͚·ͤΜʯ ୅உੑձࣾһ (-> (readFileAsync “input.txt”) (.then (fn [text]

    (js/JSON.parse text))) (.then (fn [json] (js/console.log json))) (.catch (fn [err] (js/console.log err)))) ˞͜Ε͸ݸਓͷײ૝Ͱײ͡ํʹ͸ݸਓ͕ࠩ͋Γ·͢
  16. ར༻ऀ͔Βͷ͓೰Έ ʮ$MPKVSF4DSJQU͔Βͩͱ1SPNJTFΛѻ͏ίʔυ Λ͖ͬ͢Γॻ͚·ͤΜʯ ୅உੑձࣾһ (-> (puppeteer/launch) (.then (fn [browser] (->

    (.newPage browser) (.then (fn [page] (.then (.goto page “https://www.google.com") #(.screenshot page #js{:path “image.png"})))) (.then #(.close browser))))))) ˞͜Ε͸ݸਓͷײ૝Ͱײ͡ํʹ͸ݸਓ͕ࠩ͋Γ·͢
  17. ར༻ऀ͔Βͷ͓೰Έ ‣ ͦ΋ͦ΋1SPNJTF͸+4Ͱॻ͖΍͍͢Α͏ʹઃܭ͞Ε͍ͯΔ ‣ ෳࡶͳ৔߹ͩͱ݁ہ1SPNJTFΛωετͤͯ͞࢖͏ඞཁ͕͋Δ ‣ BTZODBXBJU͘Β͍͖ͬ͢ΓͱඇಉظͳίʔυΛॻ͖͍ͨ ʮ$MPKVSF4DSJQU͔Βͩͱ1SPNJTFΛѻ͏ίʔυ Λ͖ͬ͢Γॻ͚·ͤΜʯ ୅உੑձࣾһ

    ˞͜Ε͸ݸਓͷײ૝Ͱײ͡ํʹ͸ݸਓ͕ࠩ͋Γ·͢
  18. ར༻ऀ͔Βͷ͓೰Έ ‣ 1SPNJTFϔϏʔͳ"1*Λ࢖͏ͱ͖ʹ౎౓νϟωϧʹม׵ͨ͘͠ͳ͍ ‣ HPϒϩοΫ͸े෼ʹෳࡶͳϚΫϩ  ల։ܗ΋σΧ͍  XFC"1*Λճୟͨ͘Ί͚ͩʹHPϒϩοΫΛॻ͖ͨ͘ͳ͍ ‣

    DPSFBTZOD͸Τϥʔॲཧ͕͏·͘ͳ͍ ‣ TFMGIPTUFE$-+4 -VNP1MBODL Ͱ͸ͦͷ··Ͱ͸ಈ͔ͳ͍ ʮDPSFBTZOD͸͔ͨ͠ʹڧྗͳಓ۩ͳΜ͚ͩͲɺ ͬ͘͠Γ͜ͳ͍৔߹΋͋Γ·͢ΑͶʯ ୅உੑձࣾһ ˞͜Ε͸ݸਓͷײ૝Ͱײ͡ํʹ͸ݸਓ͕ࠩ͋Γ·͢
  19. ސ٬͕ຊ౰ʹඞཁͩͬͨ΋ͷ ‣ ʮෳ਺ͷඇಉظॲཧΛஞ࣍తʹ࣮ߦ͢Δʯͱ͍͏໨తͷͨΊʹ ͸DPSFBTZOD͸ΦʔόʔΩϧ ‣ ඇಉظϓϩάϥϛϯάͷதఔ౓ʹෳࡶͳίʔυΛॻ͘ͷʹదͨ͠ ಓ۩͕΄͍͠ ໰୊ͷෳࡶ͞ ίʔϧόοΫ Promise

    core.async
  20. ސ٬͕ຊ౰ʹඞཁͩͬͨ΋ͷ ‣ ʮෳ਺ͷඇಉظॲཧΛஞ࣍తʹ࣮ߦ͢Δʯͱ͍͏໨తͷͨΊʹ ͸DPSFBTZOD͸ΦʔόʔΩϧ ‣ ඇಉظϓϩάϥϛϯάͷதఔ౓ʹෳࡶͳίʔυΛॻ͘ͷʹదͨ͠ ಓ۩͕΄͍͠ ໰୊ͷෳࡶ͞ ίʔϧόοΫ Promise

    core.async
  21. ސ٬͕ຊ౰ʹඞཁͩͬͨ΋ͷ ‣ ʮෳ਺ͷඇಉظॲཧΛஞ࣍తʹ࣮ߦ͢Δʯͱ͍͏໨తͷͨΊʹ ͸DPSFBTZOD͸ΦʔόʔΩϧ ‣ ඇಉظϓϩάϥϛϯάͷதఔ౓ʹෳࡶͳίʔυΛॻ͘ͷʹదͨ͠ ಓ۩͕΄͍͠ ‣ ႩఆόαϛͰ΋νΣʔϯιʔͰ΋ͳ͍מΓࠐΈόαϛ͕΄͍͠ʂ ໰୊ͷෳࡶ͞

    ίʔϧόοΫ Promise core.async
  22. LJUDIFOBTZOD

  23. LJUDIFOBTZOD ‣ 1SPNJTFΛத৺ͱͯ͠ɺ$MPKVSF4DSJQUͰඇಉظͳίʔυ Λॻ͖΍͘͢͢ΔϥΠϒϥϦ  $MPKVSFͷΠσΟΦϜతͳॻ͖ํͰ1SPNJTFΛѻ͑Δߏจ  DPSFBTZODͷνϟωϧ౳ͱγʔϜϨεʹ࿈ܞͰ͖Δ਌࿨ੑ ‣ ໊લ͸lFWFSZUIJOHCVUUIFLJUDIFOTJOLz

    ԿͰ΋͔ΜͰ΋  Λ΋ͬͨ͡΋ͷ
  24. LJUDIFOBTZOD͕ఏڙ͢Δ"1* ‣ 1SPNJTF༝དྷͷؔ਺ɾϚΫϩ  QSPNJTFɾUIFOɾDBUDI   BMMɾSBDF ‣ $MPKVSFͷߏจͰ1SPNJTFΛѻ͏౶ҥߏจ

     QMFU  εϨοσΟϯάϚΫϩ  ϧʔϓ  ྫ֎ॲཧ
  25. QSPNJTFɾUIFOɾDBUDI ‣ +4ͷ1SPNJTFͱ ΄΅ ౳Ձͳ"1*Λఏڙ͍ͯ͠Δ (require ‘[kitchen-async.promise :as p]) (->

    (p/promise [resolve reject] (resolve 42)) (p/then (fn [x] (js/console.log x))) (p/catch* (fn [err] (js/console.log err))))
  26. QMFUϚΫϩ (p/let [<var1> <init1> <var2> <init2> …] <body>) (p/then <init1>

    (p/then (fn [<var1>] (p/then <init2> (fn [<var2>] … <body> …))))) ্ͷϑΥʔϜ͸͓͓ΉͶҎԼͷΑ͏ʹల։͞ΕΔ
  27. QMFUϚΫϩ (p/let [browser (puppeteer/launch) page (.newPage browser)] (.goto page "https://www.example.com")

    (.screenshot page #js{:path "image.png"}) (.close browser)) (-> (puppeteer/launch) (.then (fn [browser] (-> (.newPage browser) (.then (fn [page] (.then (.goto page “https://www.example.com”) #(.screenshot page #js{:path "image.png"})))) (.then #(.close browser))))))) ্ͷίʔυ͸QMFUΛ࢖ͬͯҎԼͷΑ͏ʹॻ͖׵͑ΒΕΔ
  28. εϨοσΟϯάϚΫϩ (p/let [x (f) y (g x)] (h y)) (p/->

    (f) g h) ্ͷίʔυ͸QΛ࢖ͬͯҎԼͷΑ͏ʹॻ͖׵͑ΒΕΔ w Q QTPNF QTPNF΋ಉ༷ʹ࢖͑Δ
  29. ϧʔϓ ‣ QMPPQɾQSFDVSΛ࢖ͬͯ௨ৗͱಉ༷ʹϧʔϓ͕ॻ͚Δ ‣ QSFDVS͸QMPPQͷதͰͷΈ࢖༻Ͱ͖Δ (p/loop [i (p/timeout 1000 0)]

    (when (<= i 10) (prn i) (p/recur (p/timeout 1000 (inc i)))))
  30. ྫ֎ॲཧ ‣ QUSZɾQDBUDIɾQpOBMMZΛ࢖ͬͯ௨ৗͱಉ༷ʹྫ֎ॲཧ͕ॻ͚Δ ‣ QDBUDIɾQpOBMMZ͸QUSZͷதͰͷΈ࢖༻Ͱ͖Δ (p/try (write-async fd content) (p/catch

    :default e (js/console.log e)) (p/finally (close fd)))
  31. DPSFBTZODͱͷ γʔϜϨεͳ࿈ܞ

  32. QSPNJTF ‣ ೚ҙͷ஋Λ1SPNJTFʹม׵ DPFSDJPO ͢Δؔ਺ (defprotocol Promisable (->promise [this])) (extend-protocol

    Promisable js/Promise (->promise [this] this) default (->promise [this] (p/promise [resolve] (resolve this)))
  33. ҉໧ͷQSPNJTF ‣ LJUDIFOBTZODͷ͢΂ͯͷ"1*͸҉໧ʹQSPNJTFΛద༻ ‣ 1SPNJTFҎ֎ͷ஋΋1SPNJTFͱ۠ผͳ͘࢖͏͜ͱ͕Ͱ͖Δ (p/let [x (f), y (g

    x)] (h y)) (p/then p f) (p/then (->promise p) f) (p/let [x (->promise (f)), y (->promise (g x))] (h y))
  34. DPSFBTZODνϟωϧͱͷ࿈ܞ (ns kitchen-async.promise.from-channel (:require [clojure.core.async :as a] [clojure.core.async.impl.channels :refer [ManyToManyChannel]]

    [kitchen-async.promise :as p])) (extend-protocol p/Promisable ManyToManyChannel (->promise [ch] (p/promise [resolve reject] (a/go (let [x (a/<! ch)] (if (instance? js/Error x) (reject x) (resolve x)))))))
  35. DPSFBTZODνϟωϧͱͷ࿈ܞ ‣ dQSPNJTFGSPNDIBOOFMΛϩʔυ͢Ε͹νϟωϧͱ 1SPNJTFΛγʔϜϨεʹ࢖͑ΔΑ͏ʹͳΔ (require ‘kitchen-async.promise.from-channel) (def ch (a/chan)) (p/loop

    [i 0, x ch] (prn x) (when (<= i 10) (p/recur (inc i) ch)))
  36. ࣮༻ྫ ‣ DPSFBTZODͱಉ౳ͷ͜ͱ͕HPϚΫϩΛ࢖ΘͣʹͰ͖Δ ‣ ͨͩ͠ɺύϑΥʔϚϯεతʹ͸DPSFBTZODͷํ͕͍͍ (defn debounce ([c ms] (debounce

    (a/chan) c ms)) ([c' c ms] (p/loop [start nil loc c] (if (nil? start) (do (a/put! c' loc) (p/recur (js/Date.) nil)) (p/let [loc c] (if (>= (- (js/Date.) start) ms) (p/recur nil loc) (p/recur (js/Date.) loc))))) c'))
  37. ·ͱΊ ‣ $MPKVSF$MPKVSF4DSJQUͰඇಉظϓϩάϥϛϯάͱ͍͏ͱ DPSFBTZOD͕༗໊͕ͩৗʹ࠷ྑͷબ୒ࢶͱ͸ݶΒͳ͍ ‣ +BWB4DSJQU &$."4DSJQU ͸1SPNJTF΍BTZODBXBJU Λݴޠ࢓༷ʹऔΓೖΕ͍ͯͯɺ+4ͱͷJOUFSPQΛߟ͑Δͱ 1SPNJTFΛʮ͏·͘ʯѻ͑Δ͜ͱ΋ඞཁ

    ‣ 1SPNJTFͱ΋DPSFBTZODͱ΋͏·͘΍͍ͬͯͨ͘Ίͷϥ ΠϒϥϦLJUDIFOBTZODΛ࡞ͬͨɺͱ͍͏࿩
  38. LJUDIFOBTZOD ‣ 1SPNJTFΛத৺ͱͯ͠ɺ$MPKVSF4DSJQUͰඇಉظͳίʔυ Λॻ͖΍͘͢͢ΔϥΠϒϥϦ ‣ $MPKVSFͷΠσΟΦϜతͳॻ͖ํͰ1SPNJTFΛѻ͑Δߏจ ‣ DPSFBTZODͷνϟωϧͱγʔϜϨεʹ࿈ܞͰ͖Δ਌࿨ੑ