Share
mixi tech note #03
#技術書典8 に出典されたミクシィグループエンジニア有志による技術書です。
<< 目次 >>
1章:BigQuery ML で釣果データを分析
2章:アナログカードゲームの作り方
3章:カードゲームの対戦管理・分析アプリケーションを作った話
4章:Unity で作る競技型イベントの回答システム
5章:Haskell での Web API クライアントの作り方
<< TECH NOTE 一覧 >>
mixi tech note #01
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-01
mixi tech note #02
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-02
mixi tech note #03
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-03
mixi tech note #04
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-04
mixi tech note #05
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-05
mixi tech note #06
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-06
mixi tech note #07
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-07
MIXI TECH NOTE #08
https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-08
XFLAG Tech Note Vol.01
https://speakerdeck.com/mixi_engineers/xflag-tech-note-vol-dot-01
XFLAG Tech Note vol.02
https://speakerdeck.com/mixi_engineers/xflag-tech-note-vol-dot-02
MIXI ENGINEERS PRO
Transcript
79 slides
-
·͕͖͑ ຊॻʮmixi tech note #03ʯɺϛΫγΟάϧʔϓʹॴଐ͢Δ༗ࢤୡʹΑͬͯࣥචɾ੍࡞͞Εͨ ٕज़ॻͰ͢ɻ࣮ࡍͷݱͰΘΕٕͨज़ߟ͑ํɺ·ͨɺݸਓతʹڵຯɾؔ৺ͷ͋Δ͔Βɺࢥ͍ ࢥ͍ʹࣥච͍ͨ͠·ͨ͠ɻͦͷͨΊɺ֤ষͦΕͧΕͰ͍݁ͯ͠Δ༰ʹͳ͍ͬͯ·͢ͷͰɺ͖ͳ ষ͔Β͖ͳॱ൪Ͱָ͓͠Έ͍ͩ͘͞ɻ ·ͨɺຊॻɺϛΫγΟάϧʔϓʹ͋Δٕज़తݟΞΠσΞΛੵۃతʹڞ༗ɾެ։͍ͯ͘͜͠ͱ ͰɺੈͷதʹΑΓྑ͍αʔϏε͕ҲΕग़͢͜ͱΛئͬͯץߦ͞Ε͍ͯ·͢ɻܝࡌ͞Ε͍ͯΔใɺ
ࣥචऀࣗͷڥͰݕূࣥ͠ච͞ΕͨͷͰ͢ͷͰɺ͝ࢀߟʹ͞ΕΔࡍɺࣗ͝ͷͰஅ͠ ͝׆༻͍ͩ͘͞ɻͳ͓ɺจষදݱʹ͖ͭ·ͯ͠ɺࣥචऀࣗͷݴ༿Ͱ͑ͨ͘ɺϑϥϯΫͳදݱͱ ͳ͓ͬͯΓ·͢͜ͱ͝ཧղ͍͚ͨͩΕͱࢥ͍·͢ɻ σΟϕϩούʔϦϨʔγϣϯζνʔϜҰಉ ˗ຊॻʹؔ͢Δ͓͍߹Θͤઌ ɹ https://twitter.com/mixi_engineers ˗ϛΫγΟάϧʔϓʹ͍ͭͯ ɹ https://mixi.co.jp/ ˞ʠϛΫγΟʡ ɺ ʠmixiʡ ɺ ʠmixi ϩΰʡ ɺגࣜձࣾϛΫγΟͷඪ·ͨొඪͰ͢ɻ·ͨɺ֤ ࣾͷձ໊ࣾɺαʔϏεٴͼͷ໊শɺͦΕͧΕͷॴ༗͢Δඪ·ͨొඪͰ͢ɻ iii -
࣍ ·͕͖͑ iii ୈ 1 ষ BigQuery ML ͰՌσʔλΛੳ 1
1.1 ͡Ίʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.2 BigQuery ML ͱʁ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 1.3 BigQuery ML Ͱαϙʔτ͍ͯ͠ΔػցֶशϞσϧ . . . . . . . . . . . . . . . . . . 2 1.4 ऩूͨ͠Ռσʔλͷಛ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 1.5 ՌσʔλΛ BigQuery ML Ͱੳ . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.6 ͓ΘΓʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 9 2.1 ͡Ίʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.2 ΞφϩάΧʔυήʔϜͱԿ͔ʁ . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2.3 اը͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 2.4 ϓϩτλΠϓΛ࡞Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.5 ςετϓϨΠͱվྑ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.6 ΧʔυͷσβΠϯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.7 ࢼ࡞ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.8 ྔ࢈ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.10 εΫϦϓτશจ gist ʹͯʂ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.11 ͋ͱ͕͖ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2.12 ࢀߟ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ 21 3.1 spread ʹ͍ͭͯ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 3.2 spread ͷٕज़త֓ཁ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 3.3 my spread ͷσϓϩΠखॱ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 37 v -
࣍ 4.1 Πϕϯτʹ͍ͭͯ . . . . . . .
. . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 4.2 ४උ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 4.3 ։ൃ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41 4.4 ൃల . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 51 5.1 Լ४උ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 5.2 ࣮: ଟ૬తͳΫϥΠΞϯτ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 5.3 ࣮: API ͷઃܭΛܕͰ͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 5.4 ςετΛهड़͢Δ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 5.5 ऴΘΓʹ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 ஶऀհ 69 vi -
ୈ 1 ষ BigQuery ML ͰՌσʔλΛੳ Google ͕ެ։͍ͯ͠ΔػցֶशͷϓϩμΫτΛͬͯɺझຯͷΓͱػցֶशΛ͔͚߹Θͤͨݸ ਓϓϩδΣΫτΛհ͠·͢ɻࠓճ Web
্ͰεΫϨΠϓͨ͠Ռ*1σʔλ 32511 ݅ʢڕछ 75 छ ྨʣΛ BigQuery ʹอଘ͠ɺBigQuery ML ΛͬͨੳΛͯ͠Έ·ͨ͠ɻ 1.1 ͡Ίʹ ࢲখ͍͞ࠒ͔ΒΓ͕͖ͰɺΤϯδχΞͱ͍͏͜ͱ͋Γීஈ͔ΒʮΓʷςΫϊϩδʔʯͱ ͍ͬͨςʔϚͰԿ͔Ͱ͖ͳ͍͔ߟ͍͑ͯ·͢ɻͨͱ͑ΓΞϓϦΛ։ൃͨ͠Γɺ৽͍ٕ͠ज़Λֶ ͿࡍʹΓΛࡐʹ͍ͯ͠·͢ɻͦͷҰྫ͕ٕज़ॻయ 7 Ͱͨ͠ɺmixi tech note #2 ͷʮୈ 2 ষɹΓʷػցֶशʹઓͨ͠ʯͰ͢ɻͪ͜ΒͷهࣄͰɺػցֶशॳ৺ऀͰը૾ೝࣝͷϞσ ϧΛ࡞Ͱ͖Δ Google Cloud AutoML Vision Λͬͨڕͷը૾ೝࣝΛߦ͍·ͨ͠ɻ·ͨʮػցֶ शͷϨΠϠʔͱֶͿϋʔυϧʯʹ͍ͭͯհ͠·ͨ͠ɻͪ͜ΒແྉͰμϯϩʔυͰ͖ΔͷͰɺ ڵຯ͕͋Δ͔ͨ͝ཡʹͳ͍ͬͯͩ͘͞*2ɻࠓճ Google ͕ެ։͍ͯ͠ΔϓϩμΫτͷ 1 ͭͰ͋Δ BigQuery ML ΛͬͨऔΛհ͠·͢ɻࠓճͷهࣄڵຯͷ͋ΔࡐͰ BigQuery ML Λ৮Δ͜ ͱʹΑΓ BigQuery ML ػցֶशʹڵຯΛ࣋ͬͯΒ͑ΔΑ͏ͳղઆΛ͍ͯ͠·͢ɻׂѪ͍ͯ͠Δ ػೳղઆ͕͋ΔͨΊɺຊ֨తʹ BigQuery ML Λ࢝Ί͍ͨํॳΊʹެࣜͷνϡʔτϦΞϧΛͬ ͯΈΔͷΛΦεεϝ͠·͢*3ɻ 1.2 BigQuery ML ͱʁ BigQuery ML BigQuery ʹอଘ͍ͯ͠ΔσʔλΛͬͯػցֶश͕Ͱ͖ΔϓϩμΫτͰɺ Google Cloud Next 2018 Ͱൃද͞Ε·ͨ͠ɻBigQuery*4ΠϯϝϞϦɺ BI Τϯδϯͱ AI Platform Λࡌͨ͠ߴεέʔϥϏϦςΟͰίετޮʹ༏ΕͨαʔόʔϨεΫϥυσʔλΣΞϋεͱ͠ ͯެ։͞Ε͍ͯ·͢ɻ *1 Ռɿ ڕΓͷՌɻΕͨڕͷྔɻ *2 mixi tech note #02ɿ https://speakerdeck.com/mixi_engineers/mixi-tech-note-number-02 *3 BigQuery ML νϡʔτϦΞϧɿ https://cloud.google.com/bigquery-ml/docs/tutorials *4 BigQueryɿ https://cloud.google.com/bigquery 1 -
ୈ 1 ষ BigQuery ML ͰՌσʔλΛੳ 1.3 BigQuery ML Ͱαϙʔτ͍ͯ͠ΔػցֶशϞσϧ
BigQuery ML ͷಛ BigQuery ML ͷಛඪ४ͷ SQL ΫΤϦΛ༻ͯ͠ɺػցֶशϞσϧͷ࡞ɾධՁɾ༧ଌͰ͖ Δͷ͕ಛͰ͢ɻBigQuery ʹσʔλ͕อଘͯ͋͠Εɺ͙͢ʹ࣮ߦͰ͖·͢ɻBigQuery ͷσʔλ ΛΤΫεϙʔτ͠ػցֶशͷڥΛ֎෦Ͱ࡞͠ͳ͘ͱɺطଘͷ SQL πʔϧεΩϧͰ࣮ߦͰ͖ ΔͷͰػցֶशʹৄ͘͠ͳ͍ํͰؾܰʹѻ͏͜ͱ͕Ͱ͖ΔͱظͰ͖·͢ɻ // ϩδεςΟοΫճؼͷϞσϧΛ࡞͢Δྫ CREATE OR REPLACE MODEL ‘fishing_report.model‘ OPTIONS(model_type=’logistic_reg’) AS SELECT prefecture, IF (COUNT(*) < 20, 0, 1) AS label FROM ‘fishing_report.latest‘ GROUP BY prefecture 1.3 BigQuery ML Ͱαϙʔτ͍ͯ͠ΔػցֶशϞσϧ BigQuery ML ͰઢܗճؼɺϩδεςΟοΫճؼɺk ฏۉ๏ͱ͍ͬͨػցֶशϞσϧ͕αϙʔτ ͞Ε͍ͯ·͢*5ɻ͜ΕΒʹՃֶ͑ͯशࡁΈͷ TensorFlow ͷϞσϧ͔Β BigQuery ML ϞσϧΛ࡞ ͯ͠ɺBigQuery ML Ͱ༧ଌ͢Δ͜ͱͰ͖·͢ɻBigQuery ML ͕αϙʔτ͍ͯ͠ΔػցֶशϞσ ϧͷಛΛ؆୯ʹه͠·ͨ͠ɻ ઢܗճؼ ઢܗճؼɺ͋Δઆ໌มͷมԽʹԠͯ͡తม࿈ଓతʹมԽ͢ΔؔੑΛϞσϧԽ͢Δख๏ Ͱ͢ɻͨͱ͑ɺച্σʔλΛ༻͍ͯϞσϧΛ࡞͠ಛఆͷͷΞΠςϜͷച্Λ༧ଌ͍ͨ࣌͠ ʹ༻͠·͢ɻϥϕϧ࣮ʢਖ਼ෛͷແݶେ NaN ʹ͢Δ͜ͱͰ͖·ͤΜʣͰͳ͚ΕͳΓ· ͤΜɻ ϩδεςΟοΫճؼ ϩδεςΟοΫճؼ͋Δࣄ͕ى͜Δ֬Λى͜Δɾى͜Βͳ͍ͱ͍ͬͨΑ͏ͳɺతมΛ 2 ߲ʢ0 ͔ 1 ͔ʣͰྨ͢Δͱ͖ʹ༻͢Δख๏Ͱ͢ɻͨͱ͑ɺ͓٬༷͕ಛఆͷΛߪೖ͢Δ͔ɺ ͠ͳ͍͔Λ༧ଌ͍ͨ࣌͠ʹ༻͠·͢ɻ·ͨɺBigQuery ML Ͱ 2 ߲͚ͩͰͳ͘ɺ3 ߲Ҏ্ͷଟ *5 2020 1 ݄ݱࡏ 2 -
ୈ 1 ষ BigQuery ML ͰՌσʔλΛੳ 1.4 ऩूͨ͠Ռσʔλͷಛ ߲ϩδεςΟοΫճؼαϙʔτ͍ͯ͠·͢ɻ k
ฏۉ๏ k ฏۉ๏ʢk-means ๏ʣΫϥελϦϯάΛ͢ΔͨΊͷख๏Ͱ͢ɻΫϥελϦϯάͱ༩͑ΒΕͨ σʔλશମ͔ΒɺࣅͨΑ͏ͳΛ࣋ͭσʔλͲ͏͠ΛΫϥελͱͯ͠·ͱΊΔख๏ͷ͜ͱΛݴ͍· ͢ɻͨͱ͑ɺސ٬ͷߪങߦಈͷੳΛ͍ͨ͠ͱ͖ʹ༻͠·͢ɻk ฏۉ๏ڭࢣͳֶ͠श*6*7ʹ͋ ͨΔͨΊτϨʔχϯάධՁ༻ʹϥϕϧͷࢦఆσʔλͷׂΛߦ͏ඞཁ͋Γ·ͤΜɻ 1.4 ऩूͨ͠Ռσʔλͷಛ ࠓճͷ࣮ݧΛ͢Δʹ͋ͨͬͯ Web ʹެ։͞Ε͍ͯΔՌσʔλɺ߹ܭ 32511 ݅ʢڕछ 75 छྨʣͷ σʔλΛऩूͰ͖·ͨ͠ɻ·ͨ͜ΕΒͷՌσʔλେͱݺΕΔҰఆҎ্ͷେ͖͞ʹୡͨ͠Ռ σʔλͰ͋ΓɺւͷΓ*8ͷσʔλͷΈऩू͍ͯ͠·͢ɻΑͬͯඞવతʹւͷΓͰΕΔେ͖ ͍ڕͷσʔλͱ͍͏͜ͱʹͳΓ·͢ɻ // σʔλεΩʔϚ - name: ڕछ - size: ڕͷશʢcmʣ - date: ݄ 200612݄͔Β201911݄ͷσʔλ͕͋Δ - prefecture: ಓݝʢւͷແ͍ݝআ͘ͷͰ39ಓݝͱͳͬͨʣ ֤ڕछͷαϯϓϧͱαΠζͷಛ αϯϓϧ͕ଟ͍ڕछ্Ґ 10 ͱαΠζͷ࠷େ࠷খԼهͷ௨ΓͰ͢ɻ60cm Ͱେ͖͍ͱ͞Ε Δͷɺ30cm Ͱେ͖͍ͱ͞ΕͨΓͱڕͷαΠζڕछʹΑͬͯ͞·͟·Ͱ͢ɻ·ͨڕʹৄ͍͠ํ ͳΒ͓ؾ͖͔͠Ε·ͤΜ͕ɺγϩΪεͷ࠷খ͕ 30cm ͩͬͨΓϚμΠͷ࠷খ͕ 50cm ͩͬͨ Γͱɺ࠷খʹ͔ͯ͠ͳΓେ͖ΊͰ͢ɻࠓճͷσʔλେͱݺΕΔσʔλͷΈऩू͍ͯ͠Δͨ Ίɺ࠷খͷҰൠతʹେ͖͍ͱݟͳ͞ΕΔͱͳ͍ͬͯΔͷ͕ಛͰ͢ɻ SELECT name, MAX(size) AS max, MIN(size) AS min, *6 ڭࢣ͋Γֶशɿਖ਼ղͰ͋ΔతมʢϥϕϧʣΛඞཁͱ͢Δֶशํ๏ *7 ڭࢣͳֶ͠शɿਖ਼ղͰ͋ΔతมʢϥϕϧʣΛඞཁͱ͠ͳֶ͍शํ๏ *8 Γ: ԭͳͲਫ্ʹग़ͣւ؛؛ͳͲ্͔ΒڕΛΔ͜ͱɻࠓճͷσʔλւΓͷΈͰ͢ 3 -
ୈ 1 ষ BigQuery ML ͰՌσʔλΛੳ 1.5 ՌσʔλΛ BigQuery ML
Ͱੳ FROM fishing_report.latest WHERE name IN( SELECT name FROM fishing_report.latest GROUP BY name ORDER BY count(name) DESCɹLIMIT 10 ) GROUP BY name ڕछ αϯϓϧ ࠷େ ࠷খ ϚμΠ 3472 95.5 50.0 ϫχΤι 3471 76.0 45.7 Ϛΰν 3101 77.8 50.0 εζΩ 2914 102.9 65.0 ΞΠφϝ 1852 62.2 45.0 ΫϩμΠ 1760 64.5 45.0 ΧϫϋΪ 1737 41.5 30.0 ώϥϝ 1291 103.9 50.0 γϩΪε 1124 37.0 30.0 ϚίΨϨΠ 915 58.0 45.0 1.5 ՌσʔλΛ BigQuery ML Ͱੳ BigQuery ML ΛͬͯՌσʔλΛੳ͠·͢ɻҎԼͷࡐʹ͋Θͤͯ BigQuery ML Ͱ༻Ͱ ͖Δख๏ΛҰ௨Γࢼ͠·͢ɻ k-means ͰڕΛΫϥελϦϯάͯ͠ΈΔ σʔλͷಛͰઆ໌ͨ͠ͱ͓ΓɺڕछʹΑͬͯେখݺΕΔαΠζͷج४ɺ͞·͟·Ͱ͢ɻ k-means ΛͬͯڕछΛখɾதɾେͱྨͯ͠Έ͍ͨͱࢥ͍·͢ɻֶशͰ༻͢ΔσʔλͰ͕͢ɺα ϯϓϧ্Ґ 10 ͷத͔ΒখɾதɾେͷϞσϧΛ͍·͢ɻখ͍͞ॱ͔ΒɺγϩΪεɺϚίΨϨΠɺϚ μΠΛબͼ·ͨ͠ɻ ֶश ֶशͰ༻͢Δ SQL ҎԼʹͳΓ·͢ɻ͜ͷΑ͏ʹ BigQuery ML ͷ SQL ͷࣝͰػցֶ शͷϞσϧ͕࡞Ͱ͖Δͷ͕ಛͰ͢ɻߏจগͳ͍Ͱ͕͢গ͠ղઆ͠·͢ɻCREATE OR 4 -
ୈ 1 ষ BigQuery ML ͰՌσʔλΛੳ 1.5 ՌσʔλΛ BigQuery ML
Ͱੳ REPLACE MODEL ػցֶशϞσϧΛ࡞͢Δ࣌ͷએݴͰ͢ɻmodel_type Ͱճؼੳɺ k-means ͱ͍ͬͨػցֶशͷख๏Λࢦఆ͠·͢ɻnum_clusters ྨ͍ͨ͠Λࢦఆ͠·͢ɻࠓ ճখɾதɾେͱ̏ྨʹ͍ͨ͠ͷͰ 3 Λࢦఆ͠·͢ɻk-means ϥϕϧΛࢦఆ͠ͳ͍ڭࢣͳֶ͠श ʹ͋ͨΔͨΊϥϕϧͷࢦఆ͠·ͤΜɻ·ͨ BigQuery ML Ͱ iteration ͱݺΕΔֶशͷ෮ճ ্͕ݶճ 20 ͱܾ·͓ͬͯΓɺ্ݶճʹୡ͠ͳͯ͘ BigQuery ML ֶ͕शͷ෮Λेͱஅ ͨ͠߹ࣗಈతʹఀࢭ͠·͢ɻ CREATE OR REPLACE MODEL ‘fishing_report.kmeans‘ OPTIONS( model_type = ’kmeans’, num_clusters = 3 ) AS SELECT size AS size, MIN(size) AS min, MAX(size) AS max, AVG(size) AS avg, PERCENTILE_CONT(size, 0.5) OVER() AS median FROM fishing_report.latest WHERE name = ’γϩΪε’ OR name = ’ϚίΨϨΠ’ OR name = ’ϚμΠ’ GROUP BY size ༧ଌ ࡞ͨ͠ϞσϧΛ͍ڕछΛྨ͠·͢ɻ༧ଌʹ ML.PREDICT ͱݺΕΔએݴΛ͠·͢ɻ ֶशͰ༻ͨ͠γϩΪεɺϚίΨϨΠɺϚμΠ͕ͪΌΜͱྨ͞Ε͍ͯΔ͔֬ೝ͠·͢ɻ SELECT CENTROID_ID, name FROM ML.PREDICT( MODEL ‘fishing_report.kmeans‘, ( SELECT name, size AS size, MIN(size) AS min, MAX(size) AS max, AVG(size) AS avg, PERCENTILE_CONT(size, 0.5) OVER() AS median FROM 5 -
ୈ 1 ষ BigQuery ML ͰՌσʔλΛੳ 1.5 ՌσʔλΛ BigQuery ML
Ͱੳ fishing_report.latest WHERE name = ’γϩΪε’ ɹ OR name = ’ϚίΨϨΠ’ OR name = ’ϚμΠ’ GROUP BY size, name ) ) GROUP BY CENTROID_ID, name ORDER BY CENTROID_ID ASC ݁ՌҎԼͷ௨ΓʹͳΓ·ͨ͠ɻ͓͓ΑͦখɾதɾେͱΫϥελϦϯάͰ͖·ͨ͠ɻখ͕ 3ɺத͕ 2ɺେ͕ 1 ͱಡΈऔΕ·͢ɻϚμΠͷΈதͱݟͳ͞Ε͍ͯͨͷ͕͋Γ·ͨ͠ɻ͜Εಉ͡ϚμΠͰ େͱ͞ΕΔͷͱதͱ͞ΕΔͷ͕͋ΔͱಡΈऔΕ·͢ɻ ྨ ڕछ 1 ϚμΠ 2 ϚμΠ 2 ϚίΨϨΠ 3 γϩΪε ֶ࣍शʹ༻͠ͳ͔ͬͨڕΛ༧ଌͨ݁͠ՌͰ͢ɻචऀͷΓܦݧ্ɺ͓ͦΒ͘খɾதɾେʹͳΔ ͩΖ͏ͳͱࢥ͑ΔڕΛϐοΫΞοϓ͠·ͨ͠ɻػցֶशͷ༧ͱൺֱ͕ඞཁͳͷͰචऀͷ༧Ճ ͠·ͨ͠ɻචऀ͕Ͱ͖ΔͳΒػցֶश͠ͳͯ͘ྑ͍ͷͰʁ ͱײ͡Δํ͍Βͬ͠ΌΔ͔͠Ε ·ͤΜ͕ɺ͜Ε͕ 1000 छɺ10000 छͱͳΔ͘͠ͳΓ·͢ɻ ྨ ڕछ චऀͷ༧ 1 εζΩ 1 1 ώϥϝ 1 2 ΞΠφϝ 2 2 ώϥϝ 1 2 ΫϩΨγϥΨϨΠ 2 2 αΪΞΠφϝ 2 2 ψϚΨϨΠ 2 2 εζΩ 1 3 Ωϡηϯ 3 3 Ϛϋθ 3 ͓͓Αͦචऀͷ༧͕ͨΓ·ͨ͠ɻεζΩώϥϝϚμΠͱಉ͡Α͏ʹதͱݟ׳Ε͞ΕΔͷ ͋Γ·ͨ͠ɻ 6 -
ୈ 1 ষ BigQuery ML ͰՌσʔλΛੳ 1.5 ՌσʔλΛ BigQuery ML
Ͱੳ ઢܗճؼͰڕͷαΠζΛ༧ଌͯ͠ΈΔ ઢܗճؼΛͬͯڕͷαΠζΛ༧ଌͰ͖Δ͔ࢼ͠·͢ɻਫ਼্ΛਤΓ͍ͨͨΊɺࠓճٶݝͰ ΕΔεζΩʹରΛߜΓ·ͨ͠ɻ ֶश WHERE ͰಓݝɺαΠζɺ݄ɺ༵ΛߜΓֶश͠·͢ɻֶशʹ༻͢ΔσʔλͷΈͰ ͢ɻσʔλ͕͋ΔҰఆظؒʹִͨΓ͕͋ΔͨΊֶशͰ༻͢Δظ͕ؒΊͱͳ͍ͬͯ·͢ɻ CREATE OR REPLACE MODEL ‘fishing_report.linear_reg‘ OPTIONS (model_type = ’linear_reg’) AS SELECT size AS label, EXTRACT(MONTH FROM date) as month, EXTRACT(DAYOFWEEK FROM date) as dayofweek, FROM ‘fishing_report.latest‘ WHERE date BETWEEN CAST(’2006-01-01’ AS date) AND CAST(’2017-12-31’ AS date) AND prefecture = ’ٶݝ’ AND name = ’εζΩ’ ༧ଌ WHERE ͰΛࢦఆ͠ɺֶशʹ͍ͬͯͳ͍ผͷσʔλͰ༧ଌ͠·͢ɻ༧ଌ݁Ռͱ࣮ࡍͷ σʔλͱൺֱͰ͖ΔΑ͏ʹςʔϒϧΛ݁߹͠·ͨ͠ɻ WITH miyagi_suzuki AS ( SELECT name, date, size AS label, EXTRACT(MONTH FROM date) as month, EXTRACT(DAYOFWEEK FROM date) as dayofweek FROM ‘fishing_report.latest‘ WHERE prefecture = ’ٶݝ’ AND name = ’εζΩ’ AND date BETWEEN CAST(’2018-01-01’ AS date) AND CAST(’2019-12-31’ AS date) ) SELECT name, ROUND(predict.size, 2) AS size, actual.date as date, actual.size AS ac_size, 7 -
ୈ 1 ষ BigQuery ML ͰՌσʔλΛੳ 1.6 ͓ΘΓʹ ABS(ROUND(predict.size -
actual.size, 2)) AS diff, FROM ( SELECT predicted_label as size, date FROM ml.predict(MODEL ‘fishing_report.linear_reg‘, TABLE miyagi_suzuki) ) AS predict JOIN ( SELECT * FROM fishing_report.latest WHERE prefecture = ’ٶݝ’ AND name = ’εζΩ’ AND date BETWEEN CAST(’2010-01-01’ AS date) AND CAST(’2019-12-31’ AS date) ) AS actual ON predict.date = actual.date ORDER BY date ্Ґ 10 ݅ͷݕࡧ݁Ռ ڕछ ༧ଌͷαΠζ ࣮ࡍͷαΠζ ༧ଌͱ࣮ࡍͷࠩ εζΩ 71.56 2018-06-17 71.0 0.56 εζΩ 72.98 2018-07-01 82.0 9.02 εζΩ 72.98 2018-07-08 70.0 2.98 εζΩ 72.56 2019-06-04 70.0 2.56 εζΩ 74.41 2019-08-25 78.0 3.59 εζΩ 76.42 2019-08-29 83.0 6.58 ૾͍ͯͨ͠ΑΓαΠζͷζϨ͕͋Γ·ͤΜͰͨ͠ɻࠓճσʔλྔ͕গͳ͔ͬͨͨΊͷ༧ ଌͰ͏·͍͔͘ͳ͔ͬͨΊɺ݄ͱ༵Ͱ༧ଌͯ͠Έ·ͨ͠ɻΑΓଟ͘ͷͷσʔλΛूΊΔ͜ͱ ͪΖΜେࣄͰ͕͢ɺΓिॕͷલޙʹ͢Δ͜ͱ͕ଟ͍ͷͰ༵ͰߜΔͷ߹ཧతͩͬͨ ͔͠Ε·ͤΜɻֶशલʹσʔλΛ֬ೝͨ͠ΓɺσʔλܗΛ͢Δେ͕͞Α͔͘Γ·ͨ͠ɻ·ͨ ఱؾɺைࣚɺਫԹɺॴɺΓਓɺ࣌ؒͷλΠϛϯάͱ͍ͬͨஅࡐྉͷछྨΛ૿ͤɺ͞Βʹྑ ͍݁Ռ͕ͰΔ͔͠Ε·ͤΜɻ͜ͷΑ͏ͳେࡶͳ༧ଌͱͳΓ·͕ͨ͠ɺͲΜͳσʔλΛετοΫ͠ ͓͖ͯσʔλܗ͕ඞཁ͔ɺώϯτ͕ݟ͑ͨͷͰྑ͍ษڧͱͳΓ·ͨ͠ɻ 1.6 ͓ΘΓʹ ࠓճػցֶशʹ༻͢Δαϯϓϧσʔλ͕গͳ͔ͬͨΓɺఱؾɺைࣚɺਫԹͱ͍ͬͨՌʹؔ ࿈ͦ͠͏ͳσʔλͳ͔ͬͨͨΊେࡶͳ༧ଌ͔͠Ͱ͖·ͤΜͰ͕ͨ͠ɺࣗͷ͖ͳΓͱ͍ͬ ͨςʔϚͰීஈઢܗճؼ k-means ͱ͍ͬͨػցֶशͷٕज़ʹ৮ΕΔ͜ͱ͕Ͱ͖وॏͳମݧͱͳΓ ·ͨ͠ɻBigQuery ML ػցֶश͕͔Βͳ͍ਓͰؾܰʹѻ͑ΔΑ͏͞Ε͍ͯΔͷͱɺࠓճ ͷΑ͏ʹࣗͰσʔλΛूΊͳͯ͘ BigQuery ʹେྔͷαϯϓϧσʔλ͕ެ։͞Ε͍ͯ·͢*9ɻ BigQuery ML ΛػցֶशΛֶͿڭࡐπʔϧͱͯ͠બ͢Δͷ͓͠Ζ͍͔͠Ε·ͤΜɻࠓճͷ هࣄͰ BigQuery ML ʹগ͠ͰڵຯΛ͍͚࣋ͬͯͨͩͨΒ͍Ͱ͢ɻ *9 BigQueryɿ https://cloud.google.com/bigquery/public-data 8 -
ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.1 ͡Ίʹ ΞφϩάήʔϜͷ࡞ΓํΛհ͠·͢ɻࢲ 2018 ͔ΒถޫҰ͞Μ͕݄ 1
ճͷϖʔεͰ։࠵͢ ΔʮදݱಓʯͷߨٛʹࢀՃ͠·ͨ͠ɻ ͦͷߨٛͰɺ ʮจֶήʔϜશूʯͱͯ͠ɺจֶΛϞνʔϑʹΞφϩάΧʔυήʔϜΛ࡞Δ͜ͱͰ ͨ͠ɻࢲɺՆᕸੴʮ͜͜ΖʯΛબΜͰήʔϜΛ࡞Γ·ͨ͠ɻݖརͷΛճආ͢ΔͨΊʹɺஶ࡞ ݖ͕ΕͨͷΛ༻͍·ͨ͠ɻ ΧʔυήʔϜͷ࡞Γํʹಛʹܾ·Γ͋Γ·ͤΜ͕ɺ͜ͷͱ͖ʹಘͨܦݧΛ·ͱΊ·͢ɻҰͭͷࣄ ྫͱͯ͠ࢀߟʹͳΕ͍Ͱ͢ɻ ରಡऀ • ΞφϩάΧʔυήʔϜΛ࡞ͬͯΈ͍ͨਓɻ • ΠϥϨͷ୯७࡞ۀΛࣗಈԽ͍ͨ͠ਓɻ 2.2 ΞφϩάΧʔυήʔϜͱԿ͔ʁ ΞφϩάΧʔυήʔϜɺࢴνοϓͷΑ͏ͳʹɺҙຯΛ༩͑ͯɺผͷԿ͔ʹݟཱͯ·͢ɻͦͷ ͨΊɺͱͯ͠ͷଆ໘ͱɺ֓೦ͷଆ໘͕͋Γ·͢ɻ ʮ֓೦ʯͱͯ͠ͷΞφϩάΧʔυήʔϜ ήʔϜ͍͋·͍Ͱ͢ɻ͍Ζ͍Ζͳͷ͕ήʔϜͱͯ͠ଘࡏ͍ͯ͠·͢ɻࠓճͷήʔϜɺڱ͍ҙ ຯͰ͢ɻޭͱࣦഊ͕͋ͬͯɺ܁Γฦ͠༡ΔఔͷҙຯͰ͢ɻ֓೦ɺݴ༿ͱҙຯͷෳ߹Ͱ͢ɻ ήʔϜΛઃܭ͢Δͱ͖ɺ֓೦ΛฒͨΓɺͭͳ͛ͨΓɺΈ߹ΘͤͨΓ͠·͢ɻৄࡉޙड़͠·͢ɻ 9 -
ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.3 اը͢Δ ʮʯͱͯ͠ͷΞφϩάΧʔυήʔϜ લड़ͷ௨ΓɺήʔϜ֓೦Λߏͯ͠࡞ΔͷͰ͕͢ɺ࠷ऴతʹʹ͠·͢ɻΑ͋͘Δߏɺശɺ આ໌ॻɺΧʔυɺνοϓɺίϚͰ͢ɻΧʔυɺνοϓɺίϚΛͻͬ͘ΔΊͯίϯϙʔωϯτͱݺͼ· ͢ɻήʔϜΛઃܭ͠ςετϓϨΠͨ݁͠ՌΛɺ͜ΕΒͷʹམͱ͠ࠐΈ·͢ɻ
ήʔϜͱ ΞφϩάʹݶΒͳ͍ͷͰ͕͢ɺήʔϜͱγεςϜͱϞνʔϑ͔Βੜ·ΕΔΠϯλϥΫγϣϯͰ ͢ɻϓϨΠϠʔ͕ΠϯλϥΫγϣϯΛ௨ͯ͡ಘΒΕΔମݧ͕๛͔ͳ΄ͲɺΑ͍ήʔϜͰ͢ɻ ΠϯλϥΫγϣϯ ΠϯλϥΫγϣϯɺΠϯλʔʴΞΫγϣϯͰ͢ɻຊޠ༁Ͱɺ૬ޓ࡞༻Ͱ͢ɻήʔϜͰɺ1 ͭͷΞΫγϣϯͰऴΘΓ·ͤΜɻΞΫγϣϯͷ͋ͱʹɺผͷΞΫγϣϯ͕Կ͔ى͖ͯɺऩଋ͠· ͢ɻͦͷ࿈ଓੑޠͰ͢ͷͰɺήʔϜޠൃੜஔͰ͢ɻ༡Ϳͨͼʹ͓͠Ζ͍ޠ͕ੜΈग़͞ ΕΔΑ͏ʹܙΛߜΓ·͢ɻ γεςϜ γεςϜɺΠϯλϥΫγϣϯΛੜΈग़ͨ͢Ίͷ͘͠ΈͰ͢ɻ͘͠ΈʹɺήʔϜΛߏ͢Δཁૉ ͱɺϓϨΠϠʔͷखॱɺ݁Ռͷྑ͠ѱؚ͕͠·Ε·͢ɻ࠷ऴతʹɺओʹઆ໌ॻʹϧʔϧͱͯ͠දݱ ͞Ε·͢ɻ Ϟνʔϑ ϞνʔϑʹΑΓޠ͕๛͔ʹͳΓ·͢ɻಈɺ༯ոɺո्ɺւɺϐϥϛουɺਆɺѱຐɺຐ๏... ͳ ͲͳͲɺ࣮ࡏ͢Δͷɺ૾ͷ࢈ͳͲΛɺήʔϜͷϞνʔϑʹͳΓ·͢ɻϞνʔϑΛ͏·͘ήʔ ϜʹऔΓࠐΉ͜ͱͰɺϧʔϧʹೲಘײ͕ੜ·ΕͨΓɺͦͷੈքʹೖΓ͜Ήָ͠͞ੜ·Ε·͢ɻͪ ΖΜɺϞνʔϑ͕ͳ͍ɺࣈه߸ͱγεςϜ͚ͩͰ࡞ΒΕΔήʔϜ͋Γ·͢ɻͦͷΑ͏ͳήʔϜ ɺ ʮΞϒετϥΫτήʔϜʯͱݺΕ͍ͯ·͢ɻ 2.3 اը͢Δ ࡞ʮ͜͜Ζ -੨य़౻ฤ-ʯͷࣄྫΛॱʹॻ͖·͢ɻ ϞνʔϑΛબͿ จֶήʔϜશूͰɺ·͖ͣͳจֶΛϞνʔϑʹબͼ·͢ɻબͿࡍʹɺ·ͣ੨ۭจݿ͔Β୳ ͠·͢ɻ੨ۭจݿʹ͋Δখઆͯ͢ஶ࡞ݖ͕Ε͍ͯΔͷͰɺϞνʔϑʹબΜͰݖརؔͷ ͜͠͞Λආ͚ΒΕ·͢ɻࢲɺ͖ͳ࡞ͱ͍͏ͷ͕ࢥ͍͔ͭͳ͔ͬͨͷͰɺߴߍ͔ͩͬͨதֶͩͬ 10 -
ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.4 ϓϩτλΠϓΛ࡞Δ ͔ͨͷڭՊॻʹ͋ͬͨՆᕸੴͷʮ͜͜ΖʯΛબͼ·ͨ͠ɻ੨ۭจݿͷϥϯΩϯάͷ্Ґʹ͍ͨࣄ ͱɺڭՊॻʹࡌ͍ͬͯͨͷͰ໊͕ߴ͍ͩΖ͏ͱ͍͏ࢉஈͰͨ͠ɻ࡞ࣗମաܹ͕γʔϯ͕͋ͬ ͨͱهԱ͍ͯͨ͠ͷͰɺ ɺܹతͳήʔϜʹͳΓͦ͏ͩͱࢥ͍·ͨ͠ɻ͋ΒͨΊͯʮ͜͜ΖʯΛಡΈ
·ͨ͠ɻ ൃ͢Δ બΜͩจֶ࡞ΛಡΜͰɺࢥ͍͘୯ޠΛॻ͖ग़͠·͢ɻࢥ͍ग़ͯ͠ɺಡΈฦͯ͠ɺؾʹͳͬͨ୯ ޠɺ࿈͞ΕΔ୯ޠΛͲΜͲΜॻ͖ग़͠·͢ɻͦ͏ͬͯॻ͖ग़ͨ͠ࢴɺ࡞ۀதৗʹࢀর͠· ͢ɻήʔϜͷʹͳΓ·͢ɻ ϧʔϧΛ࡞Δ όΧʹͳͬͯϧʔϧΛߟ͑·͢ɻਂ͘ߟ͑ͣࢥ͍͖ͭͰॻ͖·͢ɻ͍ΖΜͳόΧʹͳΓ·͢ɻ࠷ޙ ͔͍֮͑ͯ͠ͳ͍όΧɺΓ্͕ͬͨͱ͜Ζ͔͍֮͑ͯ͠ͳ͍όΧɺົʹࡉ͔͍ͱ͜ΖΛ͍֮͑ͯΔ όΧɺͳͲ͍ΖΜͳόΧʹͳͬͯϧʔϧΛߟ͑·͢ɻࢲɺ࣍ͷΑ͏ͳϧʔϧΛߟ͑·ͨ͠ɻ •ʮ͓͞Μͱ݁ࠗͨ͠Βউͪɻ ʯ •ʮࣗࡴͨ͠Βෛ͚ɺࣗࡴʹ͍ࠐΜͩΒউͪʯ •ʮಓΛۃΊͨΒউͪʯ •ʮۏ͕͏·͘ͳͬͨΒউͪʯͳͲͰ͢ɻ ͜ΕΒͷϧʔϧΛͱʹϓϩτλΠϓΛ࡞͠·͢ɻ 2.4 ϓϩτλΠϓΛ࡞Δ ඦԁγϣοϓʹച͍ͬͯΔ୯ޠாΛ͍·͢ɻ୯ޠாͷΧʔυʹ࠷ݶඞཁͳใΛͰ͖Δ͚ͩࡶ ʹهೖ͠·͢ɻͦΕΛɺҰਓͰԿʹͳͬͯɺ͋Δఔ༡ΔΑ͏ʹϧʔϧͷৄࡉΛ࡞Γ·͢ɻ༡ ͼͳ͕ΒɺΧʔυΛՃͨ͠ΓɺഁΓࣺͯͨΓΛؾָʹߦ͍·͢ɻ ݁ࠗͨ͠Βউͪ (͓͞Μখѱຐฤ) ؾ·͙Εͳ͓͞ΜΛɺ͋ͷख͜ͷखͰؾΛҾ͘ήʔϜͰ͢ɻ͓͞ΜɺಓΛٻ͢Δ࢟ʹऒ͔ ΕͨΓɺࣗʹ࿀ͯ͘͠ΕΔଶʹتΜͩΓ͠·͢ɻͦͷ͋ͨΓΛநग़͠ɺ͓͞Μ݅ʹҰக͠ ͨͱ͜ΖʹҠಈ͠·͢ɻҠಈͯ͠Β͑ͨΒ 1 Ͱ͢ɻͦͷ݅ɺ ʮ͓͞Μͷؾ࣋ͪʯΧʔυ͕ ࢁࡳʹͳ͓ͬͯΓɺͦͷҰ൪্ͷΧʔυͰ͢ɻ ʮಓ࠷େʹҠಈʯ ʮ࿀࠷খʹҠಈʯͳͲ͕͋Γ·͢ɻ ϓϨΠϠɺ ʮ͓͞Μͷؾ࣋ͪʯΛ༧ͯ͠ɺ ʮಓ 1ʯ ʮಓ 2ʯ ʮಓ 3ʯ ʮ࿀ 1ʯ ʮ࿀ 2ʯ ʮ࿀ 3ʯΛग़͠· ͢ɻ ʮ͓͞Μʯ͕ʮؾ࣋ͪʯʹैͬͯɺҠಈ͠·͢ɻ͓͞Μ͕͖ͨϓϨΠϠϙΠϯτΛಘ·͢ɻ 11 -
ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.5 ςετϓϨΠͱվྑ ࣗࡴͨ͠Βෛ͚ (ࣗࡴճආฤ) ʮࣦ࿀ʯ ʮཪΓʯ
ʮ่ޚʯ ʮͷපؾʯ.... ͦΕͧΕͷΧʔυʹɺࢮʹ͍ͨϙΠϯτͱɺੜ͖͍ͨ ϙΠϯτ͕͋Γ·͢ɻϓϨΠϠ͕ͨͪͤʔͷͰͩͯ͠ɺετϨεΧʔυΛͳΔ͘औΒͳ͍Α͏ʹ͢ ΔήʔϜͰ͢ɻετϨεΧʔυ͕ 3 ຕͨ·Δͱɺࣗࡴ͠·͢ɻ 2.5 ςετϓϨΠͱվྑ ʮࣗࡴͨ͠Βෛ͚ʯҰਓϓϨΠΛ͍ͯͯ͠ɺؾ͕ΜͩͷͰϘπʹ͠·ͨ͠ɻ ʮ݁ࠗͨ͠Βউͪʯͷํɺ͏Ε͔ͣ͠͠ͳײָ͕͍͡͠ͷͰɺ༑ਓͱډञͰςετϓϨΠΛ ߦ͍·ͨ͠ɻ࣮ࡍʹ༡ΜͰΈΔͱɺͲ͏ʹϓϨΠ͕ࡶͰ͢ɻࢲ໘͕͘͞ΓͰ͢ɻ͓͞Μ ͷΧʔυΛҠಈͤͨ͞ΓɺΛܭࢉͨ͠Γͱ͍͏ͷ͕Ͳ͏ʹɺΓʹ͘͘ɺ͓͠Ζ͞ͱϓϨΠ ίετ͕߹͍·ͤΜͰͨ͠ɻ ଞʹɺ ʮ͓͞Μͷؾ࣋ͪʯΛ༧͠Α͏ʹɺطग़ͷΧʔυ͔͠ใ͕ͳ͘ɺॳճͨΜͳΔ ӡʹͳͬͯ͠·͍ͬͯ·͢ɻ͋·Γʹӡήʔ͗͢·ͨ͠ɻͬͱɺӡҎ֎ͷཁૉͰ͋ΔϓϨΠϠͷ அ࡞ઓΛήʔϜʹཉ͘͠ͳΓ·ͨ͠ɻࡶ͞ΛݮΒͭͭ͠ɺӡཁૉΛബΊ͍ͨɺͦ͏͍͏มߋΛ ࡧ͠·ͨ͠ɻ খઆʮ͜͜Ζʯɺઌੜ͕ʮ࿀ࡑѱʯͱݴ͍ͳ͕Βɺ͓͞Μͱ݁ࠗͯ͠͠·͏౻͕ڵຯਂ͍ ࡞Ͱ͢ɻήʔϜΛϞνʔϑͷओʹ͚ۙͭͭઌͷΛղܾ͢ΔͨΊʹɺ ʮ͓͞ΜʯͷཁૉΛ ݮΒ͠·ͨ͠ɻ ʮ͓͞Μͷؾ࣋ͪʯʮ͜͜Ζʯʹมߋ͠·ͨ͠ɻؾ·͙ΕɺӡͷཁૉΛݮΒͯ͠ ૬खͷʮ͜͜ΖʯΛಡΉήʔϜʹํస͠·ͨ͠ɻ खॱΛɺϓϨΠϠͲ͏͠ͷۦ͚Ҿ͖ΛੜΈग़͢Α͏ʹมߋ͠·ͨ͠ɻ ʮ͜͜ΖʯΧʔυ͚͕ͩ ݟΔΑ͏ʹ͠·͢ɻͦ͏͢Δͱɺ·͕ͣ༗རʹͳΓ·͢ɻ͔͠͠ɺ͔ΒΧʔυΛग़͢ͷͰʮ͜͜ Ζʯ͕ಡ·Εͯ͠·͍ͦ͏ʹͳΓ·͢ɻ͕ग़͢ΧʔυͱදଶΛ૯߹తʹ؍͢Δ͜ͱͰɺϓ ϨΠϠʔͲ͏͠ͷಡΈ߹͍͕ੜ·Εͦ͏ͩͱԾઆΛཱͯ·ͨ͠ɻ ࠶ςετϓϨΠΛͨ͠ͱ͜Ζɺ͔ͳΓૂ͍௨ΓͷΑ͍ײ͡ͷήʔϜʹͳΓ·ͨ͠ɻγϯϓϧͰ͢ ͕ϧʔϧ͜ΕͰ֬ఆͱͯ͠ɺ ʮ͜͜ΖʯΧʔυͷऔࣺબΛߦ͍·ͨ͠ɻ ήʔϜϚʔέοτ 2018 ळʹग़ͨ͢ΊʹకΊΓۙ͘ɺ3 ਓઐ༻ͷήʔϜͱ͠·ͨ͠ɻ4 ਓͰ ༡ͦ͏ͳϧʔϧͰ͕͢ɺ3 ਓͰ༡Ϳͷ͕ϕετͰ͋Δ͜ͱͱɺήʔϜͷߏ্Χʔυຕͷௐ ͕ඞཁͳͨΊɺ࣌ؒతʹΓ͖Εͳ͍ͱஅ͠·ͨ͠ɻ 2.6 ΧʔυͷσβΠϯ खॻ͖ͷ୯ޠாͰ༡ͼͭͭɺϦϦʔε༻ͷΧʔυͷσβΠϯਐΊ·͢ɻΧʔυͷσβΠϯʹ Αͬͯ༡ͼ͢͞มΘΔͷͰɺࢼ࡞Λ܁Γฦ͠·͢ɻΧʔυ 30 ຕͱ͕ଟ͍ͷͰɺগ͠ϨΠΞ τΛมߋ͢Δ͚ͩͰɺ30 ճಉ͜͡ͱΛ܁Γฦ͢͜ͱʹͳΓ·͢ɻͦ͜ͰɺΠϥετϨʔλͷε ΫϦϓτΛ࡞͠·ͨ͠ɻεΫϦϓτʹ͍ͭͯޙड़͠·͢ɻ 12 -
ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.7 ࢼ࡞ 2.7 ࢼ࡞ ྔ࢈ͷલʹɺσβΠϯΛ֬ೝ͢Δ্Ͱ࣮ࡍʹҹΛߦ͍·͢ɻॳɺϓϦϯλ͍࣋ͬͯͳ͍ͷ ͰɺίϯϏχͰΧϥʔҹΛߦ͍·ͨ͠ɻҹͨ͠ࢴΛΧολʔͰΓ͠ɺͷΓͰްࢴʹషΓ·͠
ͨɻ͔ͳΓखؒͷ͔͔Δ࡞ۀͰ͢ɻͦΜͳख͕͔͔ؒΒͣɺֹۚతʹίϯϏχͷΧϥʔҹఔͷ ํ๏͕͋Γ·ͨ͠ɻϥΠΦϯ໊ͷʮҟछ໘͚ʯͰ͢ɻ ϥΠΦϯ໊ͷʮҟछ໘͚ʯ ໊ҹͷձࣾଟʑ͋Γ·͕͢ɺΔݶΓʮҟछ໘͚ʯαʔϏε͕͋Δͷ͚ͩ͜͜Ͱ͢ɻ௨ ৗɺ໊ҹಉ͡σβΠϯΛ 100 ຕͰ 1 ηοτͰ͕͢ɺ ʮҟछ໘͚ʯ 20 छྨ·ͰͷσβΠϯΛ 1 ηοτͷจͰ࡞Ε·͢ɻ͞Βʹɺখϩοτͷ 20 ຕηοτͷ߹ 460 ԁͰ͢ɻ20 ຕҎͷήʔϜ Ͱ͋Εʮҟछ໘͚ʯΛར༻͢Δͱɺ460 ԁͰࢼ࡞͕࡞Ε·͢ɻ 2.8 ྔ࢈ ΧʔυͷσβΠϯ͕Ͱ͖ͨΒҹձࣾʹೖߘ͠·͢ɻҹձࣾ͝ͱʹઐ༻ͷΠϥετϨʔλͷςϯ ϓϨʔτ͕͋Γ·͢ɻͦΕʹͯΊͯೖߘ͢Δ͜ͱʹͳΓ·͢ɻ ॳΊͯͷΧʔυҹ ࠷ॳʹʮ͜͜Ζ-੨य़౻ฤ-ʯΛྔ࢈ͨ͠ͱ͖ɺᤈҹಊͱ͍͏ҹձࣾͰҹ͠·ͨ͠ɻᤈҹಊ ɺΧʔυήʔϜͷҹͰฮͱݴΘΕ͍ͯ·͢ɻ ௨ৗͷίʔεͰɺ100 ݸ࡞Γ·ͨ͠ɻ͜ͷίʔεͰɺ100 ݸ͕࠷খϩοτͳͷͰҰ൪ׂߴͰ͢ɻ ҹؔνϥγͳͲͰͦ͏Ͱ͕͢ɺখϩοτׂߴͰ͢ɻͨ͘͞Μ࡞Δ΄ͲʹɺΧʔυ 1 ຕ͋ͨ Γͷஈɺശ 1 ݸͷஈ͕҆͘ͳΓ·͢ɻ ࣮ɺᤈҹಊʹ௨ৗίʔεͷ΄͔ʹɺ ʮখϩοτԠԉҹύοΫʯ͕͋Γ·͢ɻ͜ΕɺԽহശɺ Χʔυͷηοτ͕ѹతʹ҆͘࡞Ε·͢ɻ ͔͠͠ɺ࣌ॳΊͯͷҹͱ͍͏͜ͱͰڵฃ͍ͯͨ͠ͷͰɺ௨ৗͷίʔεͰൃ͠·ͨ͠ɻ͔͠ Φϓγϣϯͨ͘͞Μ͚·ͨ͠ɻ߹ܭɺ 8 ສԁ͔͔Γ·ͨ͠ɻͦͷ࣌ͷɺΦϓγϣϯΛհ͠ ·͢ɻஈ࣌ͷݟੵΓͰ͢ͷͰɺݱࡏͰҧ͏͔͠Ε·ͤΜɻ γϡϦϯΫϥοϓ γϡϦϯΫϥοϓͱɺബ͍ϑΟϧϜͰแΉ͜ͱͰ͢ɻ͙ͬͱͬΆ͘ͳΓ·͢ɻശ 1 ݸ͋ͨΓ 50 ԁͰ͢ɻ 13 -
ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ ശ٧Ί ശ٧Ίɺͦͷ໊ͷ௨ΓശʹೖΕͯΒ͍·͢ɻശ 1 ݸ͋ͨΓ
50 ԁͰ͢ɻγϡϦϯΫϥοϓΦϓ γϣϯΛ͚Δ߹વඞਢͰ͢ɻ આ໌ॻͷંՃ આ໌ॻΛંΓۂ͛ՃΦϓγϣϯͰ͢ɻ1 ຕ 30 ԁͰ͢ɻശ٧ΊΦϓγϣϯʹඞਢʹͳΓ·͢ɻ ল ͕͋͜ΕͷγϡϦϯΫϥοϓͰ͕ͨ͠ɺ݁ՌతʹࣦഊͰͨ͠ɻޙɺ͜͜ΖҰཡΧʔυΛՃͨ͠ Γɺઆ໌ॻΛϦχϡʔΞϧͨ͠Γ͠·ͨ͠ɻ͔͠͠ɺγϡϦϯΫϥοϓΛണ͕͞ͳ͍ͱɺശʹೖΕΔ ͜ͱ͕Ͱ͖·ͤΜɻ݁ہɺྠΰϜͰͦΕΒΛശʹ͘͘Γ͚ͭ·ͨ͠ɻ 2 ճͷΧʔυҹ ߨ࠲ͷडߨੜͨͪͰɺശΛڞ௨Խͯ͠ྔ࢈͠·ͨ͠ɻ1000 ݸ΄Ͳɺྔ࢈ͨ͠ͷͰׂ҆Ͱͨ͠ɻͨ ͩɺڞ௨ͷശͰ͢ͷͰɺλΠτϧઆ໌ͳͲҹͰ͖·ͤΜɻͦͷͨΊɺผ్γʔϧΛ࡞ͬͯషΔ ͜ͱʹ͠·ͨ͠ɻจֶήʔϜશूͷશ࡞͕ಉ͡ശʹͳͬͨͷͰɺ1 ϒʔεʹ 14 ࡞ฒͼ·ͨ͠ɻ ͔ͳΓͷഭྗͰͨ͠ɻ όʔδϣϯ 1 ͷࡏݿ͋Γ·͕͢ɺ͜ͷྲྀΕʹΓ͔ͨͬͨͷͰɺ20 ݸͷΧʔυ͚ͩҹ͢Δ ͜ͱʹ͠·ͨ͠ɻ·ͨɺόʔδϣϯ 2 ɺֆΛೖΕͨΓΧʔυͷݟͨΛมߋ͠·ͨ͠ɻগྔ͚ͩ΄ ͔ͬͨ͠ͷͰɺઌड़ͷʮҟछ໘͚ʯΛར༻͠·ͨ͠ɻ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ ٕज़ॻͱͯ͠ɺ͕͜͜ϝΠϯͱͳΓ·͢ɻΠϥετϨʔλͷεΫϦϓτͰɺεϓϨουγʔτͷ ΧʔυใͱɺςϯϓϨʔτσβΠϯ͔Βɺ30 ຕͷΧʔυσʔλΛ࡞͠·͢ɻ Adobe Extendscript Toolkit CC Πϯετʔϧ Adobe Extendscript Toolkit ͕ͳ͘ͱɺεΫϦϓτͷ࣮ߦՄೳͰ͢ɻ ʮϑΝΠϧ/εΫϦϓτ/ ͦͷଞͷεΫϦϓτʯͰ࣮ߦͰ͖·͢ɻͱ͍͑ɺ͜ͷΞϓϦ͔Β࣮ߦ͢Δํָ͕Ͱ͢ɻτϥϒϧ࣌ ʹΤϥʔϝοηʔδ͕ݟ͑ΔͷͰݪҼͷٻͰ͖·͢ɻ 14 -
ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ ਤ 2.1: Adobe Extendscript
Toolkit CC Χʔυͷจࣈσʔλͷ࡞ εϓϨουγʔτΛ༻ Google υΩϡϝϯτͷεϓϨουγʔτͰΧʔυσʔλΛ࡞͠·͢ɻΧʔυͷछྨࣈϑ ϨʔόʔςΩετΛγʔτʹॻ͖·͢ɻ ࢲ͕࡞ͨ͠ʮ͜͜Ζ-੨य़౻ฤ-ʯԼهεΫγϣͷΑ͏ͳσʔλͰ͢ɻ 15 -
ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ ਤ 2.2: εϓϨουγʔτ εϓϨουγʔτͷྻΛॱʹઆ໌͠·͢ɻ
˙ຕ ͦͷߦͷΧʔυͷຕͰ͢ɻಉ͡Χʔυ͕ෳ͋Δͱ͖ʹ 2 Ҏ্Λೖྗ͠·͢ɻ ˙Χʔυ (ۭന) εΫϦʔϯγϣοτͰۭനͰ͕͢ɺΧʔυͷछྨͰ͢ɻ ˙छྨ ΧʔυͷछྨͰ͢ɻΠϥετจࣈ͕ΓସΘΓ·͢ɻ ˙υοτ ಉ͡ΧʔυͷຕͰ͢ɻ1 ྻͱ͚ͨͷɺ͜͜ΖΧʔυͷʮ௨ৗϧʔϧʯػೳ͕ಉ ͡ͰϑϨΠόʔςΩετ͚͕ͩҧ͏ͷ͕͋ΔͨΊͰ͢ɻ ˙Ϩϕϧ ॻੜΧʔυͷڧ͞Λදݱͨ͠จࣈͰ͢ɻ ˙ϑϨΠόʔ খઆʮ͜͜Ζʯ͔Βൈਮͨ͠ɺϨϕϧʹԠͨ͡ڧ͞Λײ͡ΒΕΔจষͰ͢ɻϧʔϧʹ Өڹ͠·ͤΜɻ ˙λΠτϧ ʮ͜͜ΖʯΧʔυͷϧʔϧͷλΠτϧͰ͢ɻ ˙ϧʔϧ ʮ͜͜ΖʯΧʔυɺͦͷλʔϯͷউར͕݅ॻ͔Ε͍ͯ·͢ɻ͜ͷ݅ͰΧʔυΛ ͠·͢ɻ 16 -
ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ txt ϑΝΠϧʹίϐʔ&ϖʔετ εϓϨουγʔτ͔ΒςΩετϑΝΠϧʹίϐʔ&ϖʔετ͠·͢ɻ͜ͷςΩετɺྻ͕λϒͰ ۠ΒΕ͍ͯ·͢ɻεΫϦϓτͰɺ͜ΕΛར༻ͯ͠ղੳ͠·͢ɻͦͷͨΊɺεϓϨουγʔτͷ
λϒېࢭͰ͢ɻ ςΩετϑΝΠϧͷҰ෦Λൈਮ͠·ͨ͠ɻຊདྷɺ30 ຕͷσʔλ͕͋Γ·͢ɻ Ϧετ 2.1: kokoro-v2.txt() ຕ छྨ υοτ Ϩϕϧ ϑϨΠόʔ 1 ॻੜ ಓ 1 Ұ फڭͱֶ͔ͱ͔͍͏Ή͔͍ͣ͠ɻ 1 ॻੜ ಓ 1 ೋ ࢲେΉ͔͍ͣ͠ॻΛصͷલʹ։͚ͯɺͦΕΛݟ٧ Ί͍ͯ·ͨ͠ɻ ຕ λΠτϧ υοτ ৭ ϧʔϧ ϑϨΠόʔ 1 ͜͜Ζ ௨ৗϧʕϧ 2 ௨ৗ ࿀ͷ࠷େ͕࿀ͷউऀɻ࿀ΛऔΔɻ<br>ಓͷ࠷େ͕ ಓͷউऀɻಓΛऔΔɻ ࢲΛݺͼʹདྷΔͷɺେ͓͞ΜͰͨ͠ɻ 1 ͜͜Ζ ௨ৗϧʕϧ 2 ௨ৗ ࿀ͷ࠷େ͕࿀ͷউऀɻ࿀ΛऔΔɻ<br>ಓͷ࠷େ͕ ಓͷউऀɻಓΛऔΔɻ Ұि͔ؒΓͯ͠ࢲ·̠ͨͱ͓͞Μ͕͍ͬ͠ΐʹ͍ͯ͠ΔࣨΛ௨Γൈ͚· ͨ͠ɻ ςϯϓϨʔτͷ࡞ ΧʔυͷςϯϓϨʔτΛ࡞͠·͢ɻςϯϓϨʔτʹɺશछྨͷΧʔυͷཁૉΛͯ͢ೖΕ· ͢ɻϨΠϠ໊ΞΠςϜ໊ɺεΫϦϓτ͔ΒΞΫηε͢Δͱ͖ʹ༻͢ΔͷͰɺ֯ͷΞϧϑΝ ϕοτͰॻ͖·͢ɻςΩετʹɺԾͷจࣈΛೖΕ·͢ɻֆΛΓସ͑Δ߹ɺͯ͢ͷֆΛೖΕ ·͢ɻ ॻੜΧʔυ ਤ 2.3: ॻੜΧʔυςϯϓϨʔτ 17 -
ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.9 ΠϥετϨʔλΛࣗಈԽ͢Δ ॻੜΧʔυͷʮಓҰʯΛྫʹɺΓସ͑ΔΞΠςϜΛઆ໌͠·͢ɻ • name Χʔυ໊ʮಓҰʯΛೖΕ·͢ɻ
• flavor ϑϨʔόʔςΩετʮफڭͱֶ͔ͱ͔͍͏͍͠ɻ ʯΛೖΕ·͢ɻ • koi-icon ࿀ͷ࣌ͷΠϥετ • koi-back ࿀ͷ࣌ͷ • michi-icon ಓͷ࣌ͷΠϥετ • michi-back ಓͷ࣌ͷ (koi-icon, koi-back) ͱ (michi-icon, michi-back) ͲͪΒ͔ෆཁʹͳΔͷͰɺআ͞Ε·͢ɻ εΫϦϓτͷ࣮ߦ 1. Adobe Extendscript Toolkit CC Λىಈ͠·͢ɻ 2. εΫϦϓτΛೖྗ͠·͢ɻ 3. ࠨ্ͷখ૭ʹ͋Δ࣮ߦڥΛ Adobe Illustrator CC 2018 ʹมߋ͠·͢ɻ ਤ 2.4: ࣮ߦڥͷબ 1. F5 ͔ɺ⾣ϘλϯΛԡ͠·͢ɻ ى activeDocument activeDocument ͔Β֤छςΩετΠϝʔδͳͲͷΞΠςϜΛͨͲΕ·͢ɻΧʔυ͝ͱʹϨΠϠ Λ͚ΔͷͰɺಉ໊͡લͷΞΠςϜ͕͋Γ·͢ɻͦͷͨΊɺϨΠϠͱɺΞΠςϜͷ྆ํΛࢦఆͯ͠Ξ ΠςϜΛ୳͠·͢ɻͦͷίʔυԼهͷΑ͏ʹͳΓ·͢ɻ Ϧετ 2.2: main() // ΞΠςϜΛ໊લͱϨΠϠͰ୳͢ function findItemWithLayer(itemName, layerName) { var items = activeDocument.pageItems for (var n = 0; n < items.length; ++n) { 18 -
ୈ 2 ষ ΞφϩάΧʔυήʔϜͷ࡞Γํ 2.10 εΫϦϓτશจ gist ʹͯʂ item =
items[n] if (item.name == itemName && item.layer.name == layerName) { return item } } } 2.10 εΫϦϓτશจ gist ʹͯʂ ࣮ߦ࣌ʹɺ࣮ߦڥʹ߹ΘͤͯԼهͷઃఆΛมߋ͍ͯͩ͘͠͞ɻίʔυʹશྗͰίϝϯτΛॻ͖ ·ͨ͠ɻ • https://gist.github.com/toymany/5385dff5f44146c48590000035af199d 2.11 ͋ͱ͕͖ ࢲɺΞφϩάήʔϜΛ 1 ࡞࡞Γɺݱࡏ 2 ࡞Λ࡞தͰ͢ɻ·ͩ·ͩɺۦ͚ग़͠Ͱ͕͢ɺ͜͜· Ͱʹ͍ΖΜͳ͜ͱΛֶͼ·ͨ͠ɻଟ͘ͷਓͷखΛआΓͳ͕ΒɺݟΛ͍͖ͨͩͳ͕ΒɺήʔϜϚʔ έοτʹग़ల͢Δͱ͜Ζ·ͰΛ͍Ͱۦ͚ൈ͚·ͨ͠ɻ࡞ۀͷޮԽͷͨΊɺΠϥετϨʔλͷࣗಈ ԽʹνϟϯϨϯδ͠·ͨ͠ɻΞφϩάΧʔυήʔϜΛ࡞Δ͜ͱɺσδλϧήʔϜΛ࡞Δͷͱҧͬ ͨཁૉ͕ͨ͘͞Μ͋ΓͱͯษڧʹͳΓ·͢ɻ͜ͷهࣄ͕ʮ࡞Γ͍͚ͨͲɺͲ͏͠Α͏ʯͱ͍͏ਓͷ ޙԡ͠ʹͳΕ͍Ͱ͢ɻ 2.12 ࢀߟ • Illustrator ࣗಈԽجຊฤ (Adobe JavaScript γϦʔζ (NextPublishing)) https://amzn. to/2GXdb0j • ถޫҰͷදݱಓ https://cul.7cn.co.jp/programs/program_695450.html • ᤈҹಊ https://www.mnd.co.jp/ 19 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦ έʔγϣϯΛ࡞ͬͨ ͜Μʹͪʂ ଠా Ұߦ (@samayotta) Ͱ͢ɻ
ࠓճ mixi tech note ͷεϖʔεΛ͓आΓͯ͠ɺࢲ͕࡞ͨ͠ΞϓϦέʔγϣϯ spread ͷհ Λ͍͚ͤͯͨͩ͞ΔػձΛಘ·ͨ͠*1ɻ؆୯ʹհ͢Δͱɺ͜ͷΞϓϦέʔγϣϯରਓήʔϜʢಛ ʹ TCG/DCG ΧʔυήʔϜʣͷରઓهΛνʔϜͰཧڞ༗͠ɺ݁ՌΛੳ͢ΔͨΊͷ OSS Ͱ͢ɻ ͜ͷهࣄͰຊΞϓϦέʔγϣϯΛ༰ͱٕज़ͷ໘͔Βհ͠ɺ͞ΒʹηϧϑϗεςΟϯά͢Δखॱ Λհ͢ΔͷͰ͢ɻ ͜ͷίϥϜΛಡΜͰಘΒΕΔ༰ҎԼͷ௨ΓͰ͢ɻ • spread ʹ͍ͭͯͷࣝ • Nuxt.js + Firebase αʔόϨεΞϓϦ։ൃͷݟऔΓਤ • ͚ࣗͩͷ spread αΠτͷ࡞Γํ 3.1 spread ʹ͍ͭͯ ΠϯτϩμΫγϣϯ ͍·ɺΧʔυήʔϜͷੈքʹେ͖ͳมԽ͕๚Ε͍ͯ·͢ɻe-sports ͷྲྀߦΛഎܠʹɺඇৗʹଟ͘ ͷਓ͕ΧʔυήʔϜΛϓϨΠ͍ͯ͠·͢ɻ̍̌ສυϧ୯Ґͷߴֹͳใु͕༻ҙ͞ΕͨήʔϜগͳ͘ ͋Γ·ͤΜɻϓϨΠϠʔͨͪΈͳɺΠϯλʔωοτͰௐͨใΛͱʹɺΑΓউ͍ͬͯΔͱ͞Ε ͨྲྀߦͷσοΩΛબ͢ΔΛڧ͍ͯ͘͠·͢ɻͦͯ͠ྲྀߦ·ͨͨؒ͘ʹҠΓมΘΓ·͢ɻͦͷ ͳ͔Λউͪൈ͘ʹɺΦϦδφϧσοΩΛߏங্͍ͯ͑͛ͯ͘͠ͷͱಉ͘͡Β͍ɺ ྲྀಈతͳϝλ ήʔϜΛ၆ᛌͯ͠ੳɾѲͰ͖Δྗɺ͢ͳΘͪʢ͋ΕαΠυϘʔσΟϯάؚΊͯʣྲྀߦʹର͠ ͯ࠷༏ΕͨσοΩΛબΔྗ͕ɺτʔφϝϯτγʔϯͰٻΊΒΕΔΑ͏ʹͳ͖͍ͬͯͯ·͢ɻ ͻͱͭͷղܾࡦͱͯ͠ߟ͑ΒΕΔͷɺ͍ΘΏΔʮνʔϜ੍ʯͰ͢ɻݶΒΕͨෳਓͰཏతʹ *1 https://github.com/IKKO-Ohta/spread 21 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ 3.1 spread ʹ͍ͭͯ ήʔϜΛϓϨΠ͠ɺϝλήʔϜΛ͠ΒΈ௵͠ʹղੳ͠Α͏ͱ͢Δಈ͖ͱݴ͍͑ΒΕ·͢ɻͨͱ͑ ɺMagic: The
Gathering ͷτοϓϓϩɾϓϨΠϠʔͰ͋Δࠤ౻ϨΠબखɺࣗͷରઓίϛϡχ ςΟͰ Google εϓϨουγʔτΛར༻͠ରઓΛूΊ͍ͯΔ͜ͱͰΒΕ͍ͯ·͢*2ɻࢯ͕ར༻ ͍ͯ͠ΔγʔτͷܗࣜҰൠʹެ։͞Ε͓ͯΓɺ୭Ͱར༻ՄೳͰ͢ɻ ࢲ͜ͷΞΠσΞʹೲಘ͠ɺͬͦࣗ͘͞ͰऔΓೖΕΑ͏ͱ͠·͕ͨ͠ɺ͙͢ʹ࠳ંͯ͠͠·͍ ·ͨ͠ɻ՝ͱͯ͠ײͨ͡ͷ࣍ͷΑ͏ͳ͜ͱͰ͢ɻ • ͨͱ͑ΧʔυγϣοϓͰରઓऴྃͨ͠ͱ͖ɺεϚʔτϑΥϯ͔ΒؾܰʹฤूͰ͖ͳ͍ • ΧʔυήʔϜશൠʹ༻Ͱ͖ΔΑ͏ͳҰൠੑΛ࣋ͬͨ͘͠Έʹͳ͍ͬͯͳ͍ • εϓϨουγʔτΑΓ༏ΕͨϢʔβʔମݧΛఏڙ͍ͨ͠ ͜͏͍͏ཧ༝Ͱ࡞ͨ͠ͷ͕ spread Ͱ͢ɻεϚʔτϑΥϯ͔Βؾܰʹରઓ݁ՌΛߘͰ͖ɺΧʔ υήʔϜશൠʹ༻Ͱ͖ΔΑ͏ͳҰൠੑΛඋ͍͑ͯ·͢ɻͪΖΜɺGoogle εϓϨουγʔτͰ࣮ ݱͰ͖͍ͯͨӾཡཧʢݟͤΔਓΛ੍ݶ͢Δʣػೳɺूܭ͢ΔػೳΛඋ͍͑ͯ·͢ɻ *2 https://twitter.com/r_0310/status/1102829047472521216 22 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ 3.1 spread ʹ͍ͭͯ ͳʹ͕Ͱ͖Δ͔ 1. ରઓهͷߘ
ਤ 3.1: ରઓهը໘ 23 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ 3.1 spread ʹ͍ͭͯ ਤ 3.2: εϚʔτϑΥϯදࣔ
͜Ε͕ରઓهͷߘը໘ɺ݁Ռ͕֨ೲ͞ΕͨϖʔδͰ͢ɻ • ରઓΧʔυʹهೖ͠ɺͲΜͲΜετοΫ͍ͯ͘͠ํࣜͰ͢ɻ • σοΩΛ͍ͭ͘ͰొͰ͖·͢ɻ • Gmail ΞΧϯτΛ௨ͯ͡টͨ͠ϢʔβʔͱγʔτΛڞ༗Ͱ͖·͢ɻ 24 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ 3.1 spread ʹ͍ͭͯ • ήʔϜʹԠͯ͡ BO1/BO3(Ұຊઌऔ੍ɾೋຊઌऔ੍)
͕ࣗಈͰΓସΘΓ·͢ɻ 2. ରઓهͷੳ ਤ 3.3: ରઓهͷੳ 25 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ 3.1 spread ʹ͍ͭͯ ਤ 3.4: εϚʔτϑΥϯදࣔ
͜Ε͕ੳͷͨΊͷϖʔδͰ͢ɻ • هͨ͠ରઓΧʔυ͔ΒσοΩ ύϑΥʔϚϯεΛଌఆ͠·͢ɻ • σοΩɹύϑΥʔϚϯεɺͦͷσοΩ͕ͲΕ͘Β͍উ͍ͬͯΔ͔Λ৭ͷೱ୶Ͱࣔ͠·͢ɻ ෦తʹԾઆ:উ̑̌ˋΛͦΕͧΕغ٫ 20%, 10%, 5% Ͱ྆ଆݕఆ͠ɺͦͷ݁ՌʹΑͬͯ 26 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ 3.2 spread ͷٕज़త֓ཁ ৭Λग़͚͍ͯ͠·͢ʢͨͱ͑غ٫ 5% ͷʴଆͰغ٫͞ΕΕ࠷ೱ͍ʣ
ɻ • ͞ΒʹσοΩ͝ͱͷৄࡉͳύϑΥʔϚϯεΛूܭ͠·͢ɻઌޙউͷࠩͲΕ͘Β͍͔ɺBO3 ήʔϜͰϝΠϯϘʔυɾαΠυϘʔσΟϯάޙผͷউΛूܭ͠·͢ɻ[ࣥච࣌Ͱ࣮ த] • σοΩύϑΥʔϚϯεʹϑΟϧλΛ͔͚ΒΕ·͢ɻ[ࣥච࣌Ͱ࣮த] ϗεςΟϯάʹ͍ͭͯ spread αʔό༻ͷͷؚΊͯͯ͢ͷιʔείʔυ͕ MIT ϥΠηϯεͷͱͰެ։͞Εͯ ͍·͢ɻ·ͨࢲ͕ࣗϗεςΟϯάͨ͠αΠτ*3ʹΞΫηεͯ͠શແྉͰར༻Ͱ͖·͢ɻ͔͠͠ ͪΖΜɺࣗͰϗεςΟϯά͢Δ͜ͱͰ͖·͢ɻͨͱ͑ಠࣗΧελϚΠζΛࢪͨ͠ spread Λ ༻͍ͨ͠߹ɺϗετओʹରઓใΛ༬͚ͨ͘ͳ͍߹ͳͲʹɺࣗΦϦδφϧͷ spread Λϗ εςΟϯά͢Δඞཁ͕͋ΔͰ͠ΐ͏ɻ͜ͷهࣄΛಡΉͱɺspread Λߏ͍ͯ͠Δٕज़ʹՃ͑ͯϗε ςΟϯάͷํ๏ʹ͍ͭͯҰ௨ΓͷࣝΛಘΒΕ·͢ɻ࠷ޙ·ͰಡΈऴ͑Δͱ͚ࣗͩͷ spread αΠ τΛ͍࣋ͯΔΑ͏ʹͳ͍ͬͯΔͣͰ͢ɻ ϥΠηϯε MIT ϥΠηϯεͰ͋ΔιϑτΣΞશʹແྉͰ༻Ͱ͖ɺڐͳ͠ʹվͰ͖·͢ɻ༗ঈͰ ൢച͔ͯ͠·͍·ͤΜɻͨͩ͠ɺஶ࡞ݖදཱࣔͭΑ͏ʹ໌ه͠ͳ͚ΕͳΓ·ͤΜɻ·ͨιϑ τΣΞͷར༻ʹ͋ͨͬͯɺ࡞ऀͰ͋Δࢲ (@samayotta) ҰͷΛෛ͍·ͤΜɻ 3.2 spread ͷٕज़త֓ཁ ࠷తʹ spread ʹ͍ͭͯઆ໌͢Δͱɺ όοΫΤϯυʹ Firestore Λ࠾༻ͨ͠αʔόϨε Nuxt.js γϯάϧϖʔδΞϓϦέʔγϣϯͰ͢ɻΞϓϦέʔγϣϯΛϗεςΟϯά͢Δʹ͋ͨͬͯඞͣ͠ϑ ϩϯτΤϯυͷࣝඞཁ͋Γ·ͤΜͷͰɺࢀߟఔͰेͰ͢ɻٯʹར༻ऀͷใΛकΔͨΊόο ΫΤϯυͷηΩϡϦςΟʹ͍ͭͯҙͯ͠ಡΉΑ͏ʹ͍ͯͩ͘͠͞ɻ ϑϩϯτΤϯυ: Nuxt.js Nuxt.js ɺϑϩϯτΤϯυϑϨʔϜϫʔΫͷҰͭͰɺVue.js ʹΑΔ Web ΞϓϦ੍࡞Λચ࿅͞Ε ͨΞʔΩςΫνϟͰߦ͑ΔΑ͏ʹ͢ΔͷͰ͢ɻReact ʹΑΔ Next.js ʹ͍ͭͰਓؾͷ͋ΔϑϨʔϜ ϫʔΫͰɺ2020 1 ݄ݱࡏɺGitHub ͷελʔ 24.9K ͱͳ͍ͬͯ·͢ɻ spread Nuxt.js ͷػೳʹશ໘తʹґଘͯ͠ߏங͞Ε͍ͯ·͢ɻ͍Ζ͍Ζͳہ໘Ͱ໘ͳ࡞ۀΛ γϣʔτΧοτͰ͖Δͷ͕͋Γ͕ͨ͘ɺ։ൃʹ͔͔Δ࣌ؒΛେ෯ʹॖͰ͖͍ͯ·͢ɻ·ͨɺ༏Ε ͨΞʔΩςΫνϟͷ͓͔͛ͰϦϙδτϦͷঢ়ଶΛΫϦʔϯʹอͪଓ͚Δ͜ͱ͕Ͱ͖Δ͜ͱɺNuxt.js *3 https://spread-samayotta.com/ 27 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ 3.2 spread ͷٕज़త֓ཁ ʢͱ͍͏͔ϑϩϯτΤϯυ ϑϧελοΫϑϨʔϜϫʔΫશൠʣʹݟͷ͋ΔΤϯδχΞͰ͋Εશମ Λ͙͢ʹѲͰ͖Δ͜ͱͳͲͷϝϦοτॏཁͰ͢ɻ
Nuxt.js ʹɺ͞Βʹ࣍ͷ̎ΛՃ͑ͯ։ൃޮΛ্͍ͤͯ͞·͢ɻ 1. TypeScript ʹΑΔܕ҆શԽɻ ϦʔμʔϏϦςΟͷߴ͞ɺVSCode ʹΑΔڧྗͳิػೳɺ ϓϩάϥϛϯάதͷࢥߟ͕ܕͱ͍͏நతͳ࣍ݩͰΑΓཧ͞ΕΔ͜ͱͳͲͷϝϦοτ͔ Βɺ TypeScript ͳ͠ͷ࣮͋Γ͑ͳ͍ͱߟ͍͑ͯ·͢ɻ͞Βʹɺspread Ͱ֤छ decorator ϥΠϒϥϦ (vue-property-decorator, vuex-property-decorator) Λ༻͍ͯܕ҆શͷ ߹͍ΛߴΊ͍ͯ·͢ɻ 2. Vuex ͷΫϦʔϯͳར༻ɻ͍ΘΏΔΫϦʔϯΞʔΩςΫνϟΛΏΔ͘࠾༻͍ͯ͠·͢ɻVuex ʹϏδωεϩδοΫʹؔ࿈͢ΔใΛอ࣋͢ΔΑ͏ʹ͠ɺFirestore ͱͷ௨৴෦ʢσʔλ ϕʔεͷߋ৽ɺAPI ௨৴ʣͯ͢͜Εʹू͢ΔΑ͏ʹ͍ͯ͠·͢ɻprops όέπϦϨʔΛ ݮΒ͢ɺίϯϙʔωϯτ୯ҐͰͷϢχοτςετΛΑΓָʹ͢Δɺ·ͨ௨৴෦ͷվมΛΓ ͘͢͢ΔͳͲͷԸܙ͕ड͚ΒΕ·͢ɻ ࢽ໘ͷ্ؔϑϩϯτΤϯυͷ࣮ࡍͷίʔυհ͠·ͤΜ͕ɺ্هͷ༰ΛѲͨ͠͏͑Ͱίʔ υΛಡΜͰ͍ͨͩ͘ͱΑΓશମ͕ݟ௨͍͢͠ͱߟ͍͑ͯ·͢ɻ ˙ίϥϜ: SSR ͷෆ࠾༻ ͳ͓ɺNuxt.js αʔόαΠυ ϨϯμϦϯάΛ Vue.js Ͱ༰қʹ࣮Ͱ͖ΔͰ͞Εͯ ͍·͢ɻαʔόαΠυ ϨϯμϦϯάͱɺͬ͘͟Γͱͨ͠આ໌ͰɺࣄલʹαʔόͰ HTML ͱ ༻͢ΔσʔλΛ·ͱΊͯ࡞ͯ͠ૹ৴ɺͦΕΛ͍͔͚ΔΑ͏ʹϒϥβͰ JavaScript ͷ Ұ෦ΛಡΈࠐΈಈతͳৼΔ͍Λߦ͑ΔΑ͏ʹ͢ΔϨϯμϦϯάํ๏Ͱ͢ɻϢʔβʔҰઌʹ HTML ͷจষΛಡΊΔͷͰ Time To ContentʢԿΒ͔ͷҙຯͷ͋ΔίϯςϯπʹΞΫηεͰ ͖Δ·Ͱͷ࣌ؒʣ͕ॖͰ͖·͢ɻͨͩ͠ spread ͰɺTime To Content ͷվળ͕ཁ্݅ΫϦ ςΟΧϧͰͳ͘ɺඞવతʹϨϯμϦϯάΛߦ͏ͨΊͷ Node.js αʔόΛཁٻ͠ӡӦ͢Δख͕ؒ૿ ͑ΔͷͰɺ͜ͷػೳΛ࠾༻͠·ͤΜͰͨ͠ɻ όοΫΤϯυ: Firestore spread ʮαʔόϨεʯͳΞϓϦέʔγϣϯͰ͕͢ɺຊʹαʔό্ͷॲཧΛԿʹߦ͍ͬͯͳ͍ ͷ͔ͱ͍͑ͦ͏Ͱ͋Γ·ͤΜɻ࣮ࡍɺߘ͞Εͨσʔλͷอଘͱऔಘɺೝূɺটϝʔϧͷૹ৴ ͱ͍ͬͨૢ࡞αʔόͰߦ͏ͷ͕ࣗવͰ͢ɻͦ͜ͰɺGoogle Firebase Λར༻ͯ͠࠷ݶඞཁͳػೳ Λ·͔ͳ͍ͬͯ·͢ɻ • ݱࡏ Firebase ͷແྉϓϥϯͰӡ༻͍ͯ͠·͢ɻ͜͠Ε͕͍ͬͺ͍ʹͳΔΑ͏ͳΒ༗ྉϓ ϥϯ (Blaze) ʹΓସ͑ΔͰ͠ΐ͏ɻੈքதͷΧʔυήʔϜɾϓϨΠϠʔ͕ͬͨͱԾఆͯ͠ 28 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ 3.2 spread ͷٕज़త֓ཁ Firebase Ͱ͑ͳ͍Α͏ͳେنϓϩμΫτʹͳΓΑ͏͕ͳ͍ͱஅ͍ͯ͠·͢ɻ
• σϓϩΠ·Ͱ Firebase Hosting ʹґଘ͍ͯ͠·͢ɻ͜ͷํ๏ͰαʔόίʔυηΩϡϦ ςΟϧʔϧΛҰׅͰσϓϩΠͰ͖·͢ɻαʔόίʔυΛؚΉ͜ͷϓϩάϥϜͰɺҰׅͷσ ϓϩΠ͔ܽͤͳ͍ཁ݅ͩͱߟ͍͑ͯ·͢ɻ σʔλߏɿ Firebase Cloud Firestore ͜ͷΞϓϦͷ firestore σʔλߏ࣍ͷΑ͏ʹͳ͍ͬͯ·͢ɻ spread---{sheetId}---games---{gameId} ͔ͤͬ͘ TypeScript Λར༻͍ͯ͠ΔͷͰɺγʔτ͓ΑͼήʔϜʹ͍ͭͯΠϯλϑΣʔεఆٛΛ ܝࡌ͓͖ͯ͠·͢ɻ Ϧετ 3.1: γʔτͷߏ interface SheetInfo { id: string members: string[] sheetName: string gameTitle: GameTitle decks: string[] bestOf: BestOf } enum BestOf { Bo3 = ’BO3’, Bo1 = ’BO1’ } Ϧετ 3.2: ήʔϜͷߏ interface GameInfo { id: string win: Result black: Bw myDeck: string | null oppDeck: string | null timestamp: string user: string describe: string wins?: Result[] blacks?: Bw[] 29 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ 3.2 spread ͷٕज़త֓ཁ } enum Result
{ win = ’উར’, lose = ’ഊ’ } enum Bw { black = ’ઌख’, white = ’ޙख’ } ೝূ: Firebase Authentification ೝূʹ Firebase Authentification Λ༻͍·͢ɻAuthentification ଟ༷ͳϩάΠϯखஈ ʢGmailɺ FacebookɺTwitter etc.) Λαϙʔτ͠ɺϩʔΧϥΠζݟͨͳͲͷΧελϚΠζ؆୯ͳαʔϏ εͰ͢ɻಋೖʹඞཁͳखॱɺFirebase Λઃఆ͍ͯ͠Εઐ༻ͷϥΠϒϥϦΛదʹઃஔ͢Δ͚ͩ Ͱ͢ɻ Firebase ηΩϡϦςΟ ϧʔϧ Firebase Authentification Λར༻͢ΔͱϩάΠϯػೳΛ؆୯ʹ࣮Ͱ͖·͢ɻ͔͠͠ɺͦΕͱۓ ີʹ݁ͼ͍͍ͯΔ Firebase ηΩϡϦςΟ ϧʔϧΛͲͷΑ͏ʹཧ͢Δ͔গ͚ͩ͠ෳࡶͰ͢ɻར ༻ऀͷใΛकΔͨΊɺηϧϑϗεςΟϯάΛߦ͏ਓ͜ͷ߲ΛඞͣಡΈɺࣗͷϗεςΟϯά͠ ͨ spread ʹద༻͢ΔΑ͏ʹ͍ͯͩ͘͠͞ɻ ηΩϡϦςΟͱ Cloud Firestore ͷΞΫηε Firebase ͰϢʔβʔ͕σʔλϕʔεʹΞΫηεͰ͖·͢ɻͦͷϢʔβʔͷΞΫηεݖݶΛ ੍͢Δͷ͕ Firebase ηΩϡϦςΟ ϧʔϧͰ͢ɻ ͜ͷ࣮ʹ࣍ͷར͕͋Γ·͢ 1. ΫϥΠΞϯτଆ͕ηΩϡϦςΟͷ͔Βղ์͞Ε·͢ɻ͠ΫϥΠΞϯτʹόάΛ࡞ΓࠐΜ ͩ··σϓϩΠͯ͠͠·ͬͯɺσʔλΛ҆શͳ··ʹ͓͚ͯ͠·͢ɻ 2. σʔλϕʔε͕ࣗηΩϡϦςΟΛ୲͍ͬͯΔͷͰɺڮ͠Λ͢ΔαʔόΛඞཁͱ͠·ͤΜɻ σʔλϕʔεͷΞΫηεɺ͞·͟·ͳϧʔϧʹΑ੍ͬͯ͞Ε·͕͢ɺ࠷యܕతͳ੍ϧʔ ϧϢʔβʔ͕ೝূ͞Ε͍ͯΔ͔Ͳ͏͔*4Ͱ͢ɻ *4 https://firebase.google.com/docs/rules/basics?hl=ja#all_authenticated_users 30 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ 3.2 spread ͷٕज़త֓ཁ Ϧετ 3.3: యܕతͳϧʔϧ
service cloud.firestore { match /databases/{database}/documents { match /{document=**} { allow read, write: if request.auth.uid != null; } } } ಡΈॻ͖Λ੍͢Δ݅ͱͯ͠ɺrequest.auth.uid ͢ͳΘͪೝূใΛར༻͍ͯ͠Δͷ͕Θ͔ ΔͰ͠ΐ͏ɻಉ͡Α͏ʹɺCloudStore ʹؚ·ΕΔใ੍ͱͯ͠ར༻Ͱ͖·͢ɻ spread ͰҎԼͷઃఆΛਪ͍ͯ͠·͢ɻ Ϧετ 3.4: ਪϧʔϧ rules_version = ’2’; service cloud.firestore { match /databases/{database}/documents { match /sheet/{sheetName} { allow read; allow write: if request.auth.token.email != null } match /sheet/{sheetName}/games/{game} { allow read, write: if request.auth.token.email in get(/databases/$(database)/documents/sheet/$(sheetName)).data.members; } } } • γʔτ ͲΜͳϢʔβʔಡΈग़͢͜ͱ͕Ͱ͖·͢ɻҰํͰɺ৽͘͠γʔτΛ࡞͢ΔͨΊ ʹϩάΠϯΛඞཁͱ͠·͢ɻ • ήʔϜ ಛʹӅ͞ͳ͚ΕͳΒͳ͍ใͰ͢ɻಡΈॻ͖ͷͲͪΒʹɺͦͷγʔτͷϝϯόʔ Ͱ͋Δ͜ͱΛཁٻ͠·͢ɻ FaaS: Firebase Cloud Functions Cloud Functions(ҎԼ Functions) όοΫΤϯυίʔυΛ HTTPS ϦΫΤετʹԠ࣮ͯ͡ߦ͢Δ αʔϏεͰ͢ɻ͜ΕΛ༻͍ΔͱɺREST API ෩ͷࣄΛαʔόͳ͠ʹ࣮ߦͰ͖·͢ɻ ͨͱ͑࣍ͷΑ͏ʹίʔυΛσϓϩΠ͠·͢ɿ 31 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ3.3 my spread ͷσϓϩΠखॱ Ϧετ 3.5: ping
functions exports.ping = functions.https.onCall((data, context) => { return {text:’pong’} }); ΫϥΠΞϯτଆ͔Β͜ͷΑ͏ʹ HTTPS ϦΫΤετΛߦ͍·͢ (ઐ༻ͷ API ΫϥΠΞϯτ͕ ଐ͍ͯ͠·͢): Ϧετ 3.6: ping-pong const ping = functions.functions().httpsCallable(’ping’) try { const res = await ping() console.log(res.data.text) // => pong } catch (error) { throw new Error(’#ping ERROR’) } ࣮ࡍʹσʔλϕʔεͷߋ৽ Google Analytics ͷ݁ՌͳͲɺHTTPS ϦΫΤετҎ֎ͷτϦΨ Λ༻͍ͯؔΛ࣮ߦ͠ɺΛฦ٫ͤ͞Δ͜ͱ͕Ͱ͖·͢ɻFunctions ͔ΒσʔλϕʔεೝূͳͲ Ұ௨ΓͷใΛࢀরͰ͖ΔͨΊɺαʔόϨεΞϓϦʹ͓͍ͯαʔόͷΛ͜ΕͰ·͔ͳ͑·͢ɻ spread Ͱটϝʔϧͷૹ৴ʹͷΈ͜ΕΛ༻͍͍ͯ·͕͢ɺͨͱ͑৽͘͠ήʔϜ͕ϓογϡ͞ Εͨͱ͖ʹ slack ʹ௨͢Δػೳɺσʔλʹର͢ΔόονॲཧΛఆظతʹ࣮ߦ͢ΔػೳͳͲΛՃ Ͱ͖Δͱߟ͑ΒΕ·͢ɻ 3.3 my spread ͷσϓϩΠखॱ σϓϩΠ࣍ͷखॱͰߦ͍·͢ɻ 1. Node.js ͓Αͼ yarn Λऔಘ͠·͢ɻ 2. ϑϩϯτΤϯυͷίʔυΛར༻Մೳʹ͠·͢ɻ 3. Firebase ʹొ͠·͢ɻ͞Βʹ Firebase CLI Λ༻͍ͯɺspread ʹ Firebase ΛՃ͠·͢ɻ 4. ͍͔ͭ͘ͷઃఆΛ֬ೝͨ͠ΒσϓϩΠ͠·͢ɻ 1. Node.js ͓Αͼ Yarn Λऔಘ Node.js ͷαϙʔτόʔδϣϯɺ2020 1 ݄ 24 ݱࡏͷ҆ఆ൛ʢLTSʣͰ͋Δ 12.14.1 Ͱ͢ɻ nvm ͳͲͷ Node.js όʔδϣϯཧγεςϜΛ͓ΈͰ༻͍ͨͩ͘ͱศར͔ͱࢥ͍·͢ɻ 32 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ3.3 my spread ͷσϓϩΠखॱ $ brew install
yarn --ignore-dependencies • --igonore-dependencies Φϓγϣϯͳͩ͠ͱɺbrew ͕ Node.js 12.14.1 Ҏ֎ͷόʔδϣ ϯΛ߹ΘͤͯάϩʔόϧΠϯετʔϧ͢Δ͜ͱ͕͋Γ·͢ɻҙ͍ͯͩ͘͠͞ɻ 2. ϑϩϯτΤϯυͷίʔυΛར༻Մೳʹ͢Δ ্هͷ४උ͕Ͱ͖ͨΒɺͬͦ͘͞ spread ΛΫϩʔϯ͠·͠ΐ͏ɻ $ git clone https://github.com/IKKO-Ohta/spread ·ͣґଘϥΠϒϥϦΛΠϯετʔϧ͠·͢ɻ $ cd spread $ yarn install ͜ͷ࣌Ͱςετͷ࣮ߦՄೳʹͳΔͣͰ͢ɻ $ yarn test 3. Firebase ʹొ ϒϥβଆૢ࡞ ࠓϒϥβΛ։͖ɺ͋ͳͨͷ Gmail ΞΧϯτʹ Firebase ΛՃ͠·͢ɻ 1. Google ͷެࣜαΠτ*5ʹΞΫηε͠ɺ ʮίϯιʔϧʹҠಈʯΛΫϦοΫɻ ʮϓϩδΣΫτΛ࡞ ʯͰ৽͘͠ Firebase ϓϩδΣΫτΛ࡞͠·͢ɻ໊લͳΜͰྑ͍Ͱ͕͢ɺ͜͜ͰԾ ʹ my-spread ͱ͠·͠ΐ͏ɻར༻نʹಉҙͨ͠ΒϓϩδΣΫτ͕࡞͞Ε·͢ɻ 2. GCP ϦιʔεϩέʔγϣϯΛઃఆ͠·͢ɻࠨϖΠϯͷࣃंϚʔΫ͔ΒηοςΟϯάը໘ʹҠ ಈ͠ɺ্ͷΧʔυ͔Βࣗͷ͖ͳϦʔδϣϯΛઃఆ͠·͠ΐ͏ɻ *5 https://firebase.google.com/ 33 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ3.3 my spread ͷσϓϩΠखॱ 3. ΞϓϦΛՃ͠·͢ɻίϯιʔϧτοϓը໘ʹΓɺதԝ͔ΒʮWebʯΛબͼ·͢ɻΞϓϦͷ χοΫωʔϜԿͰྑ͍Ͱ͢ɻmy-spread
ͱԾʹ͓͖ͯ͠·͢ɻhosting ͷͪʹઃఆ͠· ͕͢ࠓ௨Γա͗Δ͜ͱͱ͠·͢ɻ ʮΞϓϦΛՃʯΛΫϦοΫ͢Δͱɺ͜ͷΞϓϦΛར༻͢ ΔͨΊͷΫϨσϯγϟϧใ͕දࣔ͞Ε·͢ɻগ͠ΤσΟλʹΓɺϓϩδΣΫτϧʔτʹ c redentials σΟϨΫτϦΛ࡞͠·͢ɻcredentials/firebasekey.ts ͱ͍͏ϑΝΠϧ໊ Ͱɺ࣍ͷΑ͏ʹॻ͖·͢ɻͪΖΜ export default ͷதͷ෦͖͞΄Ͳϒϥβʹද ࣔ͞ΕͨΫϨσϯγϟϧใͰ͢ɻvar firebaseConfig ʹೖ͞Ε͍ͯΔΦϒδΣΫτΛ export default ͢ΔΑ͏ʹ͠·͢ɻ ਤ 3.5: Firebase ͷίϯιʔϧը໘ɻࠨ্ʹηοςΟϯάͷϦϯΫ͕ɺதԝʹΞϓϦՃϘλϯ͕ ͋Δ Ϧετ 3.7: credentials/firebasekey.ts export default { apiKey: *** , authDomain: ***, databaseURL: ***, projectId: ***, storageBucket: ***, messagingSenderId: ***, appId: ***, measurementId: *** } 34 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ3.3 my spread ͷσϓϩΠखॱ ίϯιʔϧଆૢ࡞ ࠶ͼίϯιʔϧʹΓɺFibase CLI
Λμϯϩʔυ͠·͢ɻ $ yarn global add firebase-tools Firebase Λ spread ʹઃఆ͠·͢ɻݸਓใͷऩू΄͔ɺFirebase ͷઃఆʹ͍͍͔ͭͯͭ͘ͷ࣭ ͕ߦΕ·͢ɻҎԼͷΑ͏ʹճ͍ͯͩ͘͠͞ɻ $ firebase login $ firebase init ... services => firestore, hosting, functions ... Project Setup => Use an existing project => my-spread‘ // ϓϩδΣΫτ໊ ... Firestore Setup => // EnterͰσϑΥϧτ໊ firestore.rulesΛ༻͍Δ =>ɹfirestore.rules Λ্ॻ͖͠ͳ͍ ... Functions Setup => TypeScript => tslintΛ༻͠ͳ͍ // ͜ͷϦϙδτϦeslintʹΑͬͯཧ͞Ε͍ͯΔͨΊ => ͍Ζ͍ΖͳϑΝΠϧͷ্ॻ͖ʹ͍࣭ͭͯ͞ΕΔ͕ɺશ෦্ॻ͖͠ͳ͍ => Y // ґଘؔͷΠϯετʔϧ ... Hosting Setup => dist // public directoryσϑΥϧτʹͤͣɺnuxtͷgenerateઌʹ߹ΘͤΔ => Yes // γϯάϧϖʔδΞϓϦέʔγϣϯͱͯ͠ઃఆ͢Δ͔ʁ Firebase initialization complete! ͱදࣔ͞ΕͨΒྃͰ͢ɻ ͳ͓ɺ͜ͷաఔͰ࣍ͷΤϥʔ͕ग़Δ͔͠Ε·ͤΜɻ • Error: Error fetching Firestore indexes 35 -
ୈ 3 ষ ΧʔυήʔϜͷରઓཧɾੳΞϓϦέʔγϣϯΛ࡞ͬͨ3.3 my spread ͷσϓϩΠखॱ Firestore ωΠςΟϒ ϞʔυΛఆ͍ͯ͠·͢ɻDataStore
Ϟʔυʹઃఆ͞Ε͍ͯΔ߹ɺ GCP ܦ༝ͰωΠςΟϒϞʔυʹΓସ͑Δඞཁ͕͋Γ·͢ɻFirestore ͷϖʔδ͔Β GCP ʹΞΫη εͯ͠มߋ͠·͠ΐ͏ɻ 4. ॳظઃఆϑΝΠϧͷ֬ೝ ηΩϡϦςΟϧʔϧͷઃఆ͓Αͼ Functions ͷઃఆΛߦ͍·͢ɻREADME_about_firestore_set tings.md Λݟͳ͕Βɺੜ͞ΕͨϑΝΠϧ firestore.rules ͓Αͼ functions/src/index.ts Λ֬ೝ͠·͢ɻ • firestore.rules ͷઃఆηΩϡϦςΟ্ॏཁͰ͢ɻඞͣ֬ೝ͍ͯͩ͘͠͞ɻ • functions/src/index.ts টϝʔϧΛૹ৴͢ΔͨΊͷॲཧͰ͢ɻটϝʔϧͷૹ৴ݩ Gmail ΞΧϯτΛઃఆ͠·͢ɻԼͷΑ͏ͳίϚϯυͰઃఆͰ͖·͢ɻͨͩ͠ɺ͜ͷ Gmail ΞυϨεશʹઐ༻ͷ৽͍͠ͷΛ࡞͢ΔΑ͏ʹ͍ͯͩ͘͠͞ɻηΩϡϦςΟϦεΫΛ૿ େͤ͞ΔͨΊͰ͢ɻొͨ͠ΞΧϯτͰܯࠂϝʔϧΛड͚औΔͷͰɺ༰Λཧղͨ͠͏͑ ͰڐՄ͢ΔΑ͏ʹ͍ͯͩ͘͠͞ɻ firebase functions:config:set gmail.email="[email protected]" gmail.password="your-password" ͍Α͍ΑσϓϩΠͰ͢ɻ $ yarn generate $ firebase deploy ਖ਼ৗʹऴྃͨ͠ΒσϓϩΠऴྃͰ͢ɻϗεςΟϯάઌͷ URL ͕දࣔ͞Ε·͢ɻ ࠓޙ development ڥͰ࣮ߦ͢Δ͜ͱͰ͖·͢ɻ $ yarn dev ͓ർΕ͞·Ͱͨ͠ʂ ίϛοτͯ͘͠ΕΔํ͕૿͑Δͱ͏Ε͘͠ࢥ͍·͢ɻΑΖ͓͘͠ئ͍͠·͢ɻ 36 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγε ςϜ ॳΊʹ ຊষɺฐࣾͰ 2019/12/07 ʹֶੜ͚ʹ։࠵ͨ͠ڝٕϓϩάϥϛϯάίϯςετʹ͓͍ͯɺͦͷ
ΠϕϯτਐߦͷͨΊʹઃܭͨ͠ճγεςϜͱӡ༻ํ๏ʹؔͯ͠ɺͦͷιϑτΣΞΛઃܭ։ൃͨ͠ ୲͕ࣗͦͷγεςϜ֓ཁͱܦҢͱ͓͠ΖΈΛࢲݟͰղઆ͢ΔͷͰ͋Δɻͩͨઌʹ͓͑ͯ͠ ͓͖͍ͨɻຊষιʔείʔυΛ͋·Γܝࡌ͍ͯ͠ͳ͍ɻͱ͍͏ͷɺԿΛͲͷΑ͏ʹ͢Δ͔ͷΈ ߹Θ͕ͤओʹͳ͔ͬͨΒͰ͋ΔɻͦͷͿΜɺιϑτΣΞγεςϜΛܭը͢Δͱ͖ʹͲΜͳࢹͰ ԿΛߟ͑Δ͔ͷώϯτͱͳΔ͜ͱΛئ͍ຊষΛ·ͱΊΔɻ 4.1 Πϕϯτʹ͍ͭͯ Unity Engine Challenge by mixi GROUP ɹͱ ਤ 4.1: ϛΫγϧܝࡌهࣄΑΓൈਮ 37 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.2 ४උ ϛΫγΟάϧʔϓͰɺ ʮgit challengeʯ
ʮTDD challengeʯ ʮBug Shooting ChallengeʯͳͲɺΤϯ δχΞࣾһ͕࣮ʹ͍ۙڥΛ༻ҙ͠ɺֶੜ͚ͷٕज़ΠϕϯτΛఆظతʹ։࠵͍ͯ͠Δɻ2019 12 ݄ʹ৽ͨͳδϟϯϧͷ Challenge γϦʔζͱͯ͠ɺ ʮUnity Engine Challenge by mixi GROUPʯ ʢhttps://mixil.mixi.co.jp/report/6788ʣΛ࣮ࢪͨ͠ɻ Twitter ϋογϡλάɿ#mixi_unity *1 ࢀՃऀΒ͞ΕΔ໊̎ͰνʔϜΛΈࢦఆͷ GitHub ϦϙδτϦ͔Β՝Λड͚औΔɻ՝ ʹ߹ΘͤͨϓϩάϥϜΛ͕ࣗ Unity Ͱ࣮͠ɺ͔ͭεϚʔτϑΥϯʹϏϧυͯࣗ͠ΒΞϓϦʹ࣮ ͨ͠ػೳΛ͍ϦΞϧੈքͰղΛग़͢ͱ͍͏ͷɻϦΞϧੈքʹ՝ͷͨΊͷಡΈऔΓը૾ εϙοτ͕ઃ͚ΒΕ͓ͯΓɺଞνʔϜͷಈ͖Λԣʹࣗͨͪෛ͚ͯͳΒͳ͍ͱࣗવͱڝ૪ҙཉ ͕ૡཱ͖ͯΒΕΔɻ͍͠՝ɺಘҙෆಘҙͷɺݶΒΕͨ࣌ؒͱ͍͏͜ͱ͋Γɺ̎ਓͷνʔϜ ϫʔΫΛ͍͔ʹ׆༻͢Δ͔େ͖ͳݤͱ͍͑Α͏ɻͦ͏ͯ͠൴ΒձΛา͖ճͬͯҰتҰ༕͠ɺ Λ্͛ͯઓΛଓ͚͍ͯ͘ɺͱ͍͏͍࠵͠Ͱ͋Δɻ ࠓճͷ՝ʹ̏ͭͷ͕͋Γɺ ʮARʯ ʮShaderʯ ʮMulti ϓϨΠʯ͔ΒͦΕͧΕ͍͔ͭ͘ͷ՝ ͕༻ҙ͞Ε͍ͯΔɻͦΕͲͷॱ൪ʹ࢝ΊͯΑ͘ɺνʔϜͰ୲ͯ͠ɺҰॹʹਐΊͯࣗ༝ɻ՝ ʹΑͬͯ༻ҙ͞Ε̙ͨ̏ϞσϧͷΞηοτΛར༻ͨ͠ΓɺඞཁͳϛυϧΣΞΛར༻ͨ͠Γɺ ܭࢉΛ͝Γ͝ΓͬͨΓͱɺUnity ͱϓϩάϥϛϯάΛ࣮ફతʹߦ͏૯߹తཧղ͕ٻΊΒΕΔͱ͍ ͏ͷͩɻ ҎԼͷ·ͱΊ͜Ε͔ΒຊষͰޙड़͢Δݕ౼ΛਐΊͨ݁Ռͱͯ͠ࢸͬͨͷͰ͋Δɻ 1. ֤νʔϜ͝ͱʹϒϥβ͔Β͜ͷνϟϨϯδճγεςϜΛ։͍ͯࢀՃ͢ΔɻࢀՃνʔϜͱճ ঢ়گձϝΠϯϞχλʔʹҰཡ͞Ε͍ͯΔɻ 2. GitHub ͔Β՝Λड͚औΓɺඞཁͳͷΛ࣮͢ΔɻͦΕͧΕணख͢ΔͨΊͷϓϩδΣΫτ ͕͞Ε͓ͯΓ clone ͯ࢝͠ΊΔɻ 3. ձʹઃஔ͞Εͨग़ͷͨΊͷಡΈऔΓը૾࣮ફεϙοτ͕͋Γɺͦ͜ͰࣗͰ࡞ͬͨΞ ϓϦΛར༻ͯ͠ղจࣈΛಋ͖ग़͢ɻ 4. νϟϨϯδճγεςϜͷϑΥʔϜ͔ΒղจࣈΛೖྗ͠൱ΛಘΔɻ νʔϜ՝Ҏ֎ʹ͓͍ͯγϯϓϧͳঢ়گ͔ͩͬͨͱࢥ͏ɻ͜Εʹ͍͍͔ͭͯʹͯ͠ߦ͖ண͍ͨ ͔ͷཪͱɺܝͷࣔ͢௨ΓճγεςϜιϑτΣΞʹ͍ͭͯΛղઆͯ͠ߦ͖͍ͨɻ 4.2 ४උ step1. ඞཁͳͷ νϟϨϯδΠϕϯτΛ࣮ࢪ͢Δʹ͋ͨΓɺզʑࢀՃνʔϜ͕ղΛݟ͚ͭग़ͨ͠ͱ͖ʹ͑ ߹Θͤ͢Δํ๏ͱɺෳ͋Δ՝ͷਐঢ়گΛه͢Δํ๏ɺͦΕʹฐࣾͰաڈ 12 ճʹͬ ͯ։࠵͞Εଓ͚͍ͯΔఆ൪Πϕϯτ git challengeʢhttps://github.com/mixi-git-challenge/ publicationsʣ ͷྫΛ౿·͑ͯձͷڊେεΫϦʔϯʹࢀՃνʔϜͷਐঢ়گΛදࣔ͢Δํ๏ *1 Twitterɿ https://twitter.com/search?q=mixi_unity&src=typed_query 38 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.2 ४උ Λݕ౼ͨ͠ɻ͍ΘΏΔϦΞϧग़ήʔϜͰ͋ΕҊ༻ࢴΛࢀՃऀʹ͓͍ͯͯ͠ऴྃ࣌ʹఏग़ͯ͠ Β͑Α͍༁ͳͷ͕ͩɺ͔ͤͬ͘ςοΫاۀΛ͍ͬͯΔΘ͚Ͱ͋ΓిࢠԽ͍ͨ͠ɻϑΥʔίϛϡ χέʔγϣϯͰΤϯλʔςΠϯϝϯτΛఏڙ͍ͯ͠Δͷ͕ฐࣾͰ͋ΓɺΠϕϯτਐߦίϛϡχέʔ
γϣϯͱͯ͠વΓ্͕Γʹߩݙͨ͘͠ͳͬͯ͘ΔϫέͰ͋Δʢࢲͷؾ࣋ͪʣ ɻͨͩ͠ɺࠓޙܧଓ ͯ͠ճͣͭ։࠵͢Δͱ͍ͯ͠Ζ͍ΖͳखʹࠔΒͳ͍ํ๏͕ྑ͍ͷͰαʔόϨεͰ͋Εͦͷํ ͕ྑ͘ɺ͜ͷΞΠσΞΛ࿅ͬͯΈΔ͜ͱʹ͢Δɻ·ͨɺਖ਼ղͯ͠ਐ͕͋Δঢ়گҎ֎ʹɺճ ऀͷೖྗཤྺͷੳίϯςετશମͷࢀՃਐใͷอશ෮چʹ͍ͭͯ୲อ͍ͨ͠ɻɾɾɾͳ ʹΒنײ͕େ͖͘ͳΓͦ͏ͳ͍͕͖ͯͨ͠ɻ Step2. ࣮ݱՄೳੑͷൺֱ ͜ΕΒΛ౿·͑ͯࢥ͍࣮͘ݱํ๏Λฒͯൺֱݕ౼ͯ͠ΈΔ͜ͱͱ͢Δɻ 参加者持参PCで ❏ 回答文字入力はどこで行う? 会場の専用PCで 貸与するタブレットの専用アプリで 回答ソフトウェア配布は避けたい あるいは回答Webページが必要 回答入力場所に足を運ぶのが大変すぎるかもし れない 端末の確保と回答アプリが必要 参加者持参PCで ❏ 課題の成否判定はどこで? 成否判定サーバーで 成否判定PCで 解答自体、もしくは判定ソフトを配るのは避けた い Webサーバー設置とWeb API(成否やりとり)、ド メインが必要 クライアント間通信手段とAPIが必要 MySQLなどのデータベースから ❏ 永続性のある進捗情報はどこから参照する? 成否判定サーバー上のローカルストレージから 成否判定PCのローカルストレージから メモリやmemcachedなどのキャッシュシステムか ら データベースサーバーの設置が必要 データの永続性がない Webサーバー設置が必要 クライアント間通手段が必要 ਤ 4.2: ൺֱݕ౼ ͜ͷΑ͏ʹ͋Ε͜ΕͱύλʔϯΛߟ͑ͯద͍࣮ͯͯ͠ݱ͍͢͠ํ๏ΛΈ͍ͯ͘ɻલهͨ͠Α ͏ʹαʔόϨε͕ྑ͍ͷͰɺΫϥΠΞϯτؒ௨৴ΛԠ༻ͯ͠൱ఆʹΞΫηε͢Δ͍ํ Ͳ͏͔ɻͦͯ͠ճೖྗɺAndroid λϒϨοτΞϓϦ͘͠੩త Web αΠτͰಈ͔͢ϒϥ 39 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.2 ४උ βΞϓϦʢUnity Ͱ build
͢ΔͳΒ WebGL ൛ͱ͍͏͜ͱʣͰ͋Εྑͦ͞͏ɺͱݟཱͯΔɻ Step3. ߏ Unity ͱ Photon ͰΔ චऀຊΠϕϯτͰ՝ͷ࡞ಉ࣌ʹ୲͍ͯ͠ΔɻͦΕ͕ʮMulti ϓϨΠʯύʔτͰ͋Γɺ Unity ͱ Photon (Exit Games ) Λͬͯ՝Λղ͘ͱ͍͏ͷͰ͋ΔɻPhoton ͷબఆཧ༝ʹ ɺචऀ͕ͦΕҎલͷۀܦݧͱͯ͠Կ͔ར༻ͯ͠αʔϏεΠϯ͍ͯͨ͠ܦҢ͕͋Γ͍উख͕ ͔͍ͬͯΔɺͱ͍͏͜ͱ͔Β࠾ͨ͠ɻͰճγεςϜͰͲ͏͢Δ͔ͱ͍͏ͱ͜ͱͰɺظతͳ ͷ͋Δ͜ͱ͔Βɺ͜ͷͲͪΒ͋Δఔಉ͡ Multi ௨৴ڥͰ։ൃ࣮ͯ͠ݱ͢Δ͜ͱ͕ࢲʹͱͬ ͯ࠷దͩͬͨ͜ͱ͔ΒɺճγεςϜʹ Unity ʷ Photon ͷ։ൃΛ࠾͢Δɻ Step4. Platform Platform ্ͷൺֱݕ౼ͷܦҢΛ౿·͑ͯɺࢀՃऀͷڥఏڙͱιϑτΣΞͷཧ͕؆୯ͳ ʮWebGL ൛ʢUNITY_WEBGLʣ ʯͰɺ൱ఆଆͰ࣮ߦϩάΛ؆୯ʹऔͬͯ PC Ͱਫ਼ࠪੳ͕Ͱ ͖ΔΑ͏ʹͨ͘͠ʮσεΫτοϓΞϓϦέʔγϣϯ൛ʢUNITY_STANDALONEʣ ʯͰͱ 2 ͭͷ Platform Λଓ͢Δ͜ͱͱͨ͠ɻ 40 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.3 ։ൃ 4.3 ։ൃ ͜͏ͯ͠ճγεςϜϓϩδΣΫτͷશମͷྲྀΕ͕Ͱ͖͕͋ͬͨ
6. 成否判定 受信と返信 8. スコアボード表示 4. 課題情報取得 +リスト配布 5. 課題リスト 受信と表示 7. 回答入力 送信と成否受信 9. リザルト等演出 3. 参加者版 ifdefでBuildスイッチ 10. 進捗情報 保存と復帰 1. Unity x Photon Multiクライアントソフト ベース開発 2. 主催者版 ifdefでBuildスイッチ ਤ 4.3: ओͳ։ൃఔ ਤ 4.3 ʹ͍ͭͯɺ • ओ࠵ऀͱճऀͰͦΕͧΕιϑτΣΞͷ༷͕ࣜ΄ͱΜͲҧ͏Θ͚͕ͩɺࠜಉ͡ͳ͜ͱͱ ࡞ۀޮͷ߹͔Β 2ɾ3 ͷΑ͏ʹಉҰϓϩδΣΫτͰڥґଘίϯύΠϧͰ͚ͯਐΊͯ ͍Δɻ • 4 ͷ՝ใਖ਼ղҰཡͷऔಘͱ͕ͩɺਐߦͱͯ͠ઌʹܾΊଧͪͰ࣮Λऴ͑ͨޙɺ࠷ऴ తʹ֎෦৴ରԠʹ֦ு͢Δɻ • 6ɾ̓ͷσʔλ௨৴ʹ͍ͭͯ͏গ͠ৄ͘͠Λ࣍ʹਤղ͍ͯ͠ΔͷͰࢀߟʹͯ͠΄͍͠ɻ • 8 ͷείΞϘʔυࢀՃऀʷ՝ਐঢ়گͷϦετը໘͕ͩɺڧ͍ͯղઆ͢ΕࢀՃऀͱ՝ ͕ࢀՃঢ়گ՝ϦετσʔλʹԠͯ͡ՄมͰಈ͘Α͏ʹ͢Δ͜ͱ͕ඞཁͩɻ 41 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.3 ։ൃ 主 催 者
参 加 者 1.デスクトップ アプリ版 2.WebGL版 5.参加 (JoinRoom) 3.ルーム開設 (CreateRoom) 4.room情報 保存 6.メインモニ ターに反映 11.リザルト 9.event受信 (OnEvent) 成否判定送信 10.進捗情報 保存 12.メインモ ニターに反 映 7.回答 フォーム 8.event 送信 (RaiseEvent) ਤ 4.4: ௨৴ͱ૬ޓͷಈ͖ ਤ 4.4 ʹ͍ͭͯɺ • 1 ɺओ࠵ऀଆͷ PC Ͱ൱ఆαʔόɺ͔ͭձͷϝΠϯϞχλʔʹग़͢શνʔϜͷਐ දࣔΛදࣔ͢ΔͷͱͳΔσεΫτοϓΞϓϦέʔγϣϯɻ • 2 ɺ֤ࢀՃऀ͕࣋ࢀ͢Δݸਓॴ༗ PC Λͬͯࣗ੮ͰͦΕͧΕ͕͑ΔճೖྗϑΥʔϜͷ WebGLɻ • 3,5,8,9 ɺPhoton Ͱߦ͏ωοτϫʔΫͷରԠͰ͋Γɺjoinroom Ͱଓ͢Δͱ 6 ͰࢀՃνʔ Ϝ͕૿͑Δɻ • 7 ͰɺࢀՃऀ͕ͷࣗ༝ςΩετೖྗϑΥʔϜʢUIʣ͔ΒճจࣈྻΛೖྗ͠ɺ11 ͷϦβϧτ ςϩοϓʢUIʣͰ൱ΛΓɺਖ਼ղ͍ͯ͠Ε 12 ͷϝΠϯϞχλʔө͞ΕΔɻ 42 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.3 ։ൃ 参加者A 主催者 回答送信
成否送信 全問正解リザルト送信 ReceiverGroup.Others 成否判定 対象者ID 自身ID == 対象者ID? (リザルト) (スルー) ReceiverGroup.MasterClient 回答文字列 自身ID 全問正解? 自身ID == 対象者ID? (コンプリート リザルト) (スルー) ① ② ③ (スルー) ReceiverGroup.Others 全問正解 対象者ID ਤ 4.5: ճͱ൱ͷૹड৴ϑϩʔ ਤ 4.5 ʹ͍ͭͯɺ • ᶃͰࢀՃऀ͕ೖྗϑΥʔϜ͔ΒจࣈΛʮओ࠵ऀʯʹૹ͢Δɻͦͷͱ͖ͷૹใઢʹه ࡌ͍ͯ͠ΔͷͰ͋Δɻ • ্Λड৴ͨ͠ͱ͖ᶄͰ൱ఆͱૹ৴ݩͷૹΓฦ͠Λߦ͏ɻૹใઢͷͱ͓ΓͰ͋Δ ͕ɺOthers ଐੑͰૹ͍ͯ͠Δ͜ͱΛཹҙ͍ͨͩ͘ͱΑ͍ͩΖ͏ɻ • ্Ͱਖ਼ղͩͬͨ߹ʹશਖ਼ղఆΛᶅͰߦ͏ɻશਖ਼ղ࣌ʹಛผͳϦβϧτԋग़ਐϦ ετʹྃͨ͠දهΛߦ͏߹ɺ͜͜Ͱ͚ͯॲཧ͢Δɻ ͜͜·Ͱ͘Δͱޙͻͨ͢Β Unity Ͱ࣮ͱςετͱτϥΠΞϯυΤϥʔͰ͋Δɻ ࣮ͷհ ͔͜͜Β࣮༰ʹ͍ͭͯগ͠৮Ε͍ͨͱࢥ͏ɻओʹ Photon ࣮෦ʹ͍ͭͯղઆ͓ͯ͠ ͜͏ɻ ˞ҎԼ͔Βͷ࣮ྫͰ using লུ͍ͯ͠Δ 43 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.3 ։ൃ Ϧετ 4.1: Photon
ཧجఈ class 1: public class IAppliPunBehaviour : MonoBehaviourPunCallbacks, IOnEventCallback 2: { 3: // ओͳ࣮Ϋϥεɻ΄͔ʹ MonoBehaviourPunCallbacks ͷதΛΈΕ 4: // PhotonϥΠϒϥϦ͔ΒಘΒΕΔCallback͕ؔ͋ΔͷͰ୳ͬͯ΄͍͠ɻ 5: public virtual void OnEvent(EventData photonEvent){} 6: public override void OnRoomListUpdate(List<RoomInfo> _roomList){} 7: public override void OnRoomPropertiesUpdate(Hashtable propertiesThatChanged){} 8: public override void OnPlayerPropertiesUpdate(Player target, 9: Hashtable changedProps){} 10: public override void OnPlayerEnteredRoom(Player newPlayer){} 11: public override void OnPlayerLeftRoom(Player otherPlayer){} 12: public override void OnCreateRoomFailed(short returnCode, string message){} 13: public override void OnJoinRoomFailed(short returnCode, string message){} 14: public override void OnJoinRandomFailed(short returnCode, string message){} 15: public override void OnConnectedToMaster(){} 16: public override void OnJoinedLobby(){} 17: public override void OnLeftLobby(){} 18: public override void OnJoinedRoom(){} 19: public override void OnLeftRoom(){} 20: public override void OnDisconnected(DisconnectCause cause){} 21: } Ϧετ 4.2: ૹड৴ཧ࣮ class 1: public class MasterReciver : IAppliPunBehaviour 2: { 3: // PhotonͷWebίϯιʔϧ͔Βೖख͢ΔAppId 4: // ຊདྷScriptableObjectͳͲ͔Βઃఆ͢Δ͕આ໌্ׂѪ 5: public static string AppId; 6: // ࢦఆ͢Δ߹ଓ࠶։࣌ʹલճͱಉҰUserͱݟͳͤΔ 7: // ຊདྷηʔϒσʔλͳͲ͔Βઃఆ͢Δ͕આ໌্ׂѪ 8: public static string UserId; 9: // ೖྗϑΥʔϜͰઃఆ͢Δݺশ 10: // ຊདྷೖྗϑΥʔϜηʔϒσʔλͳͲ͔Βઃఆ͢Δ͕આ໌্ׂѪ 11: public static string NickName; 12: // Photonͷଓ։࢝ 13: public static void ConnectUsingSettings() 14: { 15: PhotonNetwork.AuthValues = new AuthenticationValues(); 16: PhotonNetwork.AuthValues.UserId = UserId; 17: PhotonNetwork.NetworkingClient.NickName = NickName; 18: PhotonNetwork.PhotonServerSettings.AppSettings.AppIdRealtime = AppId; 19: PhotonNetwork.PhotonServerSettings.AppSettings.AppVersion = 20: MasterReciver.AppVersion; 21: PhotonNetwork.ConnectUsingSettings(); 22: } 23: // ͦͷ΄͔֤छPhotonͷΠϕϯτൃಈʹ࿈ಈͯ͠৭ʑ࣮͢Δ͕ࠓճলུ 24: // ྫ͑ɺOnConnectedToMaster, JoinedLobbyͰը໘ભҠ 25: // OnRoomListUpdate, OnPlayerPropertiesUpdate ·ΘΓͰUIΛߋ৽͢Δॲཧ 44 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.3 ։ൃ 26: // OnJoinRoomFailed,
OnDisconnected, ͱ͖ʹ࠶ଓμΠΞϩά 27: } ·ͣϦετ 4.1 ͷιʔείʔυʹ͍ͭͯɻ ຊιϑτΣΞશମͱ Photon ͱͷڮ͠ͱͯ͠ Photon ཧΫϥεΛ̍ͭਾ͑ΔɻPhoton Πϕ ϯτ Callback ͕ݺΕΔΑ͏ʹ Photon.Pun.MonoBehaviourPunCallbacks class Λੜ͠ɺRai seEvent ͷ௨͕ಘΒΕΔΑ͏ʹ Photon.Realtime.IOnEventCallback interface Λ༻͢Δɻ جఈΫϥεͱ࣮Λ͚ΔඞཁੑέʔεόΠέʔε͕ͩࠓճϦετ 4.2 ͷΑ͏ʹϚωδϟʔ࣮ ͷͨΊʹ override ͍ͯ͠Δɻಉ͡ Photon ΠϕϯτΛड͚औͬͯߦ͏ΞΫγϣϯҧ͍͕͋Δͱ͖ ʹద͍ͯ͠ΔͩΖ͏ɻ ͦͯ͠ Photon αʔόͷଓؔΛ ConnectUsingSettings Ͱઃ͚Δɻޙड़ͷଓ Button ͔ Β࣮ߦͤ͞ΕΑ͍ɻதͰ Photon ͷ Setting Λॻ͖͍͑ͯΔɻ ͦΕҎ֎ͰͷରԠͱͯ͠Լʹྫࣔ͢ΔΑ͏ͳը໘ UI ͱΠϕϯτͷඥ͚ͮΛ͢ΕΑ͍ͩΖ͏ɻ 1. Photon αʔόͷʮଓ։࢝ ButtonʯΠϕϯτͰ্ͷଓ։࢝ؔΛݺͿ 2. Room Ϧετը໘ભҠͱ OnRoomListUpdate ͰϦετөΛ࡞Δ 3. Room ରࢦఆ࣌ʹ Room ໊Ͱ JoinRoom ͢Δ 4. ओ࠵ऀ൛ͰೖྗϑΥʔϜΟϯυΛݺͼग़ͤ CreateRoomɺJoinOrCreateRoom Λݺ ΜͰ Room Λ࡞ΕΔΑ͏ʹ͢Δ 5. ओ࠵ऀɾࢀՃऀͦΕͧΕ OnJoinedRoom ͨ͠Βຊฤͷը໘ʹભҠ͢ΔΑ͏ʹ͢Δ 6. ओ࠵ऀ൛ࢀՃऀҰཡͱਐදࣔΛߦ͏ͷͰɺ՝ҰཡΛࢀՃऀʹૹΔ 7. OnRoomPropertiesUpdateɺOnPlayerPropertiesUpdateɺOnPlayerLeftRoom ͷؔ࿈Ͱ ใ͕ߋ৽͞ΕͨΒ UI ө͢Δ 8. ࢀՃऀ൛ɺ՝ҰཡΛड͚औͬͨΒҙ՝ʹରͯ͠ճೖྗ͕Ͱ͖ΔΑ͏ʹೖྗϑΥʔϜ ݺͼग़͠ϘλϯΛ՝Ϧετͱͯ͠දࣔ͢Δ ೖྗϑΥʔϜΟϯυɺϑΥʔϜɾૹ৴ɾΩϟϯηϧ͕ஔ͍ͯ͋ΕΑ͍ɻ Web αʔόɾσʔλϕʔεαʔό͕ͳ͍͜ͱΛ౿·͑ͨྫ֎ॲཧ ྫ֎ॲཧ͖ͭͷͰ͋Δ͕ɺࠓճಛʹେࣄͳ෦ճઢःஅʹਚ͖Δɻॠஅ Wi-Fi ճઢःஅ ͔Β෮ؼͨ͠߹ʹࢀՃঢ়گΛશһཱͯͤΔ͜ͱ͕ॏཁͰ͋Δɻ͞ΒʹճઢͷॠஅͷͨͼʹຊͰ ͋ΔࢀՃऀͷ՝औΓΈΛ્͠ͳ͍Α͏ʹ͠ͳ͚ΕͳΒͳ͍ɻ 45 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.3 ։ൃ 1. 主催者 回線断絶時
❏ 回線切断に備える例外 自動再接続、復帰後にRoomが残っていれ joinして MasterClientを任意に取り戻せる機能をつける 2. 参加者 回線断絶時 自動再接続 3. 全員 回線断絶時 主催者 Room情報とUser進捗情報をストレージ保存、参 加者 UserId値を固定する 5. 主催者ソフトウェア 再起動 3 対策 とおり情報 ストレージ保存な で再起動後でも すべて復旧させる 6. 参加者ソフトウェア 再起動 3 対策 とおりUserId値を固定しておき主催者端末側で マッチさせ再開させる 7. 主催者誤操作によるルーム解除時 主催者がRoomを閉じる操作をしても同一RoomIdxUserId なら再開となることとする 4. 参加者 接続失敗タイムアウト時 ダイアログ再接続 ❏ 回線接続タイムアウトに備える例外 ❏ 再起動対策 ❏ 誤操作対策 ਤ 4.6: ճઢʹؔ͢Δྫ֎ύλʔϯ ϩʔΧϧηʔϒ࣮ྫ MasterReciever Ϋϥεͷ OnRoomPropertiesUpdat ࣌ʹԼه onRoomPropertiesUpdate Λݺ ͼग़ͯ͠ਵ࣌อଘ͢ΔΑ͏ʹࠐΉɻ Ϧετ 4.3: Room ใอଘ struct 1: [Serializable] 2: public struct RoomPropertySavedata 3: { 4: public List<Pair> datas; 5: [Serializable] 6: public struct Pair 7: { 8: public string k; 9: public string v; 10: } 11: } 46 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.4 ൃల Ϧετ 4.4: UnityChallenge
Ϋϥε 1: public class UnityChallenge 2: { 3: public void onRoomPropertiesUpdate(Hashtable propertiesThatChanged) 4: { 5: // ओ࠵ऀ͔ࢀՃऀ͔Λఆ͢ΔԿΒ͔ 6: if (MasterReciver.IsRoomMasterClient) 7: { 8: // game master 9: // ߋ৽͞ΕͨroompropertyΛอଘ༻structʹऔΓ·ͱΊΔ 10: roompropertysavedata = new RoomPropertySavedata() 11: { 12: datas = PunUtility.room.CustomProperties 13: .Where(c => c.Value.GetType() == typeof(string)) 14: .Select(c => new RoomPropertySavedata.Pair() 15: ɹɹɹɹɹɹɹɹɹɹɹɹɹɹ { k = (string)(c.Key), v = (string)(c.Value) }) 16: .ToList() 17: }; 18: // ԿΒ͔ͷࣗલͷηʔϒϢʔςΟϦςΟ (ToJsonͰςΩετอଘ) 19: SavedataUtility.Save(roompropertysavepath, roompropertysavedata); 20: // ओ࠵ऀͷ՝Ϧετߋ৽ 21: // example~ 22: } 23: else 24: { 25: // student 26: // ࢀՃऀͷ՝Ϧετߋ৽ 27: // example~ 28: } 29: } 30: } 4.4 ൃల Πϕϯτͱͯ͠ͷԋग़Λߟ͑Δ جຊػೳ͕ݟ͑ͨͱ͜ΖͰ࣮ࡍʹ։࠵͞Εͨ࣌ͷ༷ࢠΛΠϝʔδͯ͠ΈΔ͜ͱͱͯେͰ͋ Δɻ͔ͦ͜Βݟ͖͑ͯͨޮՌతͳΠϕϯτԋग़ͱͯ͠ʮαϯυʯͱʮཷΊʯͱ͍͏ཁૉͰ͋Δɻࠓ ճͰকདྷతͰ͜ͷ͋ͨΓͱςϩοϓԋग़ӡӦʹΑΔ࣮گ͕खް͘Ͱ͖͍͚ͯΕ͔ͬ͠Γͨ͠ Πϕϯτײ͕ग़ͯ͘ΔͩΖ͏ɻࠓճಋೖͨͦ͠ͷ̎ͭͷཁૉʹ͍ͭͯͦͷظ͢Δͱ͜ΖΛղઆͯ͠ ͓͜͏ɻ·ͣʮαϯυʯͱʮཷΊʯ̎ͭີͳؔʹ͋ͬͯɺͦͦޮՌతͳαϯυ͕ͳ ͚ΕཷΊཱ͕͠ͳ͍ͱ͍͏ͷͰ͋Δɻࠓճಛผʹ੍࡞ͯ͠ఏڙͯ͘͠ΕͨαϯυνʔϜʹ ײँۃ·Γͳ͍ɻ ࡞ͨ͠αϯυҎԼͷ௨Γɻ 47 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.4 ൃల ❏ サウンドリスト no.
name 画面 用途 image 1 send_message 送信画面 回答送信 (静かに) 2 result_ok テロップ 正解 (静かに) 3 result_ng テロップ 不正解 (静かに) 4 completed_all テロップ 全問正解 (静かに) ਤ 4.7: αϯυϦετ ͜ΕҎ্ͳ͍͘Β͍࠷খݶͳ༁͕ͩɺͦΕͰඞཁेͰɺͳ͔ͳ͔ධͩͬͨɻ#01 ͷ͍߹Θͤ ԻͰ͔ͬ͠Γͦͯ͠ҧײ͕ͳ͍ΪϦΪϦͷ͞ͷʮཷΊʯΛೖΕ͔ͯΒ#02/#03 ͷ൱Իͱը໘ς ϩοϓ͕࿈ಈ͖ͯͬ͠Γͱ൱Λࣔ͢༁Ͱ͋Δɻ͜ͷ΄ΜͷҰॠͷ࣌ؒʹਓ͔ͳΓଟ͘ͷ͜ͱΛ ߟ͑ΔΒ͘͠ɺۓுͱߴ༲ͦͯ͠ୡײΛɺճΛೖྗͨ͠ຊਓ͚ͩͰͳ͘ɺձશମʹɺຯ߹Θ ͤͯ͘ΕΔͱ͍͏͔͚͠Ͱ͋Δɻ ӡ༻ͷޮԽΛߟ͑Δ ޮతͳӡ༻ͱɺՔಇσʔλߋ৽͕؆ܿͰߴͳεΩϧΛඞཁͱͤͣɺࣦഊ͠ʹ͍͘ɺͱ͍ͬ ͨ͜ͱ͕ڍ͛ΒΕΔͩΖ͏͔ɻۙͷঢ়گ͔Βɺස͕ߴ͍ͱΈΒΕΔཁ݅ͱͯ͠ʮ՝ͷ͑ͷ ߋ৽ʯͱ͍͏͜ͱͰ͋ͬͨɻͪΖΜσʔλϕʔεͳͲͳ͍ɻ·ͨ͞Βʹෳࡶʹ͢Δཁૉͱͯ͠ʮࢀ Ճऀຖʹਖ਼ղ͕ҟͳΔΑ͏ʹ͢Δʯͱ͍͏ͷؚ͕·Ε͍ͯΔɻͳΜͱ͍͏͜ͱ͔ɺͭ·Γʮ͑Ұ ཡΛࢀՃνʔϜ͚ͩଟॏʹ࣋ͭʯʹ͍͠Կ͔Λ࣮ͨ͠͏͑ͰɺͦΕΛ؆୯ʹཧ͢Δ͜ͱΛߟ ͑Δඞཁ͕͋Δɻͨͩɺ՝ղܾͱৗʹ͔݁Βฉ͚͍ͨͬͯγϯϓϧͳͷɺ͕ϕετͳͷͰ ͳ͍͔ͱࢥ͏ɻࠓճͱͬͨखஈσʔλςʔϒϧΛ tsv*2ςΩετϑΝΠϧͰ cdn ʹஔ͍ͨͱ͍͏ ͷͰ͋ΔɻӡӦऀͱࢀՃऀͷΈڐՄͷΞΫηε੍ݶ͕͚ͭΒΕΔ͜ͷΑ͏ͳ։࠵ڥ͍͋·ͬ ͯͱ͍͏͜ͱ൱Ίͳ͍͕ඞཁेͰ͓Γ͕͘Δɻͦͷཧ༝ʹɺ՝ͱਖ਼ղҰཡΛ Spreadsheet ʢExcel දʣͰཧ͍ͯͨͨ͠ΊɺදΛίϐʔ&ϖʔετ͢Ε tsv ܗࣜςΩετʹͳΔͷͰ cdn ʹ্ ͛ͤ࠷ܰྔσʔλγʔτߋ৽ʹͳΔ͜ͱ͔Βɺσʔλϕʔε͍ΒͣͷΈͳΒͣग़ྗϚΫϩ͍Βͣ ͱݴ͏͜ͱ͋ͬͨɻͳ͓ಉ͡Α͏ͳସાஔʹ csv ʹग़͢ͱ͔ɺExcel ΛಡΈࠐ·ͤΔͱ͔ɺ json ʹ͢Δͱ͔ɺग़ྗ͔Β cdn ʹ্͛ͯ͘ΕΔҰ࿈ͷԿ͔ΛΉɺͳͲ͞ΒʹखΛֻ͚Δબࢶ ͋ΔͩΖ͏ɻ·ͨઌʹड़ͨΑ͏ʹࢀՃऀผͷਖ਼ղରԠ͋ΓɺSpreadsheet σʔλϕʔεͷΑ͏ ͳͷͰෳओΩʔͰཧͰ͖ΔߏͷͷͰਖ਼ղҰཡΛཧ͢Δͷ͕࠷దͩɻ *2 ςΩετσʔλʹ͓͍ͯ tab Ͱ۠ΒΕͨܗࣜͷͷɻಉ༷ͷͷʹΧϯϚͰ۠ΔͷΛ csv ͱݺͿɻ 48 -
ୈ 4 ষ Unity Ͱ࡞ΔڝٕܕΠϕϯτͷճγεςϜ 4.4 ൃల (共通正 解文字) Team
A B C D E F G H I J problem key index 0 TeamID TeamID TeamID TeamID TeamID TeamID TeamID TeamID TeamID TeamID ar-1 1 0 解答 解答 解答 解答 解答 解答 解答 解答 解答 解答 ar-2 1 1 解答 shader-1 3 0 解答 shader-2 3 1 解答 shader-3 3 2 解答 shader-4 3 3 解答 shader-5 3 4 解答 解答 解答 解答 解答 解答 解答 解答 解答 解答 shader-6 3 5 解答 解答 解答 解答 解答 解答 解答 解答 解答 解答 multi-1 2 0 解答 multi-2 2 1 解答 multi-3 2 2 解答 multi-4 2 3 解答 multi-5 2 4 解答 ❏ 正解文字テーブル ਤ 4.8: ਖ਼ղσʔλςʔϒϧΠϝʔδ දʹ͍ͭͯɺproblem ྻ͕՝ɺkey ྻ͕՝ΧςΰϦɺindex ͕՝φϯόʔɻ ʮڞ௨ਖ਼ղจࣈʯ͔Β Team AʙJ ·Ͱ͕νʔϜผΛߟྀͨ͠ਖ਼ղςʔϒϧͱͳ͍ͬͯΔɻνʔϜ 0 ʹղ͕ೖ͍ͬͯΔߦͷ՝શνʔϜڞ௨ɺνʔϜผղ͕ೖ͍ͬͯΕݸผղʹɺͱผΕΔͱ ͢Δɻ ͜ΕΒ͕ߟྀ͞Εͯਖ਼ղσʔλཧ͕͍ͩͿָʹͳΓ͞Εͨɻͦͷߕ൹͋ͬͯσʔλςʔϒ ϧͷཧऀ͏·͘୲Ͱ͖͔ͨͬͯʁɺߋ৽ཧ୲ऀࢲ͕ͩԿ͔ͩͬͨͩΖ͏͔ɻ ऴΘΓʹ ୈ 1 ճ։࠵͕େ͖ͳτϥϒϧͳ࣮͘ݱͰ͖ͨͷӡӦϝϯόʔʹΑΔॏͳΔݕূձͱ KPTʢৼ ΓฦΓʹ͍ͭͯͷΑ͋͘ΔϑϨʔϜϫʔΫʣΛ͖ͪΜͱશһͰग़͠߹͑ɺ՝࡞γεςϜʹର͠ ͯ࠷ળΛਚ͔ͤͨ͘Βʹ΄͔ͳΒͳ͍ɻӡӦϝϯόʔશһͷଟେͳΔߩݙͱ׆༂ʹײँ͍ͨ͠ɻͦ͠ ͯͦ͏ԕ͘ͳ͍͏ͪʹ͞ΒʹύϫʔΞοϓͯ͠ୈ 2 ճ͕։࠵͞ΕΔ͜ͱɺͦͯ͠ git challenge ͷΑ ͏ʹ໊Πϕϯτʹ͢Δ͜ͱΛୈ 1 ճϝϯόʔͱͯ͠৺͔Βئ͍ͬͯΔɻ 49 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ ࡞Γํ ຊߘͰ
GitHub API Slack API ͷΑ͏ͳެ։͞Ε͍ͯΔ Web API ͷΫϥΠΞϯτΛ Haskell ༻ʹɺ؆୯ʹ࡞Δʢࣗྲྀͷʣํ๏Λհ͠·͢ɻຊߘͰԼهͷ͜ͱΛهड़͍ͯ͠·͢ʢ֤ύο έʔδʹ͍ͭͯޙड़͠·͢ʣ ɻ • req ύοέʔδΛͬͨ API ΫϥΠΞϯτͷ࡞Γํ • extensible ύοέʔδʹΑΔ API ΫϥΠΞϯτ࡞ͷิॿ • servant ύοέʔδΛͬͨ API ΫϥΠΞϯτͷςετͷॻ͖ํ ·ͨɺͯ͢ͷιʔείʔυ GitHub ͷ matsubara0507/haskell-slack-conversations*1 ϦϙδτϦʹ͋Γ·͢ɻ 5.1 Լ४උ ࣮Λ࢝ΊΔલʹɺ·ͣར༻͢Δ Haskell ύοέʔδͷಛͱɺࠓճࡐʹ͢Δ API ʹ͍ͭͯ հ͠·͢ɻ ར༻ύοέʔδ ຊߘͰίΞͳύοέʔδͱͯ͠ҎԼͷ 4 ͭΛར༻͠·͢ɻׅހͷதར༻ͨ͠όʔδϣϯͰ͢ɻ • aeson (1.4.6.0) : JSON σίʔμɾΤϯίʔμ • req (3.0.0) : HTTP ΫϥΠΞϯτ • extensible (0.7.0) : ֦ுՄೳϨίʔυͷಋೖ • servent (0.17) : Web ΞϓϦέʔγϣϯϑϨʔϜϫʔΫ *1 https://github.com/matsubara0507/haskell-slack-conversations 51 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.1 Լ४උ
·ͨɺຊߘͰ GHC*2 8.6.5 Ͱಈ࡞֬ೝΛ͍ͯ͠·͢ɻ aeson : JSON σίʔμɾΤϯίʔμ aeson*3 σϑΝΫτελϯμʔυͳ JSON ͷσίʔμɾΤϯίʔμύοέʔδͰ͢ɻaeson Ͱ จࣈྻͰ͞Εͨ JSON ΦϒδΣΫτΛதؒߏ Value ܕʹม͠ɺ͜ΕΛҙͷܕมͰ͖· ͢ɻValue ܕ͔ΒͷมΛఆٛ͢Δʹ FromJSON ܕΫϥεͷΠϯελϯεΛఆٛ͢Δඞཁ͕͋Γ ·͢ɻ FromJSON ͷΠϯελϯεྫ import Data.Aeson (FromJSON(..), withObject, (.:)) data ToDo = ToDo { todoID :: Int, title :: Text, done :: Bool } instance FromJSON ToDo where parseJSON = withObject "ToDo" $ \v -> ToDo <$> v .: "id" <*> v .: "title" <*> v .: "done" -- Generics ͱ͍͏ػೳΛͬͯ΄ͱΜͲ࣮Λল͘͜ͱՄೳͰ͢ ·ͨɺٯʹҙͷܕ͔Β JSON ʹม͢Δʹ ToJSON ܕΫϥεͷΠϯελϯεΛఆٛ͠·͢ɻ ToJSON ͷΠϯελϯεྫ import Data.Aeson (ToJSON(..), object, (.=)) instance ToJSON ToDo where toJSON todo = object [ "id" .= todoID todo , "title" .= title todo , "done" .= done todo ] req : HTTP ΫϥΠΞϯτ API ΫϥΠΞϯτΛ࡞Δͷʹ͔ܽͤͳ͍ͷ͕ HTTP ΫϥΠΞϯτύοέʔδͰ͢ɻࠓճ HTTP ΫϥΠΞϯτύοέʔδͱͯ͠ req*4 ͱ͍͏ͷΛ͍·͢ɻHaskell ʹ͍͔ͭ͘ͷ HTTP Ϋϥ ΠΞϯτύοέʔδ͕͋Γ·͢ʢ༗໊ͳͷʹߜͬͯෳ͋Γ·͢ʣ ɻͦͷதͰ req ൺֱతޙ *2 GHC 2020 ݱࡏɺ Haskell ͷσϑΝΫτελϯμʔυͳॲཧܥͰ͢ *3 https://hackage.haskell.org/package/aeson-1.4.6.0 *4 https://hackage.haskell.org/package/req-3.0.0 52 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.1 Լ४උ
ൃͷϥΠϒϥϦͰ͢ɻͦͷͨΊ req ɺreq ࡞ऀ͕ࣗ΄͔ͷϥΠϒϥϦʹରͯ͠ײ͡ΔෆຬΛվ ળ͢ΔతͰ࣮͞Ε͍ͯ·͢ʢෆຬʹ͍ͭͯ req ͷ README ʹهड़͞Ε͍ͯ·͢ʣ ɻ req ύοέʔδͷར༻ྫ import Data.Aeson (Value) import Data.ByteString (ByteString) import Network.HTTP.Req displayUserInfo :: ByteString -> IO () displayUserInfo token = runReq defaultHttpConfig $ do response <- req GET url NoReqBody jsonResponse (oAuth2Bearer token) liftIO $ print (responseBody response :: Value) where url = https "slack.com" /: "api" /: "users.info" extensible : ֦ுՄೳϨίʔυͷಋೖ extensible*5 ύοέʔδ֦ுՄೳϨίʔυͷΑ͏ͳɺߴ֊ΧΠϯυͳσʔλܕΛ؆୯ʹѻ͑Δ ύοέʔδͰ͢ɻߴ֊ΧΠϯυʹ͍ͭͯ͜͜ͰׂѪ͠·͕͢ɺͨͱ͑લड़ͨ͠ ToDo ܕΛ extensible Λͬͯ࣍ͷΑ͏ʹهड़Ͱ͖·͢ɻ extensible ه๏ͷ ToDo ܕ import Data.Extensible type ToDo = Record ’[ "id" >: Int , "title" >: Text , "done" >: Bool ] ϑΟʔϧυʹΞΫηε͢Δ߹ person^.#name ͷΑ͏ʹͰ͖·͢*6ɻ͜Ε͚ͩͷ߹ɺΘ͟ Θ͟ extensible Λར༻͢Δར͋Γ·ͤΜɻextensible ه๏ʹΑΔϨίʔυܕͷେ͖ͳརͷҰ ͭɺϨίʔυϑΟʔϧυΛྻϦετߏͷΑ͏ʹڞ௨ͷॲཧΛద༻ͨ͠͏͑ͰΈࠐΊΔ Ͱ͠ΐ͏ɻͨͱ͑ɺϑΟʔϧυ໊ʢจࣈྻʣͷϦετΛߏங͢Δؔɺ֤ϑΟʔϧυͷ FromJ SON ܕΫϥεͷΠϯελϯεΛͱʹͯ͠ JSON ΦϒδΣΫτʢValue ܕʣΛߏங͢ΔؔͳͲ ͕Ͱ͖·͢ɻޙऀ·͞ʹͦΕࣗମ͕ FromJSON ܕΫϥεͷΠϯελϯεͷؔͰ͋Γɺ͢ͳΘͪ extensible ه๏ͷͯ͢ͷϨίʔυ FromJSON ܕΫϥεͷΠϯελϯεͱͳ͍ͬͯ·͢ʢToJSON ಉ༷Ͱ͢ʣ ɻ͜ͷΑ͏ʹ extensible ه๏ͷϨίʔυϨίʔυʹରͯ͠ڞ௨ͷॲཧΛ࣮͍ͨ͠ *5 https://hackage.haskell.org/package/extensible-0.7 *6 ^ . lens ܥ ύ ο έ ʔ δ ͷ ؔ Ͱ ͢ https://hackage.haskell.org/package/lens-4.18.1/docs/ Control-Lens-Getter.html#v:-94-. 53 -
ୈ5ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.1 Լ४උ ߹ʹ༗ޮͰ͢ɻ servent
: Web ΞϓϦέʔγϣϯϑϨʔϜϫʔΫ servant*7 ͍ΘΏΔ Web ΞϓϦέʔγϣϯϑϨʔϜϫʔΫͷҰͭͰɺܕͱͯ͠ Web ΞϓϦέʔ γϣϯͷ API Λهड़Ͱ͖·͢ɻ ToDo ΞϓϦΛΠϝʔδͨ͠ API ܕͷྫ import Servant.API type API = "todos" :> Get ’[JSON] [Todo] :<|> "todos" :> ReqBody ’[JSON] Todo :> Post ’[JSON] Todo :<|> "todos" :> Capture "id" Int :> ReqBody ’[JSON] Todo :> Put ’[JSON] () :<|> "todos" :> Capture "id" Int :> Delete ’[JSON] () ͪΖΜ͜Ε API ͷϧʔςΟϯάͷఆ͚ٛͩͰɺ࣮ผͰ༩͑·͢ɻ͔͠͠ɺJSON ͱͯ͠ ԿΛฦ͔͢ͳͲ͕ܕͱͯ͠ఆٛ͞Ε͍ͯΔͷͰɺͦͷ௨Γʹ࣮͠ͳ͍ͱܕݕࠪͰΤϥʔͱͳΓ· ͢ɻຊߘͰ࡞͍ͨ͠ओʮެ։͞Ε͍ͯΔ APIʯͷΫϥΠΞϯτ͚ͩͰ͢ͷͰɺservant ςε τͷͱ͖ʹϞοΫαʔόΛ࡞ΔతͰ͍·͢ɻ ࡐ: Slack Conversations API ຊߘͷࡐͱͯ͠ Slack ͷ Conversations API*8 Λ༻͍Δ͜ͱʹ͠·͢ɻSlack API ͱͱ public νϟϯωϧ͔ private νϟϯωϧ͔ DM ͔Ͱ API Λશʹ͚͍ͯ·͕ͨ͠ɺ͜ΕΒΛ͢ ͯ౷Ұͨ͠ͷ͕͜ͷ API Ͱ͢ɻͨͩ͠ɺϨεϙϯε JSON ͷߏόϥόϥͰ͢ʢҙͷܕ ʹσίʔυ͢Δܥٽ͔ͤͰ͢Ͷʣ ɻطଘͷ Haskell ͷ Slack API ΫϥΠΞϯτ*9ͲΕɺݱࡏ͜ ͷ API ʹରԠ͍ͯ͠ͳ͍Α͏ͩͬͨͷͰ͍ͭͰʹࣗ࡞͢Δ͜ͱʹ͠·ͨ͠ʢ2020 1 ݄ʣ ɻͳ͓ɺ Conversations API ͕Ͱ͖ͨͨΊݩͷνϟϯωϧͷछྨຖͷ API 2020 11 ݄ 25 ͔Β͑ͳ ͘ͳΔΑ͏ͳͷͰҙ͠·͠ΐ͏ɻ $ curl https://slack.com/api/conversations.info?channel=...\&token=... | jq { "ok": true, "channel": { "id": "C4LFB6DE0", "name": "general", "is_channel": true, *7 https://hackage.haskell.org/package/servant-0.17 *8 https://api.slack.com/docs/conversations-api *9 https://github.com/mpickering/slack-api https://github.com/jpvillaisaza/slack-web 54 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.2 ࣮:
ଟ૬తͳΫϥΠΞϯτ "is_group": false, "is_im": false, ... } } 5.2 ࣮: ଟ૬తͳΫϥΠΞϯτ ·ͣτʔΫϯͳͲΛอ࣋ͨ͠ SlackApiClient ܕΛఆٛ͠·͢ɻ SlackApiClient ܕఆٛ module Web.Slack.Conversations.Client ( SlackApiClient -- ίϯετϥΫλΤΫεϙʔτ͠ͳ͍ , newClient ) import Data.ByteString (ByteString) type Token = ByteString newtype SlackApiClient = SlackApiClient Token newClient :: Token -> SlackApiClient newClient = SlackApiClient SlackApiClient ܕͷίϯετϥΫλΛΤΫεϙʔτͤͣʹɺઐ༻ͷߏஙؔ newClient Λఆ ͍ٛͯ͠·͢ɻ͜͏͢Δ͜ͱͰɺ֎෦Ϟδϡʔϧ͔Βͷ Token ͷࢀরํ๏͕ͳ͍ͨΊ Token ܕͷར ༻Λ੍ݶͰ͖·͢*10ɻ ͯ͞ɺSlackApiClient ܕ͔Β API ͷ base URL τʔΫϯΛຒΊࠐΜͩϔομͳͲΛੜ͢ Δ෦Λఆٛ͠·͠ΐ͏ɻ͜ͷ࣌ʹɺʑϞοΫαʔόͰར༻Ͱ͖ΔΑ͏ʹܕΫϥεͱͯ͠ఆٛ͠ ͓͖ͯ·͢ɻ Client ܕΫϥε module Web.Slack.Conversations.Client ( Client (..) , SlackApiClient -- ίϯετϥΫλΤΫεϙʔτ͠ͳ͍ , newClient ) import Network.HTTP.Req (Option, Scheme (..), Url, (/:)) import qualified Network.HTTP.Req as Req *10 ͜ͷΑ͏ʹܕ͚ͩΛެ։͠σʔλίϯετϥΫλΛӅณͨ͠Α͏ͳܕΛ Opaque Data Type ͱݺͿͦ͏Ͱ͢ 55 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮:
API ͷઃܭΛܕͰ͢Δ class Client a where type ClientScheme a :: Scheme baseUrl :: a -> Url (ClientScheme a) mkHeader :: a -> Option (ClientScheme a) instance Client SlackApiClient where type ClientScheme SlackApiClient = ’Https baseUrl = const (Req.https "slack.com" /: "api") mkHeader (SlackApiClient token) = Req.oAuth2Bearer token class ҎԼʹ͋Δ type Type Family ͱݺΕΔػೳͰɺܕΫϥεͷΠϯελϯεએݴ͝ͱʹ ෦Ͱར༻͢ΔܕΛࢦఆͰ͖·͢ɻScheme ͱॻ͍ͯ͋Δ෦ΧΠϯυʢܕͷܕʣͷࢦఆͰ͢ɻreq Ͱ HTTP ͔ HTTPS ͔ΛܕͰදݱ͓ͯ͠ΓɺͦΕΒͷܕͷΧΠϯυ͕ Scheme Ͱ͢ɻεΩʔϚ ΛܕͰදݱ͍ͯ͠ΔͷͰɺHTTPS ͷܕͰ HTTP ௨৴Λ͢Δ http ؔΛར༻͠Α͏ͱ͢Δͱܕݕ ࠪͰΤϥʔͱͳΓ·͢ɻΘ͟Θ͟ܕΫϥεͰεΩʔϚΛࣗ༝ʹઃఆͰ͖ΔΑ͏ʹ͍ͯ͠ΔͷɺϞο Ϋαʔόͷ࣌ʹ HTTP Λઃఆ͢ΔͨΊͰ͢ɻ 5.3 ࣮: API ͷઃܭΛܕͰ͢Δ ͯ͞ɺ͔͜͜Β͕͍Α͍ΑຊͰ͢ɻຊߘͰ conversations.info API *11Λ࡞ͬͯΈ·͢ʢ΄ ͔ͷׂ࣮Ѫ͠·͢ʣ ɻ ϨεϙϯεͷܕΛ࡞Δ ࠷ॳʹϨεϙϯεͷܕ Conversation Λఆٛ͠·͢ɻυΩϡϝϯτʹΑΔͱ conversations.in fo ࣍ͷΑ͏ͳ JSON ͕ฦͬͯ͘ΔΑ͏Ͱ͢ɻ conversations.info ͷϨεϙϯε JSON ͷྫ { "ok": true, "channel": { "id": "C012AB3CD", "name": "general", "is_channel": true, "is_group": false, "is_im": false, "created": 1449252889, "creator": "W012A3BCD", "is_archived": false, "is_general": true, "unlinked": 0, *11 https://api.slack.com/methods/conversations.info 56 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮:
API ͷઃܭΛܕͰ͢Δ "name_normalized": "general", "is_read_only": false, "is_shared": false, "parent_conversation": null, "is_ext_shared": false, "is_org_shared": false, "pending_shared": [], "is_pending_ext_shared": false, "is_member": true, "is_private": false, "is_mpim": false, "last_read": "1502126650.228446", "topic": { "value": "For public discussion of generalities", "creator": "W012A3BCD", "last_set": 1449709364 }, "purpose": { "value": "This part of the workspace is for fun. Make fun here.", "creator": "W012A3BCD", "last_set": 1449709364 }, "previous_names": [ "specifics", "abstractions", "etc" ], "locale": "en-US" } } Conversation ܕͱͯ͠දݱ͍ͨ͠ͷ "channel" Ωʔͷ JSON ΦϒδΣΫτͷ෦Ͱ͢ɻҰ ݟ͜ΕΛ͜ͷ··ܕͱͯ͠ॻ͚ྑͦ͞͏Ͱ͕͢ɺ1 ର 1 ͷμΠϨΫτϝοηʔδͷ߹ͷ JSON ࣍ͷΑ͏ͳͷ͕ฦͬͯ͘ΔΑ͏Ͱ͢ɻ conversations.info ͷϨεϙϯε JSON ͷμΠϨΫτϝοηʔδͷͱ͖ͷྫ { "ok": true, "channel": { "id": "C012AB3CD", "created": 1507235627, "is_im": true, "is_org_shared": false, "user": "U27FFLNF4", "last_read": "1513718191.000038", "latest": { "type": "message", "user": "U5R3PALPN", "text": "Psssst!", 57 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮:
API ͷઃܭΛܕͰ͢Δ "ts": "1513718191.000038" }, "unread_count": 0, "unread_count_display": 0, "is_open": true, "locale": "en-US", "priority": 0.043016851216706 } } શવҧ͍·͢ͶɻࠔΓ·ͨ͠ɻ͠ΐ͏͕ͳ͍ͷͰɺID Ҏ֎ΦϓγϣφϧͳϑΟʔϧυͱ͍͏͜ ͱʹͯ͠ఆٛ͠·͢ɻ Conversation ܕఆٛ module Web.Slack.Conversations.Type where type ChannelID = Text type UserID = Text type Conversation = Record ’[ "id" >: ChannelID , "name" >: Maybe Text , "is_channel" >: Maybe Bool , "is_group" >: Maybe Bool , "is_im" >: Maybe Bool , "created" >: Maybe Int64 , "creator" >: Maybe UserID ... -- ׂѪ ] Ϩεϙϯε JSON ΛΑ͘ݟΔͱ "ok":true ͱ͍͏ͷ͕͍͍ͯ·͢Ͷɻ࣍͜ͷ෦Λܕఆٛ ͠·͢ɻ ΤϥʔܕͰϥοϓ͢Δ "ok":true ͱ͍͏͜ͱɺԿ͔Ͱࣦഊͨ͠߹ "ok":false ʹͳΔͰ͋Ζ͏͜ͱ༰қʹ૾ Ͱ͖·͢ɻͦͷ߹ͳΜͱ࣍ͷΑ͏ͳ JSON ͕ฦ͖ͬͯ·͢ɻ Τϥʔͷͱ͖ͷϨεϙϯε JSON ͷྫ { "ok": false, "error": "channel_not_found" } 58 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮:
API ͷઃܭΛܕͰ͢Δ ͜Ε·ͨ JSON ͷߏ͕ΘΓ·͢ɻ͔͠೦ͳ͜ͱʹϦβϧτίʔυ 200 Ͱฦ͖ͬͯ·͢ɻ req 400 ൪ 500 ൪ͷϦβϧτίʔυͰ͋Εඇಉظྫ֎Λ͛ͯ͘Ε·͕͢ɺ200 ͷ߹ ී௨ʹ JSON ͷσίʔυΛࢼΈ·͢ɻͰ͢ͷͰɺ"ok" ͷΛΈͯσίʔυ͢ΔܕΛذ͢Δඞཁ͕ ͋Γ·͢ͶɻͦͷͨΊͷܕ Ok Λ࣍ͷΑ͏ʹఆٛ͠·͢ɻ Ok ܕͷఆٛ import qualified Data.Aeson as J import qualified Data.HashMap.Strict as HM data Ok a = Ok a | Err Text deriving (Show, Eq) instance FromJSON a => FromJSON (Ok a) where parseJSON = J.withObject "Result e a" $ \obj -> case HM.lookup "ok" obj of Just (J.Bool True) -> Ok <$> parseJSON (J.Object obj) Just (J.Bool False) -> Err <$> obj .: "error" _ -> fail "key ‘ok:bool‘ is not found." instance ToJSON a => ToJSON (Ok a) where toJSON (Ok a) = case toJSON a of J.Object obj -> J.Object $ HM.insert "ok" (J.Bool True) obj value -> J.Object $ HM.fromList [("ok", J.Bool True), ("value", value)] toJSON (Err e) = J.Object $ HM.fromList [("ok", J.Bool False), ("error", J.String e)] σίʔυͱΤϯίʔυ͕ରশʹͳ͍ͬͯͳ͍ͷগͬ͠ͱ͠·͕͢க͠ํ͋Γ·ͤΜɻ͜ΕͰ "ok":true ͷ߹ Ok (Record ’[ "channel" >: Conversation]) ͷܕ͕ฦͬͯ͘Δͱ͍͏ ͷΛఆٛͰ͖ΔΑ͏ʹͳΓ·ͨ͠ɻ ϦΫΤετύϥϝʔλͷܕΛ࡞Δ ϨεϙϯεͷܕΛఆٛͰ͖ͨͷͰ࣍ϦΫΤετͷܕΛߟ͑·͠ΐ͏ɻυΩϡϝϯτΛಡΉͱ con versations.info API ʹ࣍ͷΑ͏ͳϦΫΤετύϥϝʔλ͕͋Δ͜ͱ͕Θ͔Γ·͢ɻ • channel : νϟϯωϧ ID (ඞਢ) • include_locale : Ϩεϙϯεʹ locale ΛؚΉϑϥάʢσϑΥϧτ falseʣ • include_num_members : Ϩεϙϯεʹ num_members ΛؚΉϑϥάʢσϑΥϧτ falseʣ ϦΫΤετύϥϝʔλʹجຊతʹඞਢͷͷͱͦ͏Ͱͳ͍ͷ͕͋Γ·͢ɻࠓճߟ͑Δͷඞਢ Ͱͳ͍ํͰ͢ɻ͜ͷΑ͏ͳΦϓγϣφϧͳͷΛදݱ͢Δͷʹ Haskell Ͱ Maybe a ܕΛ͍·͢ɻ conversations.info ͷϦΫΤετύϥϝʔλܕ module Web.Slack.Conversations.API where import Data.Extensible 59 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮:
API ͷઃܭΛܕͰ͢Δ type InfoParams = Record ’[ "include_locale" >: Maybe Bool , "include_num_members" >: Maybe Bool ] -- શͯσϑΥϧτͷ߹͜͏ॻ͖·͢ -- #include_locale @= Nothing <: #include_num_members @= Nothing <: nil ͔͠͠ɺͲ͏ߟ͑ͯ͜ΕΛཧ͢Δͷ໘Ͱ͢Ͷɻ • ύϥϝʔλ͕૿͑ͨͱ͖ʹ Maybe Λॻ͖·͘Δඞཁ͕͋Δ • σϑΥϧτͰྑ͍ͱ͖ʹύϥϝʔλΛ͢ͷ͕໘͍͘͞ ͜Μͳͱ͖ʹศརͳͷ͕ extensible ͷ Nullable*12 Ͱ͢ɻͯ͢ͷϑΟʔϧυ͕ Maybe a ܕͷ ϨίʔυΛఆٛͯ͘͠Ε·͢ɻ Nullable Λ༻͍ͨ conversations.info ͷϦΫΤετύϥϝʔλܕ import Data.Functor.Identity (Identity (..)) -- type Record = xs :& Field Identity Ͱ͢ type OptionalParams xs = xs :& Nullable (Field Identity) type InfoParams = OptionalParams ’[ "include_locale" >: Bool , "include_num_members" >: Bool ] Nullable ʹ࣍ͷΑ͏ͳศརͳ͕ؔ͋Γɺ͜ΕΛ༻͍Δ͜ͱͰ؆୯ʹ෦తͳϦΫΤετύϥ ϝʔλΛ͢͜ͱ͕Ͱ͖·͢ɻ Nullable ͷศརͳؔ -- શͯ Nothing ͳϨίʔυͱಉٛ vacancy :: xs :& Nullable h -- wrench (#include_locale @= True) ͰҰ෦͚ͩ Just ͷϨίʔυΛ࡞ΕΔ wrench :: xs ˧ ys => (xs :& h) -> ys :& Nullable h ϦΫΤετύϥϝʔλͷܕ͕Ͱ͖ͨͷͰɺ࣮࣍ࡍʹ͜ͷܕͷΛϦΫΤετύϥϝʔλͱͯ͠ req ʹ͢ํ๏Λߟ͑·͠ΐ͏ɻreq ͰϦΫΤετύϥϝʔλΛ͢߹ʹ࣍ͷΑ͏ʹॻ͖·͢ɻ *12 https://hackage.haskell.org/package/extensible-0.7/docs/Data-Extensible-Nullable.html 60 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮:
API ͷઃܭΛܕͰ͢Δ req ʹϦΫΤετύϥϝʔλΛ͢ྫ main :: IO () main = runReq defaultHttpConfig $ do response <- req GET url NoReqBody jsonResponse $ "id" =: ("ABCDEFG" :: Text) <> "limit" =: (100 :: Int) <> "flag" =: True ... (=:) ԋࢉࢠͰύϥϝʔλΩʔͱΛ݁ͼ͚ͯɺ(<>) ԋࢉࢠͰύϥϝʔλͲ͏͠Λ݁ͼ͚· ͢ɻϨίʔυͷϑΟʔϧυΛݺͼग़ͯ͠ຖճ͜ΕΒͷԋࢉࢠͰߏஙͯ͠ྑ͍Ͱ͕͢ɺউखʹ req ͢ܗࣜʹมͯ͘͠ΕΔ͕ؔ͋Δͱ͏Ε͍͠ͰΑͶʁ ͏Ε͍͠ঢ়گͷྫ import Web.Internal.HttpApiData (ToHttpApiData (..)) hoge :: InfoParams -> IO () hoge params = runReq defaultHttpConfig $ do response <- req GET url NoReqBody jsonResponse (buildRequestParams params) ... -- (=:) :: (ToHttpApiData a, QueryParam param) => Text -> a -> param -- (<>) :: Monoid param => param -> param -> param buildRequestParams :: (QueryParam param, Monoid param) => OptionalParams xs -> param buildRequestParams = undefined buildRequestParams Λ࡞Δͷ؆୯Ͱ͢ɻલड़ͨ͠ͱ͓Γ extensible ͷϨίʔυϑΟʔϧυ ʹڞ௨ͷॲཧΛద༻ͨ͠͏͑ͰΈࠐΊΔͷͰ֤ϑΟʔϧυΛ (=:) ͯ͠ʢڞ௨ͷॲཧʣ͔Β (<>) ͰΈࠐΊΑ͍ͷͰ͢ɻ buildRequestParams ͷ࣮ -- Θ͔Γ͢͞ͷͨΊʹʹهड़͓ͯ͠Γ·͢ buildRequestParams’ :: ( Forall (KeyTargetAre KnownSymbol ToHttpApiData) xs -- தͰ (=:) Λ͏ͨΊͷ݅ , QueryParam param, Monoid param ) => OptionalParams xs -> param buildRequestParams’ params = hfoldMapWithIndexWith @ (KeyTargetAre KnownSymbol ToHttpApiData) -- TypeApplication ͱ͍͏ͷΛͬͯ·͢ (\key value -> case unwrap value of Nothing -> mempty -- mempty Monoid ͷ୯Ґݩʢa <> mempty = aʣ Just v -> stringKeyOf key =: v ) params 61 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮:
API ͷઃܭΛܕͰ͢Δ ͔ͳΓஸೡʹ͍ॻ͍ͨͷͰجຊతʹงғؾͰಡΊΔͱࢥ͍·͢ɻಠಛͳͷ TypeApplication ͱ͍͏ GHC ݴޠ֦ு*13ʹΑͬͯɺKeyTargetAre... ͱ͍͏ͷܕΫϥεΛద༻͍ͯ͠ΔͰ͢ɻ ͜͏͢Δ͜ͱͰɺhfoldMapWithIndexWith ʹద༻͢ΔؔͰ KeyTargetAre ͷܕΫϥεʢࠓճ KnownSymbol ͱ ToHttpApiDataʣʹґଘͨؔ͠Λݺͼग़͢͜ͱ͕Ͱ͖·͢ɻͪΖΜɺͦͷ ͨΊʹ buildRequestParams ʹద༻͢ΔϨίʔυͷͯ͢ͷϑΟʔϧυ͕ KeyTargetAre ͷ ܕΫϥεͷΠϯελϯε͡Όͳ͍ͱ͍͚·ͤΜɻ͜Εܕݕࠪ࣌ʹΤϥʔͱͯ͠ݕग़͞Ε·͢ɻstr ingKeyOf ؔϑΟʔϧυ໊ʢ͜͜Ͱ keyʣ͔ΒจࣈྻΛऔΓग़ؔ͢Ͱɺ͜ΕΛ͏ͷʹ Kno wnSymbol ܕΫϥεͷΠϯελϯεͰ͋Δඞཁ͕͋Γ·͢ɻToHttpApiData ܕΫϥε (=:) Λ ͏ͨΊͰ͢ɻ ͯ͞ɺ͜͜Ͱ͕Ұͭ͋Γ·͢ɻConversation API ͷϦΫΤετύϥϝʔλͷதʹ types=p ublic_channel,private_channel ͷΑ͏ͳϦετΛཁٻ͢Δͷ͕͋ΔͷͰ͕͢ɺ(=:) Ϧετ ʹରԠ͍ͯ͠·ͤΜɻແཧʹϦετͷΠϯελϯεʹରԠͤ͞ΔͱӨڹ͕େ͖͍ͷͰରԠ൛ͷܕΫϥ εΛಠࣗʹఆٛ͢Δ͜ͱʹ͠·͠ΐ͏ɻ ྻʹରԠͨ͠ buildRequestParams ͷ࣮ import Data.Maybe (mapMaybe) import Data.Text (Text, intercalate) class ToHttpApiData’ a where toQueryParam’ :: a -> Maybe Text -- ϦετҎ֎ ToHttpApiData ͷ࣮Ͱ͢ʢtoQueryParam ToHttpApiData ͷؔʣ instance ToHttpApiData’ Int where toQueryParam’ = Just . toQueryParam instance ToHttpApiData’ Bool where toQueryParam’ = Just . toQueryParam instance ToHttpApiData’ Text where toQueryParam’ = Just . toQueryParam instance ToHttpApiData’ a => ToHttpApiData’ [a] where toQueryParam’ [] = Nothing toQueryParam’ xs = Just $ intercalate "," $ mapMaybe toQueryParam’ xs buildRequestParams :: ( Forall (KeyTargetAre KnownSymbol ToHttpApiData’) xs , QueryParam param, Monoid param ) => OptionalParams xs -> param buildRequestParams params = hfoldMapWithIndexWith @ (KeyTargetAre KnownSymbol ToHttpApiData’) (\key value -> case toQueryParam’ =<< unwrap value of Nothing -> mempty Just v -> stringKeyOf key =: v ) params *13 https://downloads.haskell.org/~ghc/8.6.5/docs/html/users_guide/glasgow_exts.html# extension-TypeApplications 62 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.3 ࣮:
API ͷઃܭΛܕͰ͢Δ ͜ΕͰ؆୯ʹϦΫΤετύϥϝʔλΛѻ͑ΔΑ͏ʹͳΓ·ͨ͠ɻ͋ͱɺϦΫΤετͦͷͷΛ͠ ͯ͘ΕΔؔΛ࣮͢Δ͚ͩͰ͢ɻ ϦΫΤετͷؔΛ࣮͢Δ ࣮͜Μͳײ͡Ͱ͢ɻ ϦΫΤετͷؔ type SlackApiResponse r = JsonResponse (Slack.Ok r) info :: (MonadHttp m, Client c) => c -> ChannelID -> InfoParams -> m (SlackApiResponse (Record ’[ "channel" >: Slack.Conversation ])) info client cid = buildGetApi "info" client . ("channel" =: cid <>) . buildRequestParams ͨͬͨ͜Ε͚ͩͰ͢ɻଞͱॏෳ͢ΔͰ͋Ζ͏࣮ͯ͢ buildGetApi ʹ͠·ͨ͠ɻ buildGetApi ؔͷ࣮ buildGetApi :: (MonadHttp m, Client c, FromJSON r) => Text -- ^ request path -> c -- ^ client -> Option (ClientScheme c) -- ^ request params -> m (SlackApiResponse r) buildGetApi path c params = req GET (buildUrl c path) NoReqBody jsonResponse (mkHeader c <> params) buildUrl :: Client c => c -> Text -> Url (ClientScheme c) buildUrl c path = baseUrl c /: "conversations." <> path buildGetApi GET ϦΫΤετݶఆͰ͢ɻConversations API ʹ GET ͱ POST ͕͋Δͷ ͰɺPOST ൛࡞Δඞཁ͕͋Γ·͕͢ɺ΄ͱΜͲಉ͡ͳͷͰׂѪ͠·͢ɻ࣮ࡍʹ GHCi Ͱࢼ͢લʹɺ ࢼ͢ͱ͖ʹศརͳؔΛ༻ҙ͓͖ͯ͠·͠ΐ͏ɻ ϦΫΤετΛ࣮ߦ͢Δศརͳؔ module Web.Slack.Conversations where run :: (MonadIO m, FromJSON r) => Req (SlackApiResponse r) -> m (Either Text r) run request = runReq defaultHttpConfig $ do 63 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.4 ςετΛهड़͢Δ
result <- request pure $ case responseBody result of Ok r -> Right r Err e -> Left e ͜ΕΛ͏͜ͱͰ run (info ...) Ͱ؆୯ʹ࣮ߦͰ͖·͢ɻ gchi> import Lens.Micro gchi> import Data.Extensible ghci> import Web.Slack.Conversations as Conversations ghci> client = newClient "XXX" -- Slack API Token ghci> Right ch <- run $ Conversations.info client "YYY" vacancy ghci> ch ^. #channel ^. #name Just "general" 5.4 ςετΛهड़͢Δ ͯ͞ɺ͔͜͜Β͓·͚Ͱ͢ɻࠓճΑ͏ʹެ։͞Εͨ API ͷΫϥΠΞϯτΛςετ͢Δͱ͖ʹͲ ͏͠·͔͢ʁ Ұͭͷํ๏ͱͯ͠ɺ؆қతͳϞοΫαʔόΛ༻ҙͯ͠ςετ͢Δͱ͍͏ͷ͕͋Δͱࢥ ͍·͢ɻHaskell ͷ߹ɺ಄Ͱड़ͨ servent Λ͏͜ͱͰ؆୯ʹϞοΫαʔόΛ༻ҙͰ͖·͢ɻ ·ͣϞοΫαʔόΛ༻ҙ͢Δͱ͜Ζ͔Β࢝Ί·͠ΐ͏ɻ ϞοΫαʔόΛ༻ҙ͢Δ લड़ͨ͠ͱ͓Γɺservent ͰܕʹΑͬͯ API ͷϧʔςΟϯάΛهड़͠·͢ɻ ϞοΫαʔόͷ API ܕ module Web.Slack.Conversations.Test.MockServer where import qualified Web.Slack.Conversations as Slack type ConversationResult = Record ’[ "channel" >: Slack.Conversation ] type API = "conversations.archive" :> Post ’[JSON] (Slack.Ok (Record ’[ "ok" >: Bool ])) :<|> "conversations.close" :> Post ’[JSON] (Slack.Ok Slack.CloseResule) :<|> "conversations.create" :> Post ’[JSON] (Slack.Ok ConversationResult) :<|> "conversations.history" :> Get ’[JSON] (Slack.Ok Slack.Messages) :<|> "conversations.info" :> Get ’[JSON] (Slack.Ok ConversationResult) ... -- ׂѪ ͯ͞ɺ࣍αʔόଆͷ࣮Ͱ͢ɻ͠ΐͤΜ؆қతͳϞοΫαʔόͰ͢ͷͰɺͲΜͳϦΫΤετύ 64 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.4 ςετΛهड़͢Δ
ϥϝʔλ͕དྷͯυΩϡϝϯτͷαϯϓϧʹ͋Δ JSON Λฦ͢Α͏ʹ͓͖ͯ͠·͠ΐ͏ɻ ϞοΫαʔόͷ࣮ server :: Server API server = returnJsonFile "test/fixture/ok.json" :<|> returnJsonFile "test/fixture/ok.json" :<|> returnJsonFile "test/fixture/conversation.json" :<|> returnJsonFile "test/fixture/messages.json" :<|> returnJsonFile "test/fixture/conversation.json" ... -- ׂѪ where returnJsonFile path = do file <- liftIO (J.eitherDecodeFileStrict path) case file of Right json -> pure json Left err -> throwError $ err500 { errBody = fromString err } returnJsonFile Ҿͷύεͷ JSON ϑΝΠϧΛಡΈࠐΜͰɺࢦఆ͞Εͨܕʹσίʔυͯ͠ฦ ͍ͯ͠ΔؔͰ͢ɻϑΝΠϧ͕ͳ͍ɺ͋Δ͍ࢦఆͨ͠ܕʹͪΌΜͱσίʔυͰ͖ͳ͍߹ 500 ͷϦβϧτίʔυΛฦ͍ͯ͠·͢ɻ͜͏͢Δ͜ͱͰɺαϯϓϧίʔυͱఆٛͨ͠ܕͷରԠ͕ͪΌΜͱ ͱΕ͍ͯΔ͔͔֬ΊΔ͜ͱ͕Ͱ͖·͢Ͷɻ͋ͱɺςετͱҰॹʹϞοΫαʔόΛόοΫΤϯυͰ ىಈ͢ΔؔΛ༻ҙ͓͖ͯ͠·͢ɻ ϞοΫαʔόΛόοΫΤϯυͰىಈ͢Δؔ import Network.Wai.Handler.Warp (run) mockServer :: IO () mockServer = run 8000 (serve (Proxy @ API) server) runMockServer :: IO () -> IO () runMockServer action = do _ <- forkIO mockServer action αʔόଆΛ༻ҙͰ͖ͨͷͰɺΓϞοΫαʔό༻ͷ Client ܕΫϥεͷΠϯελϯεΛ༻ҙ͢Ε ४උྃͰ͢ɻ ϞοΫαʔόͷ Client ܕΫϥεͷΠϯελϯε module Web.Slack.Conversations.Test.Client where data TestClient = TestClient instance Client TestClient where type ClientScheme TestClient = ’Http 65 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.4 ςετΛهड़͢Δ
baseUrl _ = http "localhost" mkHeader _ = mconcat [ port 8000 ] hspec ʹΑΔςετͷهड़ ςετͷهड़ʹ Haskell ൛ RSpec Ͱ͋Δ hspec*14ͱɺΑΓϦονʹςετΛग़ྗ͢Δ tasty*15 ͱ͍͏ύοέʔδΛར༻͠·͢ɻͻͳܕ͜Μͳײ͡Ͱ͢ɻ ςετίʔυͷͻͳܕ import qualified Spec.Web.Slack.Conversations.API import Test.Tasty (TestTree, testGroup, defaultMain) import Web.Slack.Conversations.Test.Client (TestClient (..)) import Web.Slack.Conversations.Test.MockServer (runMockServer) main :: IO () main = runMockServer $ defaultMain =<< spec spec :: IO TestTree spec = testGroup "Web.Slack.Conversations" <$> sequence [ Spec.Web.Slack.Conversations.API.specWith TestClient ] ͜͜ͰςετΛָʹهड़Ͱ͖ΔΑ͏ʹิॿؔΛ࣮͓͖ͯ͠·͠ΐ͏ɻલઅͰఆٛͨ͠ info ؔ ͱɺϞοΫαʔόͰར༻ͨ͠ JSON ϑΝΠϧͷύεΛ͚ͩ͢ͰൺֱͰ͖ΔΑ͏ͳؔͰ͢ɻ ςετͷهड़Λָʹ͢Δؔ module Web.Slack.Conversations.Test.Helper where import qualified Data.Aeson as J import Test.Tasty.Hspec (Expectation, expectationFailure, shouldReturn) shouldResponse :: (J.FromJSON r, Show r, Eq r) => Req (JsonResponse r) -> r -> Expectation shouldResponse request expect = do resp <- runReq defaultHttpConfig request responseBody resp ‘shouldReturn‘ expect shouldResponseAs :: (J.FromJSON r, Show r, Eq r) => Req (JsonResponse r) -> FilePath -> Expectation shouldResponseAs request path = do file <- J.eitherDecodeFileStrict path *14 https://hackage.haskell.org/package/hspec *15 https://hackage.haskell.org/package/tasty 66 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.5 ऴΘΓʹ
case file of Right json -> request ‘shouldResponse‘ json Left err -> expectationFailure err ͜ͷؔΛར༻͢Δ͜ͱͰɺςετ؆୯ʹهड़Ͱ͖·͢ɻ API ΫϥΠΞϯτؔͷςετ module Spec.Web.Slack.Conversations.API where import Test.Tasty import Test.Tasty.Hspec import Web.Slack.Conversations.API as Conversations import Web.Slack.Conversations.Client (Client) import Web.Slack.Conversations.Test.Helper (shouldResponseAs) specWith :: Client c => c -> IO TestTree specWith client = testSpec "Web.Slack.Conversations" $ do describe "info" $ it "should return conversation" $ Conversations.info client "C1234567890" vacancy ‘shouldResponseAs‘ "test/fixture/conversation.json" ࣮Ͱ͖ͨ API ͷछྨ͕૿͑ͨΒɺಉ͡Α͏ʹ describe ҎԼΛॻ͚ͩ͘Ͱ͢Ͷɻ $ stack test slack-conversations> test (suite: spec) Web.Slack.Conversations Web.Slack.Conversations info should return conversation: OK (0.05s) All 1 tests passed (0.05s) slack-conversations> Test suite spec passed 5.5 ऴΘΓʹ req ͱ extensible Λར༻ͨ͠ΫϥΠΞϯτ࡞ 2017 ͝ΖʹΞϧόΠτͳͲͰ͍ͬͯ·͠ ͨɻࠓճٱ͠ͿΓʹಉ༷ͷํ๏Ͱ࣮ΛࢼΈ͍ͯ·͢ɻ͜ͷύλʔϯҙ֎ͱ࠶ར༻Ͱ͖Δؾ͕͢ ΔͷͰɺύοέʔδͱͯ͠·ͱΊͯΈ͓ͯ͠Ζ͍͔Ͱ͢Ͷɻ·ͨɺਖ਼ Conversations API ͚ͩΛΓग़ͨ͠ΫϥΠΞϯτෆศͳͷͰؾ͕͍ͨΒͬͱଟ͘ͷ Slack API ʹରԠͨ͠ͷ 67 -
ୈ 5 ষ Haskell Ͱͷ Web API ΫϥΠΞϯτͷ࡞Γํ 5.5 ऴΘΓʹ
Λࣗ࡞͢Δ͔͠Ε·ͤΜɻ͠ɺ2020 11 ݄·ͰʹରԠͨ͠ΫϥΠΞϯτ͕ݱΕͳ͔ͬͨΒͥͻ ͷΛ͍ͬͯͩ͘͞ʢextensible Λ͏ඞཁ͕ग़͖ͯͯ͠·͍·͕͢ʣ ɻ 68 -
ஶऀհ ా ګฏ (ୈ 1 ষ୲, GitHub,Medium,Qiita: flatfisher, Twitter: @canoefishing)
ϥΠϒಈըϓϥοτϑΥʔϜࣄۀ෦ͷαʔόʔαΠυΤϯδχΞɺGCP ͱ Go ݴޠΛ༻͠ ͍ͯΔɻGDG ͱݺΕΔٕज़ίϛϡχςΟͷΦʔΨφΠβʔΛ͓ͯ͠ΓɺίϛϡχςΟք۾ ͰϑΟογϡͱݺΕ͍ͯΔɻΓ͕झຯͳͷͰʮΓʷςΫϊϩδʔʯͱ͍ͬͨςʔϚʹ औΓΉͷ͕͖ɻ ɹ ϠϚϠλέγ (ୈ 2 ষ୲, GitHub: toymany, Twitter: @toyman_jp) ۙεϚϗ༻ͷήʔϜ։ൃϓϩδΣΫτʹࢀՃ͍ͯ͠·͢ɻझຯͰΞφϩάΧʔυήʔϜΛ ࡞ͬͨΓɺήʔϜΞϓϦΛ࡞ͬͨΓ͍ͯ͠·͢ɻ ɹ ଠా Ұߦ (ୈ 3 ষ୲, GitHub:IKKO-Ohta, Qiita,Twitter: @samayotta) 19 ৽ଔɻϑϩϯτΤϯυͱػցֶशͷೋͷΘΒ͡Λཤ͍͍ͯΔɻspread ͷ։ൃʹ͔͚ͨ࣌ ؒʰϖϧιφ̑ RʱҎ্ɺ ʰϑΝΠΞʔΤϜϒϨϜ ෩Ֆઇ݄ʱະຬɻ ɹ ాಹล ً (ୈ 4 ষ୲) ։ൃຊ෦ CTO ࣨॴଐɻΤϯδχΞͱͯ͠εϚϗήʔϜ։ൃͱӡӦɺͦΕ͔ΒऩӹੑήʔϜ ੑͱ UX ໘ͷΫϦΤΠςΟϒʹܞΘ͖ͬͯͨɻࠓϓϩάϥϛϯάڭҭʹ͍ͭͯͷֶशιϑ τͱΧϦΩϡϥϜΛࣗࣾͰ୯ಠͰ࡞Γɺதɾߴߍੜʹ͚ͯϓϩάϥϛϯάߨ࠲ͷߨࢣ͠ ͍ͯΔɻ ɹ দݪ ৴ (ୈ 5 ষ୲, GitHub: matsubara0507) ॴଐϞϯεταʔόνʔϜͰ Ruby Λॻ͍ͯΔɻϓϩάϥϛϯά͕͖Ͱɺීஈਪ͠ݴ ޠͷ Haskell Ͱ༡ΜͩΓɺ৽͍͠ϓϩάϥϛϯάݴޠΛษڧͨ͠Γ͍ͯ͠ΔɻHaskell-jp Elm-jp Ͱͪΐͪ͜ΐ͜׆ಈ͍ͯ͠Δɻ ɹ 69 -
ஶऀհ խ (σβΠφʔ, Twitter: @miyabt_, note: miyabt) ࠓճ͓͕͚͍͖ͨͩσβΠϯΛ୲͍͖ͤͯͨͩ͞·ͨ͠ɺ͋Γ͕ͱ͏͍͟͝·͢ʂ
ٕ ज़ॻ 5 ͱ͍͏͜ͱͰɺօ͞Μͷࣥචྗ͕ૉఢͰ͢Ͷʂ σβΠϯָ͓͠Έ͍͚ͨͩͨ Βخ͍͠Ͱ͢ʂ ɹ ਿా ֆඒ (ϓϩδΣΫτਪਐɾ੍࡞ਐߦͷॸ, GitHub: esugita, Twitter: @semiemi7) ݩΤϯδχΞͰɺϛΫγΟͰɺPerl Λ৮ͬͨΓɺΞϓϦΛ৮ͬͨΓɺ৽نࣄۀͷ PM Λ͠ ͨΓͯ͠ɺࠓɺDevRel νʔϜͰ֤छΠϕϯτΛاըɾӡӦͨ͠Γɺٕज़తͳݟͷΞτ ϓοτ׆ಈΛαϙʔτͨ͠Γ͍ͯ͠·͢ɻ ɹ تଟ ޭ࣍ (੍࡞Ξγετ, GitHub: kojikita, Twitter: @kojikita) ݩΤϯδχΞͰҎલ web ϑϩϯτଆͷαʔϏε։ൃΞυςΫͳͲΛ୲͍ͯ͠·ͨ͠ɻ ࠓ DevRel νʔϜͰษڧձΠϕϯτͷӡӦͳͲΛ͍ͯ͠·͢ɻ࠷ۙษڧձͷੜ์ૹʹྗ Λ͍Ε͍ͯ·͢ɻ 70 -
mixi tech note #03 2020 2 ݄ 29 ɹॳ൛ୈ
1 ɹൃߦ ஶɹऀ גࣜձࣾϛΫγΟ ༗ࢤ ൃߦॴ גࣜձࣾϛΫγΟ ҹॴ ޫاը ɹ mixi, Inc.