├── docs
├── img
│ ├── flat-browser.png
│ ├── unitflat1.xml
│ ├── unitflat2.xml
│ ├── unitflat5.xml
│ ├── unitflat3.xml
│ ├── unitflat1.svg
│ ├── unitflat2.svg
│ ├── unitflat3.svg
│ └── unitflat5.svg
├── ch_10.adoc
├── ch_02.adoc
├── index.adoc
├── ch_09.adoc
├── ch_01.adoc
├── ch_03.adoc
├── ch_06.adoc
├── ch_07.adoc
├── ch_08.adoc
├── ch_04.adoc
├── ch_05.adoc
└── asciidoctor-custom.css
├── README.md
├── LICENSE
└── .gitignore
/docs/img/flat-browser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kentutorialbook/30minLearningJavaScriptMonad/HEAD/docs/img/flat-browser.png
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 30minLearningJavaScriptMonad
2 | 30分でわかるJavaScriptプログラマのためのモナド入門
3 |
4 |
5 | ---
6 |
7 | 2024年バージョンの全面改定された新しい本が公開されているので移動してください
8 |
9 |
10 | この古いドキュメントのすべての内容は新しい本に統合された上で、内容が広範にアップグレードされています
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/docs/ch_10.adoc:
--------------------------------------------------------------------------------
1 |
2 | = ミュータブルな状態、IO、そしてモナド
3 | ifndef::stem[:stem: latexmath]
4 | ifndef::icons[:icons: font]
5 | ifndef::imagesdir[:imagesdir: ./img/]
6 | ifndef::source-highlighter[:source-highlighter: highlightjs]
7 | ifndef::highlightjs-theme:[:highlightjs-theme: tomorrow-night]
8 |
9 | ++++
10 |
17 | ++++
18 |
19 | これについては、一大別テーマなので、「後編」として別に書きます。長いし。
20 |
--------------------------------------------------------------------------------
/docs/ch_02.adoc:
--------------------------------------------------------------------------------
1 |
2 | = JavaScriptプログラマのためのモナド入門
3 | ifndef::stem[:stem: latexmath]
4 | ifndef::icons[:icons: font]
5 | ifndef::imagesdir[:imagesdir: ./img/]
6 | ifndef::source-highlighter[:source-highlighter: highlightjs]
7 | ifndef::highlightjs-theme:[:highlightjs-theme: tomorrow-night]
8 |
9 | ++++
10 |
17 | ++++
18 |
19 | これは、一般的なJavaScriptプログラマのためのモナド入門記事です。
20 |
21 | == 対象とする読者
22 |
23 | 関数型プログラミングをしたいJavaScriptプログラマーでモナドを理解したい人。
24 |
25 | 義務教育レベルの数学を理解していることが望ましい。
26 |
27 | モナドを知りたいと思ってWikipediaやWeb上の解説記事などを漁ってみたが、やっぱりさっぱりわからずに挫折していたところ、たまたまこの記事にたどり着いた人。
28 |
29 | 関数型プログラミングについて入門したい人は、
30 |
31 |
32 | [TIP]
33 | .当ブログの入門記事
34 | ====
35 | https://kentechdoc.blogspot.com/2018/06/day-jsvar-s-0for-var-n-1-n-b-const-s2-1.html[関数型プログラミング超入門]
36 | ====
37 |
38 |
39 |
40 | とりあえず配列とMapがわかればいいです。
41 |
42 | == 本稿のアプローチ
43 |
44 | <>をアンチパターンとして最大限留意しています。
45 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 kentutorialbook
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # Directory for instrumented libs generated by jscoverage/JSCover
15 | lib-cov
16 |
17 | # Coverage directory used by tools like istanbul
18 | coverage
19 |
20 | # nyc test coverage
21 | .nyc_output
22 |
23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
24 | .grunt
25 |
26 | # Bower dependency directory (https://bower.io/)
27 | bower_components
28 |
29 | # node-waf configuration
30 | .lock-wscript
31 |
32 | # Compiled binary addons (https://nodejs.org/api/addons.html)
33 | build/Release
34 |
35 | # Dependency directories
36 | node_modules/
37 | jspm_packages/
38 |
39 | # TypeScript v1 declaration files
40 | typings/
41 |
42 | # Optional npm cache directory
43 | .npm
44 |
45 | # Optional eslint cache
46 | .eslintcache
47 |
48 | # Optional REPL history
49 | .node_repl_history
50 |
51 | # Output of 'npm pack'
52 | *.tgz
53 |
54 | # Yarn Integrity file
55 | .yarn-integrity
56 |
57 | # dotenv environment variables file
58 | .env
59 |
60 | # next.js build output
61 | .next
62 |
--------------------------------------------------------------------------------
/docs/img/unitflat1.xml:
--------------------------------------------------------------------------------
1 | 7VjLcpswFP0aL9MBBBgvYyduFk3baRadLmUQoEYgKkSM8/W9GPEQkOZRnHQy9cKDjqQrcc65V7IXaJOUHwXO4mseELawjKBcoIuFZXlLC74r4FADtmHXQCRoUENmB9zQe6JAQ6EFDUiuDZScM0kzHfR5mhJfahgWgu/1YSFn+qoZjsgIuPExG6PfaSBj9VqO0eFXhEZxs7JpqJ4EN4MVkMc44PsehC4XaCM4l/VTUm4Iq7hreKnnbR/obTcmSCqfMsFOrr79+JzcfzmzLkp2Swzz2j5TUe4wK9QLq83KQ8PAPqaS3GTYr9p7EHmB1rFMGLRMeMR5VvMe0pLAUuuQMrbhjIvjdBRg4oU+4LkU/Jb0elzfI7sQetQeiJCkfPDlzJYysBrhCZHiAEPUBFeRrFxmLp26ve9ppobEPbkaDCuXRG3gjkh4UFw+g9fl47ySAIymmlzImEc8xeyyQ9fgmKzqDRkpzysvAyZ4kQYVzxeGrsNPIuVB5Q8uJAeoi/qJ80yNG8hjwGe7bUWoNvVcCQRhWNI7fd4Uo2rqV04hYivdaqVJh4yBJjkvhE/UpL6//xyntUQTR2IRETmKc5S3fZuXK24/QXHGoGxVyv5lTm23Hsg2lVM7z7EdY56c8vScQk1J7uWUPZFT9qlyynnvDJurcdV6VYZN9K9XqcfkeIP61Z74jYrohQVsFGj5tAoGpONDb1hWDchPUeNWp78tOMQL7CmhPWuHXHeevHN0oq1xYWu16OedOTya5ks84/TMvsU9DI2ZfdVrmPnuT2XnjQ9lc+pUdhksu97BQySPb10DIT+Wu45791fBm46z/HgqnMMAKGFl19lEKVIqm0iw1TqYvgDAvUUHOgPlUhdU1ynlKRmIqiDMaJRC0weJCODrSkAKvxHPVUdCg4A95CD9LJzBAaY1KF/LsQfcCQ8Mz6X5POA+1wNz+iKE0/sRX+zEhFP+G6i7sVgfxjfPmSwEze4/jvoi0v1RhC5/Aw==
--------------------------------------------------------------------------------
/docs/img/unitflat2.xml:
--------------------------------------------------------------------------------
1 | 5VjLcpswFP0aL9NBvIyXsRM3i6btNItOlzIIUCMQleUY5+t7MRJvj50G59WNBx1JV9I5xxddJtYiyT8LnMW3PCBsYhpBPrGuJqbpTU34LYBdCdiGXQKRoEEJoRq4o49EgYZCNzQg69ZAyTmTNGuDPk9T4ssWhoXg2/awkLP2qhmOSA+48zHroz9pIGN1LMeo8RtCo1ivjAzVk2A9WAHrGAd824Cs64m1EJzL8inJF4QV3GleynnLA73VxgRJ5SkT7OTmx6+vyeO3C/MqZ/fEQLf2hYrygNlGHVhtVu40A9uYSnKXYb9ob0HkiTWPZcKgheARr7OS95DmBJaah5SxBWdc7KdbASZe6AO+loLfk0aP63tkFUKP2gMRkuQHD4cqysBqhCdEih0MURNcNUO5zLJnn5wS2TZUU0rEDcE0hpVPoip0TSU8KDafwKxznFmIAjaGxvyZHC+XngG+G+B45Tm2Y4zDsdfh2B3i2B7g2D4Xx7Pzu9chXmAPMeuZK8t1x2HWaTOLZu4As1VmaVJbgaNzqxPwR0sNpjFE7oumBmR/9NwArL92bkBDCdhlsOx8BQ+R3B+8BEIOp2zS7/7ZcN1xsd5fSi5hgGlmed2po2xSKnUk2GoZrL0AwI1FO1ID67KtaVuqlKeko6uCMKNRCk0fVCKAzwsNKVxeLlVHQoOAHTKR4Js0KCxzNZIJkPaxfkMgNOACd8AF1tlc4D7VBWM6I2T4mDNWYsAr/7OFOq9C7ZZXM9D0hFwdQMmgmlzImEc8xey6Rudw98+K3pCR/LKoSnrcNaT7TaTcqUoIbyQHqI76hfNMjeskesjyxnJZyVBs6qkiCAKGpQ/teUOUqqnfOd3/PbR4RvcWXv3/dZA13wifqHnNYuVYKKMXSmIREdkLtVe5OtO/C1+VbW9W52Mv9DfgAOSY4+iPZuZJ6gPpeNcYlhUD1mfxh3U8MTzz6kYQlB/TIaVn7tTCIxUftt15afcSLtIX1lblocHRU655wvX4fZR1neuQ12f2ZWs684SPEu+jpmsza/eZfdGCzhy6Zb7zgq7NsNtneKRqDpr1N9AyOdcfkq3rvw==
--------------------------------------------------------------------------------
/docs/img/unitflat5.xml:
--------------------------------------------------------------------------------
1 | 7Vpdc6IwFP01PrpDCCA+qtV2OtvdTvuws/sWJQptICzGr/76DRI+ArHVFVF364MDJ+GGnHNz7020BQf++jZCoftAHUxauuasW/Cmpet2R+ffMbBJAGBqIEFmkecILAeevTcsQE2gC8/Bc6kjo5QwL5TBCQ0CPGEShqKIruRuU0rkUUM0wxXgeYJIFf3hOcwV8zK1HL/D3sxNRwaaaPFR2lkAcxc5dFWA4LAFBxGlLLny1wNMYvJSXpLnRjtasxeLcMD2ecDw755+fvPfvrf1mzV5xRp4MNqdxMoSkYWYsHhZtkkZwA4nRNzSiLl0RgNEhjna5zML49YpwetezDnHIroIHBwPrfE7l/mEXwJ++YIZ2wid0YJRDuVWv1Iain5Tj5ABJTTavgXU+GfEp9avTlswEb9nARAk3GLqYxZteIcIE8S8pSwsEv4xy/pljz5Sjw+ha8KXIRBkCVc2DCibmNNFNMHiqaIQHxnSSoYYimaYVQzxi8J8cmir8wGaG3toTghfYLG2K9dj+DlEk7hlxde4rCaah8mqm3rrWO2ybKORzYWLfYRF9BUXWsa2aZjae4IuccTw+l1JRaveLTFqCkZX+ZI1xDJ0C6s1xVROILF+KMXAVHBsET5sf8wvZmw77wSY0q1z5Oxbvxc0bWjPtyulxzvoerjOG1Mri8BjqSX+qokxeQAOFwYtKc0ZZrKkslIBDXBJVgEh4s0CfjvhumGO92O9PB45e6LB9xyH7PIhOT7U4QNQk30AVH3AUvgAPJkPWIf6QJ1+MeWx7gO/GEcKT/l/HQgasgPpHeuL2ZgLPS3RL+B277u9R3/4cj/2FqtlGyg8qET/kfHZQdieTlTx2ZrYeDzNqK3wqGB799rsyPEZAlNBra2g1j4VtfolJsHjSC4lQQhVJJ8qDSpJhqf3XxPbjqGi1tbH0LJqotaSqQWWrqA2K/2L3GZg7eTuUcZdZXAAHRW5jQYHVfX2bwUH0FWR3GhwOLg8+iyRj3UCWK5wOueucOw9Vtolnj+UDhv2UuHY8wdNVg9qmXoHn0CUTPH8VjZV3xmEurLVTp69MODFQUcVWLtWB6LaSgOZynZ11wmAoagLUrD+TUMDu4aGqi5botY4d8UF9tg0XEnJJTMLqtQ2Wm+BPTYKV1dwyRTDKsWNVlupxJebWD+S4xwpN1Uty5P2X6fckimoVUztSLmceLQpdAvjDvOTJOWDT60/TyzPWc+XTyyBIkU2e175uac7954O6Oc+s77Wn5QvYEsHzJo2dAA0vJ2zL13jK6guQBo+j60tgGntpX4dlQW/zf/CknTP/wgEh38A
--------------------------------------------------------------------------------
/docs/img/unitflat3.xml:
--------------------------------------------------------------------------------
1 | 7Vpdd5owGP41XnaHED4vq63d6Vm3nvZiZ7uLEoUtEIex6n79XiQIgbSlFazdqRce8iYk8Dzvtw7wKN5cpWQR3vCAsoFpBJsBvhiYpuea8J0JtrkA2YaVS+ZpFEhZKbiP/lIpNKR0FQV0qSwUnDMRLVThlCcJnQpFRtKUr9VlM87UUxdkThuC+ylhTen3KBChfC/bKOWfaTQPi5ORIWdiUiyWgmVIAr6uiPDlAI9SzkV+FW9GlGXgFbjk940fmd0/WEoT0eYGK/589+Nr/PfbmXmxYb+pgW6sM7nLA2Er+cLyYcW2QGAdRoLeL8g0G6+B5QEehiJmMEJwSZaLHPdZtKFw1HAWMTbijKe723FAqDebgnwpUv6bVmacqUcnM5hpvknxWDQVdFMRyTe7ojymIt3CEjnrSJALNXPtfLyucCaXhBW6ChmRWjLfb1wCCRcSyxfg6j6PKw1A0eSQpyLkc54QdllKh6Axi2x2xujmPNNlkKV8lQQZzheGysMvKsRW2g9ZCQ6ictcvnC/kuho9BnzG46dIyJ7zSQpSyoiIHlSD0SEqb73lERyxp873FeqwUeNkyVfplMqbqvr99D57lSj2ESSdU9HYZ0fv/m1ez7jVgnHGwG1lzB5oU+OxB7TpbGri2ZZtdGNTnmpTuHDJFZuyNDZl9WVT9v+OMPKbXuuoCCN86l7qFXT07b/2Eb9gEb/SgTU2ctt5MACdbCvLFtmCZR8+zu8/W7CpF1g6oj1zgh2nG7uzVaDNpmPbc1G1O1QPTd0ZntE/sm+Rh+EmskdNw9B/H5XtNw7KSBeVHQbHDidwMRe7t84FM75zdyX2zp8VLybOlruocA4LwIVtyslil1USiWIneNR8M/UAEFcOrfEM+AqVUJWnhCe0RqoUERbNExhOgTUK8mHGVgQ14rmciKMgYI9pkBoLO9AAZNbcl9vUAUejA/W41J0OOC/VgS71YgbR+xm9mKQaTflQoDJjMT81M8++VOjugfxEoX/tn9/Gl7+uJ9Fq/XBKfYgGjhq0H4XWdF01ACJbA21fMVALrXmKIfAwkP0ayFgHcl9hUAsy7l9/22bGh0HrqNAiR+ca+suOteC2SOLepXNArg7cozqHk+ypdOockK8D+ajO4cXp0UeKfKASYOwpSgCW99YZju4XgY8c+YQ1qOZGTOdt9cdr4alPq1ebP1+t/dqKgwM7shjV8kNjb/0v7cnWt4L8qL5Vd78raXn3T53m56L5CSgAQl439CPba0V+Ly15feHcf+eYIqg9XB3TvuNi0lnloTYlipKqWnYU7Vyl7CiE3fckjtCUOFJRp2ZDVhPZ4xZ0qEVP4p1UdCqyqAntUcs51KIP8e7qORVi3IS4o2IOhuW/sXL/XP6nDV/+Aw==
--------------------------------------------------------------------------------
/docs/index.adoc:
--------------------------------------------------------------------------------
1 | = 30分でわかるJavaScriptプログラマのためのモナド入門
2 | 岡部 健 / Ken Okabe
3 | :keywords: monad, JavaScript, ECMAScript, Promise, FRP, timeline
4 | :description: timeline FRP Monad
5 | :homepage: https://kentutorialbook.github.io/30minLearningJavaScriptMonad/
6 | :toc: left
7 | :sectnums:
8 | :toclevels: 2
9 | :imagesdir: https://kentutorialbook.github.io/30minLearningJavaScriptMonad/img/
10 | :stem: latexmath
11 | :icons: font
12 | :source-highlighter: highlightjs
13 | :highlightjs-theme: tomorrow-night
14 |
15 | ++++
16 |
25 | ++++
26 |
27 | この記事は
28 |
29 | - https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%8A%E3%83%89_(%E5%9C%8F%E8%AB%96)[モナド (圏論)]
30 | - https://ja.wikipedia.org/wiki/%E3%83%A2%E3%83%8A%E3%83%89_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)[モナド(プログラミング)]
31 | - https://ncatlab.org/nlab/show/monad[monad]
32 | - https://ncatlab.org/nlab/show/monad+%28in+computer+science%29[monad (in computer science)]
33 |
34 | を解説しています。
35 |
36 | 「30分でわかる」のは、だいたい、 +
37 | **4. モナド(Monad)とは何か?** +
38 | の読了までを想定しています。 +
39 | また速い人なら、30分で全部一気に読み通せる分量でもあると思います。 +
40 | 30分以上かかっても一気読みしてしまうことが推奨されますし、一気読みできるように、前に戻って知識の再確認をしなくて済むように、最大限留意して構成を設計した上で執筆されています。
41 |
42 |
43 | :leveloffset: 1
44 |
45 | include::./ch_01.adoc[]
46 |
47 | include::./ch_02.adoc[]
48 |
49 | include::./ch_03.adoc[]
50 |
51 | include::./ch_04.adoc[]
52 |
53 | include::./ch_05.adoc[]
54 |
55 | include::./ch_06.adoc[]
56 |
57 | include::./ch_07.adoc[]
58 |
59 | include::./ch_08.adoc[]
60 |
61 | include::./ch_09.adoc[]
62 |
63 | include::./ch_10.adoc[]
64 |
65 |
66 |
67 | ++++
68 |
69 |
70 |
77 | ++++
--------------------------------------------------------------------------------
/docs/ch_09.adoc:
--------------------------------------------------------------------------------
1 |
2 | = Promise (ES6+) はモナドか?
3 | ifndef::stem[:stem: latexmath]
4 | ifndef::icons[:icons: font]
5 | ifndef::imagesdir[:imagesdir: ./img/]
6 | ifndef::source-highlighter[:source-highlighter: highlightjs]
7 | ifndef::highlightjs-theme:[:highlightjs-theme: tomorrow-night]
8 |
9 | ++++
10 |
17 | ++++
18 |
19 | ES6+ Promiseがモナドだ、としばしば言われますが、モナドではありません。
20 |
21 | Promiseは事前想定の多すぎるAPIで使いにくいと思うのですが、事前想定が多い1つの弊害として、構造が限定されてしまっている、ということがあります。
22 |
23 | Promiseで `unit` に相当するのは、`Promise.of` ですが、link:https://stackoverflow.com/questions/45210122/why-cant-promise-resolve-be-called-as-a-function/45210249[そのまま置き換えることは出来ない]ようなので、 `Promise.resolve.bind(Promise)` として、
24 |
25 |
26 |
27 | [source,js]
28 | .Promise 構造テスト
29 | ----
30 | const unit = Promise.resolve.bind(Promise);
31 |
32 | const p = unit(3); <1>
33 | const pp = unit(p); <2>
34 | const ppp = unit(pp) <3>
35 |
36 | console.log(p);
37 | console.log(pp);
38 | console.log(ppp);
39 | ----
40 |
41 | <1> ただのPromiseオブジェクト
42 | <2> 二重にネストしたPromiseオブジェクト?
43 | <3> 三重に・・・
44 |
45 |
46 | [source,js]
47 | .Console
48 | ----
49 | Promise { 3 }
50 | Promise { 3 }
51 | Promise { 3 }
52 | ----
53 |
54 | おっと、全部同じ値になってしまいました。
55 |
56 | Promiseは、ネストしたPromiseオブジェクトを許容しておらず、ぜんぶ平坦化してしまうようです。
57 |
58 | つまり、Promiseは、これ
59 |
60 | image::./unitflat1.svg[align="center"]
61 |
62 | をやっているわけですが、問題は、endofunctor の `map` に相当する `then` をする **前** に、問答無用で `flat` かましてるんですね。
63 |
64 | 結果、裸の値 or Promise1階層の二択しかありません。Promiseでネストした構造をもつことは出来ません。
65 |
66 | 本稿で何度も例に出てくる数字や文字列は、モノイドでありモナドですが、これらもご存知の通り、内部構造を持てません。
67 |
68 | [source,js]
69 | .Number String 構造テスト
70 | ----
71 | {
72 | const unit = Number;
73 |
74 | const n = unit(1);
75 | const nn = unit(n);
76 |
77 | console.log(n);
78 | console.log(nn);
79 | }
80 | {
81 | const unit = String;
82 |
83 | const s = unit("Hello");
84 | const ss = unit(s);
85 |
86 | console.log(s);
87 | console.log(ss);
88 | }
89 | ----
90 |
91 | [source,js]
92 | .Console
93 | ----
94 | 1
95 | 1
96 | Hello
97 | Hello
98 | ----
99 |
100 | しかし、数字と文字列がPromiseと違うのは、構造が自分自身の1種類だけなんですね。Promiseは、別のタイプの裸の値かPromiseの値か2種類。
101 |
102 | ネストした数字や文字列っていうのは、(ここで扱っているものに限れば)はなっから存在せず、構造持たない1種類で整合性が取れてますが、Promiseはそうではないので整合性が取れません。
103 |
104 | モナドというのは、このように、
105 |
106 | image::./unitflat5.svg[align="center"]
107 |
108 | 自由に階層と構造を指定できる(それか数や文字列のようにそもそも階層が存在しない)ので、ここであえてモナド則を持ち出すまでもなく、Promiseがモナドではない、というのは明らかです。
109 |
110 | ネストできるだろうと想定してPromise自身を受け渡す `Promise.then` メソッドを無理やり組むと、それ以前に即座に `flat` かまされているわけですから、タイプエラーが出るでしょう。モナド則(モノイド則)で言うと、Promiseオブジェクトをモナド値としたときの結合法則が成り立ちません。
111 |
112 | Promiseがネスト構造の操作を許容しない、ということで得られるメリットとは?ちょっと思いつかないですね。
113 |
114 | == まとめ
115 |
116 | 自由に内部構造(ネスト構造)を持てないオブジェクトはモナドではない。ただし、数字、文字列などは最初から自身でネストしてるような構造(JavaScriptのPrimitive)は除く。それ以外にもさらに何か特別な例外がある可能性までは厳密に検証はしていないが、少なくともPromiseがモナドでないことは明らか。
--------------------------------------------------------------------------------
/docs/ch_01.adoc:
--------------------------------------------------------------------------------
1 | = モナドが難しい?
2 | ifndef::stem[:stem: latexmath]
3 | ifndef::icons[:icons: font]
4 | ifndef::imagesdir[:imagesdir: ./img/]
5 | ifndef::source-highlighter[:source-highlighter: highlightjs]
6 | ifndef::highlightjs-theme:[:highlightjs-theme: tomorrow-night]
7 |
8 | ++++
9 |
16 | ++++
17 |
18 | 巷の解説が混乱に満ちあふれている・・・
19 |
20 | [[whysohard]]
21 | == モナドを理解するのが難しい理由
22 |
23 | - **数学と用語問題。**モナドの理論的基盤として圏論があるのは事実。理論的基盤がしっかりしているのはプログラミングという数学的作業において歓迎すべきことではある一方で、他方そのため一般的なプログラマにとってはまず用語に馴染みがない。歴史的に、圏論ベースのモナドを理論から関数型プログラミングに応用されていく過程では、実際、先駆者の間でさえ紆余曲折があったのだが、学習者へは馴染みのない用語を伴って、いきなり高度な数学的概念全開で天下り的に提示されてしまうことが多い。わかっている人、そもそも実用性以上に数学性、理論的側面に興味がある人にとっては知的好奇心を掻き立てられるトピックではあるが、そうでないプログラマにとっては「難しい、とっつきにくい、学習コストが大きすぎて実用性もよくわからない」となることが多い。「わからないの?ならとりあえず、巷の半端な解説より
24 | http://homepages.inf.ed.ac.uk/wadler/topics/monads.html[Philip Wadler先生の数々の素晴らしい論文]を読んだほうがいい!」という人もいるが、ほとんどの学習者にとって、そういうアドバイスをされる時点で、このアプローチは絶望的である。そもそも彼らには初学者に向けて噛み砕いて教えるつもりはない。そして、実はモナドを理解するために高度な数学の素養は不要。小中高で習った数学レベルで十分だ。
25 |
26 | - **逆に過度に理論面を放棄した解説を読んだ結果、余計にわけわからなくなった問題。** モナド解説に限らず科学分野の一般読者向け解説記事でアルアル。比喩、例示という極めて高度な芸術的作業が不十分なため、一瞬わかったような錯覚にさせられはするが、実際はなにもわかっておらず、その後長期間に渡り理解の不整合に苦しむ羽目になる、という不幸なパターン。読者、特にプログラマは馬鹿ではないので、そういう読者の知性を愚弄する真似は努めて避けるべき。小中高レベルの数学で十分ならばちゃんと説明すればいいだけのこと。それができないというのは、説明者自身が理解していない証拠。
27 |
28 |
29 | - **Haskellに寄りすぎ問題。**歴史的に、圏論のモナドが関数型プログラミングへ応用できることが発見され、論文が発表された際に、使用された言語はHaskellであり、関数型言語としてのHaskellの根源的なフレームワークとして積極的にモナドが導入された。そのためHaskellerにとってはモナドの理解は必須要項であり、情報交換もHaskellのSyntaxをもって活発に行われている。彼らの知識の源泉は主にlink:https://wiki.haskell.org/Monad[Monad - Haskell Wiki]であったり、link:https://ja.wikibooks.org/wiki/Haskell/%E5%9C%8F%E8%AB%96#%E3%83%A2%E3%83%8A%E3%83%89[Haskell/圏論#モナド]であったり、link:http://learnyouahaskell.com/chapters[Learn You a Haskell for Great Good!](無料公開中)(有料日本語訳link:https://amzn.to/2TIeoRe[『すごいHaskellたのしく学ぼう!』]
30 | )であったりして、ほとんどの場合そのHaskellで一般的な用語、Syntaxで語られる。Haskellerにとっては「モナドとはすでに手元にあるもの」であり、手元あるいは、足場となる言語を活用するための学習モティベーションも極めて高い。裏を返すと、Haskellerでないその他大勢のプログラマにとっては以上の事実は逆風となる。
31 |
32 |
33 | - **複数の新規概念ごっちゃまぜ問題。**モナドが関数型プログラミングに応用される際、学習者にとっては。複数にわたる本来興味深いはずの新規概念があるのだが、それらはほとんどの場合整理されて説明されることはない。たとえば上記のHaskellに寄りすぎ問題により、Haskellの基本的文法とからめて天下り的に `do` とか `IO` だ、などとしょっぱなから当たり前のように言われるのだが、これらはモナドを遅延評価、イベント、非同期プログラミング、IO/状態(State)、FRPの概念と合わせて**応用する話**であり、モナドの概念導入段階では本来すべき話ではない。事実モナドの関数型プログラミングへの応用黎明期では、モナドによって入出力(IO)が扱える、とPhilip Wadler先生たちから提案されたのはちょっと後になってからだ。聡明な専門家の間でさえそんな感じだったのだから、IO、それから状態管理への展開はこれはこれでひとつの一大発明であって、モナドの応用シーンとして、面白い別トピックとしてわけて考えたほうがいい。しかし、「モナドが一体何に役立つのか?」という強い要請のために「ほらHaskellではIOやdoで使われてるよ」と言いたい事情もわかる。これは**Haskellに寄りすぎ問題**の弊害でもある。
34 |
35 | これはFRPの先駆者であるConal Elliott先生もStackOverflowのlink:https://stackoverflow.com/questions/16439025/what-is-so-special-about-monads[モナドの何がそんなに特別なのか?]へのlink:https://stackoverflow.com/a/16444789[回答として、似たようなことを主張している]。
36 |
37 | > (Haskellの)Monadタイプクラスへの不釣り合いなまでの大注目度合いは歴史的な幸運にすぎない。彼らはよくIOとモナドを関連づけるが、この2つは独立した有用な概念だ。(関数型プログラミングでの)IOはマジカルで、モナドはそのIOとしょっちゅう関連づけられているので、モナドがマジカルだという錯覚に陥りやすい。
38 |
--------------------------------------------------------------------------------
/docs/img/unitflat1.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/ch_03.adoc:
--------------------------------------------------------------------------------
1 |
2 | = なぜモナドか?
3 | ifndef::stem[:stem: latexmath]
4 | ifndef::icons[:icons: font]
5 | ifndef::imagesdir[:imagesdir: ./img/]
6 | ifndef::source-highlighter[:source-highlighter: highlightjs]
7 | ifndef::highlightjs-theme:[:highlightjs-theme: tomorrow-night]
8 |
9 | ++++
10 |
17 | ++++
18 |
19 | JavaScript上でかなり実用的だから。
20 |
21 | 上述のとおり、モナドとは関数型プログラミングの一部です。
22 |
23 | 関数型プログラミングは、プログラミングの複雑性を、以下の2つ
24 |
25 | - 値
26 | - 値でもある関数
27 |
28 | の組み合わせ(function composition)で制御します。
29 | <> 以降で詳しく解説します。
30 |
31 | == jQuery
32 |
33 | image:https://upload.wikimedia.org/wikipedia/commons/thumb/d/d3/Logo_jQuery.svg/512px-Logo_jQuery.svg.png[]
34 |
35 | いろんな値&関数が考えられるわけですが、JavaScript世界で超有名なのが、 https://jquery.com/[jQuery]でしょう。jQueryのオフィシャルロゴには _"write less, do more"_ とあり、それまで不十分なAPIにより煩雑だったDOM操作を簡潔な記法で柔軟に操作できる値&関数を提供し、その実用性の高さから人気を集めました。
36 |
37 | _"write less, do more"_ とは、複雑なプログラムをなるだけシンプルに取り扱おうとする関数型プログラミングの唯一にして究極のゴールの具現化そのものであり、一例をあげてみると、
38 |
39 |
40 | [source,js]
41 | .tryjquery_chaining2
42 | ----
43 | $("#p1")
44 | .css("color", "red")
45 | .slideUp(2000)
46 | .slideDown(2000);
47 | ----
48 |
49 | と、link:https://www.w3schools.com/jquery/jquery_chaining.asp[メソッドチェーンをもって書き連ねる]だけで、link:https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_chaining2[Demo:こんなこと]ができるようになるとか、当時JavaScriptコミュニティに衝撃を与えました。要するに、この _"write less, do more"_ こそが、関数型プログラミングの真価であり、jQueryはただひとつの、 `$()` というjQueryオブジェクト生成関数と、それにぶら下がる巨大なメソッド群から成立していて使い方自体はシンプルです。
50 |
51 | jQueryは値(オブジェクト)&関数(オブジェクトにぶらさがるメソッド群)のペアです。
52 |
53 | jQueryがモナドかどうか?というのはしばしば議論にあがるところですが、jQueryのAPIは巨大なので、その全部がモナドであるわけではないが、そのうちの一部はモナドになっている、というのが答えでしょう。
54 |
55 | **jQueryの一部の特性としてモナドの性質を備えている理由はメソッドチェーンを壊さないため**です。
56 |
57 | 全部がモナドではないが一部は確実にモナドである、という別の事例として、最近のJavaScriptのlink:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array[Array]があげられます。これについては次の章で。
58 |
59 | == MonadicReact
60 |
61 | jQueryは標準DOMのAPIがかなりマシになってきたこととでパフォーマンスの観点からも、jQuery非依存で書こうというトレンドが見られます。さらに仮想DOMのコンポーネント機構をもつReactが登場したことにより、世代交代が起こった感もあります。
62 |
63 | Reactをより関数型プログラミングで、という目的でいろんなライブラリがありますが、
64 |
65 | https://github.com/hoppinger/MonadicReact
66 |
67 | https://www.npmjs.com/package/monadic_react
68 |
69 | みたいなReactのモナドラッパーがあります。
70 |
71 | Ph.Dを持つ作者が、Medium記事:link:https://medium.com/@giuseppemaggiore/type-safe-monads-and-react-499b4a2f56d7[Type-safe monads and React]で
72 | __Yet another introduction to monads__とモナドの紹介をしながら「便利でパワフルだ」みたいなことをエンドユーザに向けて書いてますが、とりあえず何が書かれているのかさっぱり理解できない!という人は、でもやっぱり理解したい、となるでしょう。
73 |
74 | == Promise
75 |
76 | ES6+ 以降で導入された https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Using_promises[Promise]も一部モナドっぽいふるまいをします。モナドだと言う人もいますが、モナドではありません。PromiseはjQueryほど巨大なAPIではないので、すべて厳密にモナドであったほうが有用性はあがるはずですが、そうではないので残念です。
77 |
78 | Promiseはすでに、ESModule
79 |
80 | [NOTE]
81 | .参考記事
82 | ====
83 | https://kentechdoc.blogspot.com/2018/09/20189javascriptes-moduleesm.html[2018年9月時点のJavaScriptモジュール(ES Module/ESM)界隈の最新情報、これまでの経緯とこれからの見通しを解説]
84 |
85 | https://kentechdoc.blogspot.com/2018/09/es-modulesesm-jdalton.html[明日のES Modulesを今日使おう!(esm ライブラリ開発者 @jdalton による解説記事の翻訳)]
86 | ====
87 |
88 | の動的Importの返り値として標準化されるなど、今どきのJavaScriptプログラマにとっては必須事項となってしまいました。Promiseが「モナドっぽい」振る舞いをするが、そうでない振る舞いするときもある、と挙動を把握しておくこと、人に説明できるほど理解しておくことは、Promiseを正しく使いこなすためにも重要だと思います。
89 |
90 | == Fluture
91 |
92 | image::https://raw.githubusercontent.com/fluture-js/Fluture/HEAD/logo.png[]
93 |
94 | https://www.npmjs.com/package/fluture
95 |
96 | FantasyLand compliant (monadic) alternative to Promises
97 |
98 |
99 | > Much like Promises, Futures represent the value arising from the success or failure of an asynchronous operation (I/O). Though unlike Promises, Futures are lazy and adhere to the monadic interface.
100 |
101 | Promises(ES6+ Promise含む)のオルタナティブ。
102 |
103 | npmのデータでは、それなりのパッケージから依存され、それなりのダウンロード数も誇るようです。
104 |
105 | Promisesと違ってモナドインターフェイス(monadic interface)になっているよ、と書かれています。
106 |
107 | 何が違うのか、どんなのメリットがあるのか?そもそもモナド理解してないと意味不明ですよね?
108 |
109 | == まとめ
110 |
111 | 今どきのJavaScriptプログラマならば、モナドくらいは知っておいたほうが良さそうだ。
112 |
113 |
--------------------------------------------------------------------------------
/docs/img/unitflat2.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/ch_06.adoc:
--------------------------------------------------------------------------------
1 |
2 | [[relation]]
3 | = 代数学と関数型プログラミングとオブジェクト指向の用語・記法の相互関係
4 | ifndef::stem[:stem: latexmath]
5 | ifndef::icons[:icons: font]
6 | ifndef::imagesdir[:imagesdir: ./img/]
7 | ifndef::source-highlighter[:source-highlighter: highlightjs]
8 | ifndef::highlightjs-theme:[:highlightjs-theme: tomorrow-night]
9 |
10 | ++++
11 |
18 | ++++
19 |
20 | この章では、今まで棚上げしてきた、モヤモヤしていたものをスッキリさせることを目指します。
21 |
22 | [QUOTE]
23 | ====
24 | 関数型プログラミングは、プログラミングの複雑性を、以下の2つ
25 |
26 | - 値
27 | - 値でもある関数
28 |
29 | の組み合わせ(function composition)で制御します。
30 |
31 | ---
32 |
33 | メソッドチェーンをもって書き連ねるだけで、Demo:こんなことができるようになる
34 |
35 | ---
36 |
37 | jQueryは値(オブジェクト)&関数(オブジェクトにぶらさがるメソッド群)のペアです。
38 | ====
39 |
40 | なとど書いていますが、これは若干問題があります。多数の意味が曖昧な言葉、定義がはっきりしない、その正体についてはJavaScriptや関数型プログラミングとオブジェクト指向プログラミングで出て来がちな複数の文脈で暗黙の了解に委ねられている用語が散見されます。
41 |
42 | - 値
43 | - 関数
44 | - オブジェクト
45 | - メソッド
46 |
47 | オブジェクト・メソッドについては明らかに出自がオブジェクト指向の用語です。
48 |
49 | 値、関数については、関数型ぽいが、同時にオブジェクト指向でも使われたりする。
50 |
51 | JavaScriptは良かれ悪しかれ「マルチパラダイムプログラミング」言語なので、こういうわけのわからない状況に至ってもまあ仕方はないですが、特に関数型プログラミングを導入する際に曖昧さと混乱を引きずったままでゴリ押ししてしまうことが多いです。
52 |
53 | 用語は違うのに、数学的対象としては同じものを指し示していたりすることで、概念の重複、冗長性、曖昧さが生じてしまっています。
54 |
55 | == 二項演算とは 小学1/2年の算数からの復習
56 |
57 | そこでとりあえず、根底となるプロトコルである数学の記法についてまず整理しておきましょう。
58 |
59 | 数学と言ってもたいしたことはない、小学1/2年の算数レベルのお話です。
60 |
61 | [stem]
62 | ++++
63 | 1 + 2 = 3
64 | ++++
65 |
66 | [stem]
67 | ++++
68 | 1 \times 2 = 2
69 | ++++
70 |
71 | これは初等数学で真っ先に習う
72 | https://ja.wikipedia.org/wiki/%E7%AE%97%E8%A1%93#%E5%9B%9B%E5%89%87%E6%BC%94%E7%AE%97[四則演算]のうち「加算」と「乗算」です。
73 |
74 | またさらに一般化、抽象化して、「2つの数から新たな数を決定する演算」のことを https://ja.wikipedia.org/wiki/%E4%BA%8C%E9%A0%85%E6%BC%94%E7%AE%97[二項演算]と呼びます。要するに**演算のパラメータが2つあったらそれは二項演算**。また、2つのパラメータの中間に `+` 、`-` などの演算子を置くのを https://ja.wikipedia.org/wiki/%E4%B8%AD%E7%BD%AE%E8%A8%98%E6%B3%95[中置記法]と呼びます。
75 |
76 | パラメータが1個ならば、 https://ja.wikipedia.org/wiki/%E5%8D%98%E9%A0%85%E6%BC%94%E7%AE%97[単項演算] で、中学で習う平方根の演算子、ルートを使って
77 |
78 | [stem]
79 | ++++
80 | \sqrt9 = 3
81 | ++++
82 |
83 | となりますね。
84 |
85 | == 演算は関数として捉えられる
86 |
87 | パラメータの文字が出た時点でお察しですが、以上の演算は関数として解釈できます。
88 |
89 | 単項演算は、パラメータが1個なので、
90 |
91 | [source,js]
92 | .Math.sqrt
93 | ----
94 | Math.sqrt(9) //3
95 | ----
96 |
97 | 二項演算は、パラメータが2個なので、
98 |
99 | [source,js]
100 | .plus
101 | ----
102 | const plus = (a) => (b) => (a + b);
103 |
104 | plus(1)(2) //3
105 | ----
106 |
107 | [[withobjectmethod]]
108 | == オブジェクト指向のメソッドでは
109 |
110 | ここであえて復習するつもりもありませんが、オブジェクト指向のメソッドとは、オブジェクトに紐付いた関数のことですね。
111 |
112 | JavaScriptの数値はカッコ()で囲んでやると、 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Number[Numberオブジェクト]になるので、`Number.prototype` へ新たに `plus` メソッドを追加します。
113 |
114 |
115 | [[NumberPlus]]
116 | [source,js]
117 | .Number(a).plus(b)
118 | ----
119 | Object.defineProperty(
120 | Number.prototype,
121 | "plus", {
122 | value: function (b) {
123 | return this + b;
124 | }
125 | });
126 | ----
127 |
128 |
129 | [source,js]
130 | .(1).plus(2) === 3
131 | ----
132 | (1).plus(2) //3
133 | ----
134 |
135 | - オブジェクト自身の値 `this = 1`
136 | - メソッド `plus` 関数
137 | - パラメータ `2`
138 |
139 | 二項演算(中置記法)
140 |
141 | [stem]
142 | ++++
143 | 1 + 2
144 | ++++
145 |
146 | は、JavaScriptのオブジェクトとメソッドで書けます。
147 |
148 | [stem]
149 | ++++
150 | (1).plus(2)
151 | ++++
152 |
153 | そして、このように、値(オブジェクト)&関数(オブジェクトにぶらさがるメソッド)のペアで書くのは非常に優れているんですね。
154 |
155 | jQueryのメソッドチェーンのことを思い出しましょう。
156 |
157 |
158 | [stem]
159 | ++++
160 | 1 + 2 + 5 + 9
161 | ++++
162 |
163 | は、そのまま、
164 |
165 | [stem]
166 | ++++
167 | (1).plus(2).plus(5).plus(9)
168 | ++++
169 |
170 | と、メソッドチェーンで自然に書けてしまう。
171 |
172 | [TIP]
173 | ====
174 | 逆に言うと、メソッドチェーンは、代数のなんらかの二項演算の連鎖をコード上に表現しているにすぎません。そして後からでてきますが、これはendofunctorやモナドにも当てはまります。
175 | ====
176 |
177 | オブジェクトにぶらさがるメソッドではない普通の関数の形式
178 |
179 | [stem]
180 | ++++
181 | plus(1)(2)
182 | ++++
183 | ではこううまくは行きません。
184 |
185 | [stem]
186 | ++++
187 | plus(plus(plus(1)(2))(5))(9)
188 | ++++
189 |
190 | 「なんとか地獄」と名前がつきそうな感じです。
191 |
192 | JavaScriptがマルチパラダイムで、オブジェクト指向のメソッド形式で書けるおかげで、**任意の二項演算、つまりパラメータを2つとる関数は、特別な定義不要で、その関数名(メソッド名)のまま中置記法が実現できてしまう**という予期しない副産物(棚ぼた)です。
193 |
194 | == 値と演算は常に組(ペア)で存在する
195 |
196 | > 抽象代数学におけるマグマ(英語: magma)または亜群(あぐん、groupoid)は、演算によって定義される種類の基本的な代数的構造であり、**集合 M とその上の二項演算 M × M → M からなる組をいう**。マグマ M における二項演算は M において閉じていることは要求するが、それ以外の何らの公理も課すものではない。
197 | https://ja.wikipedia.org/wiki/%E3%83%9E%E3%82%B0%E3%83%9E_(%E6%95%B0%E5%AD%A6)[マグマ(数学)]
198 |
199 | 基本的な代数構造において、演算だけ独立して存在していることはありません。必ず演算のターゲットとなる値の集合と組(ペア)として存在しています。
200 |
201 | たとえば、 四則演算のうち「加算」は演算対象となるデータとは加算可能な数値ですよね?文字列であったり、なにかの画像データではありません。
202 |
203 | _抽象代数学_ とか _代数的構造_ とか言われると、つい数値のことを連想しがちなのですが、
204 |
205 | > マグマ M における二項演算は M において閉じていることは要求するが、それ以外の何らの公理も課すものではない。
206 |
207 | とあるとおり、なんの制約もありません。
208 |
209 | 値が文字列ならば、その組となる、文字列というデータを演算するための二項演算は自由に定義可能だし、実際JavaScriptには、 https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/String[String]プロトタイプオブジェクトと、それ専用の二項演算子が実装されています。
210 |
211 |
212 | [source,js]
213 | .Hello world
214 | ----
215 | "Hello" + " " + "world" //Hello world
216 | ----
217 |
218 | 文字列データを二項演算するときの `+` は文字列の接続処理で、数値データを二項演算する `+` の加算処理とは意味が異なります。**値と演算は常に組(ペア)で存在するのであって、演算子の単独では意味を成しません**。
219 |
220 | そしてこれは、まさに**オブジェクトとメソッドの関係に合致**しており、二項演算の連続的操作が、そのまま上手くオブジェクトのメソッドチェーンで書けてしまう理論的背景が納得できます。
221 |
222 | 関数型プログラミングで、値、関数というとき、暗黙に組(ペア)となる相手がいます(プログラムで処理されないデータは意味がない)。そして、静的型付けの仕組み(JavaScriptならTypeScriptを使えばいい)などで、この値と関数の組(ペア)性を保証していきます。
223 |
224 | しかし、繰り返し、これはまったく想定外のことですが、関数型プログラミングであっても、オブジェクト指向のオブジェクトとメソッドという組は、値(データ)と演算(関数)が組となる二項演算を定義する代数構造と解釈することで極めて有用です。
225 |
226 | == まとめ
227 |
228 | 二項演算をベースに考える。
229 |
230 |
231 | [NOTE]
232 | ====
233 |
234 | マグマ(英語: magma)または亜群(あぐん、groupoid)は、演算によって定義される種類の基本的な代数的構造であり、集合 M とその上の二項演算 M * M → M からなる組をいう。
235 |
236 | 値と演算は常に組(ペア)で存在するのであって、演算子の単独では意味を成しません。
237 |
238 | ---
239 |
240 | と、逐一書くのも面倒なので、今後マグマという組(ペア)は
241 |
242 | [stem]
243 | ++++
244 | (M, ∗)
245 | ++++
246 |
247 |
248 |
249 | と書くことにします。
250 |
251 | 演算 `*` はワイルドカードです。二項演算 M ∗ M → M ならなんでも良い。
252 |
253 | たとえば、二項演算が**自然数の足し算**と定まれば、ワイルドカード `*` は `+` になります。
254 |
255 | [stem]
256 | ++++
257 | (自然数,+)
258 | ++++
259 |
260 |
261 | 二項演算が**自然数の掛け算**と定まれば、
262 |
263 | [stem]
264 | ++++
265 | (自然数,\times)
266 | ++++
267 |
268 | 繰り返し念の為ですが、代数構造といえども、対象となるデータは、数値に限りません。
269 |
270 | 二項演算が**文字列の接続**と定まれば、ワイルドカード `*` は `+` になります。
271 |
272 | [stem]
273 | ++++
274 | (文字列,+)
275 | ++++
276 |
277 |
278 | ====
279 |
280 | マグマ(M, ∗) はプログラムの世界にそのまま展開できて、
281 |
282 | `M` = 値、データ、オブジェクト
283 |
284 | `*` = 二項演算、パラメータ2つの関数、メソッド
285 |
286 | と言うように、データと処理の組、つまり**データ処理**のことだと解釈できます。
287 |
288 | [stem]
289 | ++++
290 | 1 + 2 + 5 + 9
291 | ++++
292 |
293 | という二項演算の連続的操作は、そのまま、
294 |
295 | [stem]
296 | ++++
297 | (1).plus(2).plus(5).plus(9)
298 | ++++
299 |
300 | とオブジェクトのメソッドチェーンとして表現できる。
301 |
302 |
303 | .代数、関数型、オブジェクト指向のイディオム
304 | [cols="h,d,d"]
305 | |================
306 | |代数 |値|演算
307 | |関数型 |値、データ|関数
308 | |オブジェクト指向|値、データ、オブジェクト|メソッド
309 | |================
310 |
--------------------------------------------------------------------------------
/docs/img/unitflat3.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/img/unitflat5.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/docs/ch_07.adoc:
--------------------------------------------------------------------------------
1 |
2 | = モノイド(Monoid)
3 | ifndef::stem[:stem: latexmath]
4 | ifndef::icons[:icons: font]
5 | ifndef::imagesdir[:imagesdir: ./img/]
6 | ifndef::source-highlighter[:source-highlighter: highlightjs]
7 | ifndef::highlightjs-theme:[:highlightjs-theme: tomorrow-night]
8 |
9 | ++++
10 |
17 | ++++
18 |
19 | モノイドは、理解するのが簡単、しかし奥が深く、モナドと同じかそれ以上に関数型プログラミングで応用局面もあり実用性が高いという、費用対効果(コスパ)抜群の品質の高いプログラミングの部品となりうるものです。
20 |
21 | だいたい、モナドを知りたいのなら、同時にモノイドを知っておくべきなのは当たり前のことなのですが、ここまでモナド偏重でモノイドについてはあまり語られないのは、<>の事情が原因です。
22 |
23 | == 単位元
24 |
25 | また、小学1年算数を復習すると、
26 |
27 | [stem]
28 | ++++
29 | 1 + 1
30 | ++++
31 |
32 | みたいな足し算を最初に学びます。
33 |
34 | 子供というか、我々大人でも、脳は、すでに馴染みがある事象の延長・拡張でしか「理解する」というのは無理で、まず最初は、具体的な物質である「数え棒」「おはじき」を渡されて、徐々に数学的な抽象的概念に慣らされていきます。
35 |
36 | どういう数学なのかというと、**(正の)自然数全体のなす加法の二項演算**ですよね。一番シンプルなパターンです。
37 |
38 | このとき、 `+` は、**(正の)自然数全体** と組(ペア)となる**二項演算**としてしっかりと定義されています。
39 |
40 | 児童が**(正の)自然数全体のなす加法の二項演算**という抽象的作業に慣らされたところで大事件が起こります。
41 |
42 |
43 | ---
44 |
45 | https://ja.wikibooks.org/wiki/%E5%B0%8F%E5%AD%A6%E6%A0%A1%E7%AE%97%E6%95%B0/1%E5%AD%A6%E5%B9%B4#%E3%82%BC%E3%83%AD_0[**ゼロ 0**]
46 |
47 | _1 から 1 を ひいた かず を ゼロ と いいます。 ゼロ は 0 と かきます。_
48 |
49 | _0は、なにも、ない かず です。_
50 |
51 | _だから、 かず に 0 を たしても、 かわりません。_
52 |
53 | _たとえば_
54 |
55 | _7+0=7_
56 | _「なな たす ゼロ は(わ) なな」です。_
57 |
58 | ---
59 |
60 | [stem]
61 | ++++
62 | 7 + 0 = 7
63 | ++++
64 |
65 |
66 | ゼロの発明は、数学史の飛躍の一つで、5世紀ごろのインド文明
67 | で数字としてのゼロが発明されたのも数学が生まれてから2000年くらい経過した後ですし、ヨーロッパで広まったのは、中世を経てルネサンスのさらに後のニュートンの時代ですから、人類の数学史を考えると結構最近の発明だと言えます。
68 |
69 | それなのに、さらっと小1の児童にゼロの概念をさも当たり前のように伝えるのですから、教育というものの凄まじさを実感できます。
70 |
71 | 数の体系が
72 |
73 | **(正の)自然数全体**
74 |
75 | ↓
76 |
77 | **(ゼロを含む)自然数全体**
78 |
79 | にしれっと拡張されてしまいました。
80 |
81 | そして、ここで誤魔化されてはならないのが、同じ記号 `+` であっても、ゲームのルールが異なる、ということ。
82 |
83 | **二項演算**というのは、必ず、演算対象と組(ペア)となってはじめて意味がある定義が成されるはずだったので、
84 |
85 | **(正の)自然数全体のなす加法の二項演算**
86 |
87 | ↓
88 |
89 | **(ゼロを含む)自然数全体のなす加法の二項演算**
90 |
91 | と、二項演算も同時に更新されてしまいました。
92 |
93 |
94 | [TIP]
95 | .単位元の添加
96 | ====
97 | こういう正の自然数に更に「ゼロの後乗せ」してゼロを含む自然数に拡張する、同時に組(ペア)になっているはずの二項演算子も更新することを、単位元の添加と言います。
98 |
99 | https://ja.wikipedia.org/wiki/%E5%8D%98%E4%BD%8D%E5%85%83#%E6%80%A7%E8%B3%AA[単位元#性質]
100 |
101 | _マグマ (M, ∗) が与えられたとき、M に M のどの元とも異なる新たな元 1 を付け加えた集合 M1 := M ∪ {1} で_
102 |
103 | _任意の a ∈ M1 に対して a * 1 = 1 * a = a_
104 | _と定めて、M の演算 ∗ を M1 上に延長することにより、元 1 を M1 の ∗ に関する単位元とすることができる。この (M1, ∗) を (M, ∗) の 1-添加という。_
105 |
106 | _もし、M がもともと ∗ に関する単位元 e を持っていたとしても、e は M1 上ではもはや ∗ に関する単位元ではない。_
107 |
108 |
109 |
110 |
111 | ====
112 |
113 |
114 |
115 | こういう、「だから、 かず に 0 を たしても、 かわりません。」というような、ある対象を演算しても不変になるような対象を
116 | https://ja.wikipedia.org/wiki/%E5%8D%98%E4%BD%8D%E5%85%83[
117 | 単位元]と言います。
118 |
119 | ここで「かず」とは書かずに「対象」とか書いたのは、演算も単位元も、べつに数に限らないからです。
120 |
121 | ---
122 |
123 | > 数学、とくに抽象代数学において、単位元(たんいげん, 英: identity element)あるいは中立元(ちゅうりつげん, 英: neutral element)は、二項演算を備えた集合の特別な元で、ほかのどの元もその二項演算による単位元との結合の影響を受けない。
124 |
125 | https://ja.wikipedia.org/wiki/%E5%8D%98%E4%BD%8D%E5%85%83[
126 | 単位元]
127 |
128 | ---
129 |
130 | [WARNING]
131 | .単位元を表す記号
132 | ====
133 | 「単位元」は、**identity element**から、
134 |
135 | - identity
136 | - e
137 |
138 | あるいは「中立元」**neutral element**の頭文字**n**に似ている(と筆者は思っている)
139 |
140 | - η (エータ、イータ、イタ)
141 |
142 | と表記されることが多いですが、本稿では、とっつきやすさを重視して、
143 |
144 | **e**と表記することにします。
145 |
146 | ただし、後々まったく同じ数学的対象なのに、後から、単位元のことを、identityと書かれたり、ηと書かれたり、場合によっては `unit` だの `return` だの本質ではないところの用語の使い回しで、モナド界隈特有の混乱を極めるので、それは心の準備が必要です。
147 | ====
148 |
149 |
150 | === 左右の単位元
151 |
152 |
153 | 加法の単位元 `e` は `0` で、
154 | [stem]
155 | ++++
156 | 0 + 7 = 7 = 7 + 0
157 | ++++
158 |
159 | 乗法の単位元 `e` は `1` で、
160 | [stem]
161 | ++++
162 | 1 \times 7 = 7 = 7 \times 1
163 | ++++
164 |
165 | 文字列の単位元 `e` は `""` となります。
166 | [stem]
167 | ++++
168 | "" + "Hello" = "Hello" = "Hello" + ""
169 | ++++
170 |
171 | === 結合法則
172 |
173 | [stem]
174 | ++++
175 | (a + b) + c = a + (b + c)
176 | ++++
177 |
178 | [stem]
179 | ++++
180 | (a \times b) \times c = a \times (b \times c)
181 | ++++
182 |
183 | [stem]
184 | ++++
185 | ("Hello" + "\space") + "world" = "Hello" + ("\space" + "world")
186 | ++++
187 |
188 | このように
189 |
190 | 1. **左右の単位元 e がある**
191 |
192 | 2. **結合法則が成り立つ**
193 |
194 | 代数構造のことを、モノイド(monoid)と呼びます。
195 |
196 | ちなみに、四則演算の仲間でも引き算と割り算は、モノイドにはなりません、念の為。
197 |
198 | == なぜモノイドと結合法則が重要なのか?
199 |
200 | モノイド(monoid)だの「結合法則」だの言われると、理屈は単純でも、仰々しい天下り説明ぽくて、なんでそんなことが必要なのか?と思いがちなので説明します。
201 |
202 | モノイドは、構造として対称性があって、適当に組み合わせても不変性があるので、関数型プログラミングの部品としては優れています。
203 |
204 | 部品の組み合わせということで、たとえばLEGOブロックを考えてみると、組み立て順序は自由なはずです。ある部分を先に組み立てて、別の部分を組み立て、それらをまた組み合わせる。これがもし、aとbは先に組み立てなければいけない、bとcを先に組み立てたものに後からaを組み合わせても、別物になるから!となると面倒なことになります。
205 |
206 | USBデバイスを考えてみましょう。USBハブやら組み合わせ自由で、その接続する順番は気にする必要はないですよね?組み合わせは組み合わせです。順序によって構造に違いは生まれません。
207 |
208 | ちなみに、LEGOブロックの組み立て、USBデバイスの接続も二項演算です。小1の授業でやられたみたいに、何も組み立てない、何も接続しない、というゲームのルールを追加したならば、二項演算しても何も影響を及ばなさい単位元の添加したってことなので、それまで考えていた組み立ての意味とは異なるでしょうが、そういうモノイドになります。
209 |
210 |
211 | **結合法則が成り立つ** というのは、法則によってプログラマが縛られたり、法則を満たすように留意事項増える、ということではありません。まったくその逆で、法則によって、こういった組み合わせ順序は自由、という自由度、柔軟性、堅牢性がある部品、という保証があるということです。言い換えると、使いやすい基準をパスしている品質の高い部品だということ。
212 |
213 | プログラミングはただでさえ、複雑で、何も考えないでやると、どんどん複雑になっていってコントロール不能、デバッグ不可能になっていきますよね?なるだけ構造はシンプルに維持しておきたいのです。
214 |
215 | この部品はモノイドなので、組み合わせの自由度が高い、逆に、モノイドじゃないので、どんどん構造が増えていって面倒なことになるな・・・という認識が持てるのと持てないとでは大きな違いです。**この部品はモノイドであることは事前に十分確認済みなので、このメソッド(二項演算)まわりで予期しない振る舞いをして、バグが出るはずはない、と確信を持ってスルーできるのはかなり大きいメリットですよね?**
216 |
217 |
218 |
219 |
220 |
221 | [TIP]
222 | .モノイドは3つ組
223 | ====
224 |
225 | > マグマ(英語: magma)または亜群(あぐん、groupoid)は、演算によって定義される種類の基本的な代数的構造であり、集合 M とその上の二項演算 M * M → M からなる組をいう。
226 |
227 | > 値と演算は常に組(ペア)で存在するのであって、演算子の単独では意味を成しません。
228 |
229 | でしたが、マグマ(M,∗)でも特に、
230 |
231 | 1. **左右の単位元 e がある**
232 |
233 | 2. **結合法則が成り立つ**
234 |
235 | がモノイドです。モノイドのことは、
236 |
237 | [stem]
238 | ++++
239 | (M,e,*)
240 | ++++
241 |
242 | と書くことにしましょう。
243 |
244 | 組(ペア)から3つ組(トリプル)になったのがポイントです。
245 |
246 | 具体的な二項演算が定まったときは、
247 |
248 | [stem]
249 | ++++
250 | (自然数,0, +)
251 | ++++
252 |
253 | [stem]
254 | ++++
255 | (自然数,1,\times)
256 | ++++
257 |
258 | [stem]
259 | ++++
260 | (文字列,"", +)
261 | ++++
262 |
263 | というようになります。
264 | ====
265 |
266 |
267 |
268 | == 単一のタイプで自己完結
269 |
270 | モノイドは
271 |
272 | [stem]
273 | ++++
274 | 自然数 + 自然数 = 自然数
275 | ++++
276 |
277 | [stem]
278 | ++++
279 | 自然数 \times 自然数 = 自然数
280 | ++++
281 |
282 | [stem]
283 | ++++
284 | 文字列 + 文字列 = 文字列
285 | ++++
286 |
287 | [stem]
288 | ++++
289 | レゴブロック + レゴブロック = レゴブロック
290 | ++++
291 |
292 | [stem]
293 | ++++
294 | USBデバイス + USBデバイス = USBデバイス
295 | ++++
296 |
297 |
298 | というようにすべて、ただ一種類のタイプで自己完結している二項演算の世界です。
299 |
300 | モノイドは連続的に接続可能で、自然数の加法の二項演算の場合、
301 |
302 | [stem]
303 | ++++
304 | 1 + 2 + 3 = 6
305 | ++++
306 |
307 | という二項演算の連続的操作は、そのまま、
308 |
309 | [stem]
310 | ++++
311 | (1).plus(2).plus(3) = 6
312 | ++++
313 |
314 | と<>、メソッドチェーンとして表現できます。
315 |
316 | == Array(リスト・配列)は、モノイド
317 |
318 | Array(リスト・配列)は、モノイドです。
319 |
320 | [stem]
321 | ++++
322 | (Array,[\space],concat)
323 | ++++
324 |
325 |
326 | === Array.concat メソッドという二項演算
327 | [stem]
328 | ++++
329 | [1,2].concat([3]).concat([4,5]) = [1,2,3,4,5]
330 | ++++
331 |
332 | .https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/concat[Array.concat]
333 |
334 | > concat() メソッドは、配列に他の配列や値をつないでできた新しい配列を返します。
335 |
336 | === Array.concat メソッドで不変の左右の単位元 eとは?
337 |
338 | [stem]
339 | ++++
340 | [\space].concat([1,2])
341 | ++++
342 | [stem]
343 | ++++
344 | = [1,2]
345 | ++++
346 | [stem]
347 | ++++
348 | =[1,2].concat([\space])
349 | ++++
350 |
351 | `Array.concat` メソッドを二項演算 `*` と再び捉え直すと、
352 |
353 | [stem]
354 | ++++
355 | [\space]*[1,2] = [1,2] = [1,2]*[\space]
356 | ++++
357 |
358 | と、Arrayモノイドの左右の単位元 e は `[ ]` 。
359 |
360 |
361 |
362 |
363 | === Array.concat は結合法則を満たす
364 |
365 | [stem]
366 | ++++
367 | [1,2] * [3] * [4,5] = [1,2,3,4,5] = [1,2] * ( [3] * [4,5] )
368 | ++++
369 |
370 |
371 | [source,js]
372 | .Array Monoid
373 | ----
374 | const array1 =
375 | [1, 2]
376 | .concat([3]) <1>
377 | .concat([4, 5]); <2>
378 |
379 | console.log(array1);
380 | ----
381 |
382 | <1> `[1, 2]` と `[3]` を接続
383 |
384 | <2> `[1, 2, 3]` と `[4,5]` を接続
385 |
386 | [source,js]
387 | .Console
388 | ----
389 | [ 1, 2, 3, 4, 5 ]
390 | ----
391 |
392 |
393 | [source,js]
394 | .Array Monoid 結合法則
395 | ----
396 | const array1 =
397 | [1, 2].concat( <1>
398 | [3].concat([4, 5]) <2>
399 | );
400 |
401 | console.log(array1);
402 | ----
403 |
404 | <1> `[1,2]` と `[3,4,5]` を後から接続
405 |
406 | <2> `[3]` と `[4,5]` を先に接続
407 |
408 | [source,js]
409 | .Console
410 | ----
411 | [ 1, 2, 3, 4, 5 ]
412 | ----
413 |
414 | と結合順序を変えても結果は変わりません。
415 |
416 |
417 | == まとめ
418 |
419 | モノイドは関数型プログラミングで役立つし、理解しておくのは重要。この章はただの紹介にすぎず、もっと充実すべく加筆が必要。
420 |
421 | [TIP]
422 | .Array.flatMapと似ている?
423 | ====
424 |
425 | モノイドの結合法則から、`Array.concat` のメソッドチェーンを組み替えてネストしても同じ結果を出す、という光景は、モナドである `Array.flatMap` のメソッドチェーンの組み換えとネストの実現ととても似ています。
426 |
427 | 次の章ではそこを追求してスッキリさせましょう。
428 |
429 | ====
430 |
431 |
432 |
433 |
--------------------------------------------------------------------------------
/docs/ch_08.adoc:
--------------------------------------------------------------------------------
1 |
2 | = モノイドとモナドの関係
3 | ifndef::stem[:stem: latexmath]
4 | ifndef::icons[:icons: font]
5 | ifndef::imagesdir[:imagesdir: ./img/]
6 | ifndef::source-highlighter[:source-highlighter: highlightjs]
7 | ifndef::highlightjs-theme:[:highlightjs-theme: tomorrow-night]
8 |
9 | ++++
10 |
17 | ++++
18 |
19 | だいたい、モノイドとモナドは名前が似すぎています。何らかの密接な関係性がきっとあるのでしょう。
20 |
21 | ここでは、まず Arrayモノイドと Arrayモナドの根本的な差を確認してから、関係性をみていきます。
22 |
23 | == モノイドは2つの単一のタイプの間の二項演算
24 |
25 | `Array.concat` を二項演算とするArrayモノイド(3つ組)
26 |
27 | [stem]
28 | ++++
29 | (Array,[\space],concat)
30 | ++++
31 |
32 | は、
33 |
34 | [stem]
35 | ++++
36 | [1,2].concat([3]).concat([4,5])
37 | ++++
38 |
39 | [source,js]
40 | .Array.concat chain
41 | ----
42 | [1, 2]
43 | .concat([3])
44 | .concat([4, 5])
45 | ----
46 |
47 | とメソッドチェーンで書けます。
48 |
49 | - 任意の `Array` の値を `a`
50 |
51 | - `Array.concat` メソッドを二項演算子 `*`
52 |
53 | と置き換えてやれば、
54 |
55 | [stem]
56 | ++++
57 | a_{1} * a_{2} = a_{3}
58 | ++++
59 |
60 | が基本形で、連鎖できるのだから、
61 |
62 | [stem]
63 | ++++
64 | a_{1} * a_{2} * ...
65 | ++++
66 |
67 | という形になっています。
68 |
69 |
70 | [stem]
71 | ++++
72 | 1 + 2 = 3
73 | ++++
74 | [stem]
75 | ++++
76 | 1 + 2 + ...
77 | ++++
78 |
79 | と同じことです、念の為。
80 |
81 | `Array.concat` は2つのパラメータをとり、1つの返り値がありますが、すべて3つとも同一のタイプで閉じた世界の二項演算です。
82 |
83 | [[bicategory]]
84 | == モナドはモナド値とモナド関数の間の二項演算
85 |
86 | `Array.flatMap` を二項演算とするArrayモナド(リストモナド)は、
87 |
88 | [source,js]
89 | .Array.flatMap chain
90 | ----
91 | [1, 2, 3, 4, 5]
92 | .flatMap(a => [a * 2])
93 | .flatMap(a => [a + 1])
94 | ----
95 |
96 | とメソッドチェーンで書けます。同じように
97 |
98 | - 任意の `Array` の値を `a`
99 |
100 | - `Array.concat` メソッドを二項演算子 `*`
101 |
102 | - モナドが返り値と規定される関数を `f`
103 |
104 | と置き換えてやれば、
105 |
106 | [stem]
107 | ++++
108 | a_{1} * f = a_{2}
109 | ++++
110 |
111 | が基本形で、連鎖できるのだから、
112 |
113 | [stem]
114 | ++++
115 | a * f_{1} * f_{2} * ...
116 | ++++
117 |
118 | という形になっています。
119 |
120 | モノイドのように、2つのパラメータ、1つの返り値、すべて3つとも同一のタイプで閉じた世界だ、というのとは根本的に異なります。
121 |
122 | Arrayモナドのメソッドである `Array.flatMap` は
123 |
124 | 1. `Array` の値 `a`
125 | 2. モナド関数 `f`
126 |
127 | と二つの異なるタイプの間の二項演算です。
128 |
129 |
130 |
131 | <>で、
132 |
133 | > A monad in a bicategory K というのは、とりあえず置いときましょう
134 |
135 | の `bicategory` (2つのカテゴリ)とか書かれていたのは、このことです。
136 |
137 |
138 | == モナドはモノイドなのか?
139 |
140 | まあ、上記のとおり、モノイドは単一タイプの二項演算で、モナドは二つの異なるタイプの二項演算と根本的に異なるので、答えは__NO__のように思えますが、見方によっては__YES__・・・みたいなことにはならないでしょうか?
141 |
142 | そういえば、<>の<>に、
143 |
144 | [NOTE]
145 | ====
146 | 圏論のモナド(monad)の定義をまとめると
147 |
148 | 1. ベースとして、オブジェクト自身を返す `map` メソッドを持つendofunctorとしての特性をもつオブジェクトで、さらに以下の2つの関数(メソッド)がある
149 | 2. `unit`
150 | 3. `flat`
151 |
152 | この3つ組(トリプル)
153 |
154 | [stem]
155 | ++++
156 | (endofunctor, unit, flat)
157 | ++++
158 |
159 | をモナドと呼びます。
160 | ====
161 |
162 | と厳密な圏論のモナド定義がありましたが、`flat` はすでにモナド二項演算として関数合成されてしまって今は、`flatMap` となっていたのでした。
163 |
164 | === モナドの単位元
165 |
166 | では今度は、`Array.flatMap` を二項演算とするArrayモナドを、
167 | `Array.concat` を二項演算とするArrayモノイド(3つ組)
168 | [stem]
169 | ++++
170 | (Array,[\space],concat)
171 | ++++
172 |
173 | のように、モノイドの観点から捉えられないか?
174 |
175 | [stem]
176 | ++++
177 | (Array,
178 | ++++
179 | [stem]
180 | ++++
181 | flatMapの左右単位元,
182 | ++++
183 | [stem]
184 | ++++
185 | flatMap)
186 | ++++
187 |
188 |
189 | とならないか?
190 |
191 | **flatMapの左右単位元** の候補として手元に唯一残っている部品は、`flatMap` に合成されてしまった `flat` と対になる関数 `unit` (`a => [a]`) です。
192 |
193 | Arrayモノイドの `Array.concat` メソッドで確認したことは以下です。
194 |
195 |
196 | [NOTE]
197 | .Array.concat メソッドの二項演算と単位元
198 | ====
199 | `Array.concat` メソッドで不変の左右の単位元 eとして、
200 |
201 | [stem]
202 | ++++
203 | [\space].concat([1,2])
204 | ++++
205 | [stem]
206 | ++++
207 | = [1,2]
208 | ++++
209 | [stem]
210 | ++++
211 | =[1,2].concat([\space])
212 | ++++
213 |
214 | `Array.concat` メソッドを二項演算 `*` と再び捉え直すと、
215 |
216 | [stem]
217 | ++++
218 | [\space]*[1,2] = [1,2] = [1,2]*[\space]
219 | ++++
220 |
221 | と、Arrayモノイドの左右の単位元 e は `[ ]` だから、
222 |
223 | モノイド(3つ組)
224 |
225 | [stem]
226 | ++++
227 | (Array,[\space],concat)
228 | ++++
229 | ====
230 |
231 | **flatMapの左右単位元** が `unit`
232 |
233 | [stem]
234 | ++++
235 | (Array,unit,flatMap)
236 | ++++
237 |
238 | だと証明するためには、これをリバースエンジニアリングしていければいいでしょう。多分。
239 |
240 | `Array.flatMap` メソッドを二項演算 `*` と再び捉え直すと、
241 |
242 | [stem]
243 | ++++
244 | unit*[1,2] = [1,2] = [1,2]*unit
245 | ++++
246 |
247 | としたいところですが、これではタイプエラーになります。
248 |
249 | `Array.flatMap` は
250 |
251 | 1. `Array` の値 `a`
252 | 2. モナド関数 `f`
253 |
254 | と二つの異なるタイプの間の二項演算
255 | [stem]
256 | ++++
257 | a_{1} * f = a_{2}
258 | ++++
259 | で、右辺はこのタイプで合致しますが、左辺は、最初に `Array` の値 `a` が入るべきところ、`unit` 関数になっているのでタイプが合いません。
260 |
261 | 逆に、モナド関数 `f` を使っても
262 |
263 | [stem]
264 | ++++
265 | unit*f = f = f*unit
266 | ++++
267 |
268 | 同じ理由で左右タイプエラーになります。
269 |
270 |
271 | なので、すべての項において、この二項演算に合うようにパラメータと返り値のタイプを合わせます。
272 |
273 | [stem]
274 | ++++
275 | unit(a)*f = f(a) = f(a)*unit
276 | ++++
277 |
278 | これが本当に成立していれば、**flatMapの左右単位元** は `unit` だと言えそうです。
279 |
280 | 二項演算 `*` をまた `Array.flatMap` メソッドに戻して、具体的な値を決め打ちして挙動を検証してみます。
281 |
282 | [stem]
283 | ++++
284 | a = [1,2]
285 | ++++
286 |
287 | [stem]
288 | ++++
289 | f(a) = 適当なモナド関数
290 | ++++
291 |
292 | [source,js]
293 | .Array Monad 左右単位元
294 | ----
295 | const unit = a => [a];
296 |
297 | const a = [1, 2];
298 | const f = a =>
299 | a.flatMap(a => [a, a * 5]); <1>
300 |
301 | const left = unit(a).flatMap(f); <2>
302 | const center = f(a); <3>
303 | const right = f(a).flatMap(unit); <4>
304 |
305 | console.log(left);
306 | console.log(center);
307 | console.log(right);
308 | ----
309 |
310 | <1> 適当なモナド関数 <>
311 | <2> stem:[unit(a)*f] 左単位元
312 | <3> stem:[f(a)]
313 | <4> stem:[f(a)*unit] 右単位元
314 |
315 | [source,js]
316 | .Console
317 | ----
318 | [ 1, 5, 2, 10 ]
319 | [ 1, 5, 2, 10 ]
320 | [ 1, 5, 2, 10 ]
321 | ----
322 |
323 | 本当に成立したので、**flatMapの左右単位元** は `unit` だと言えそうです。
324 |
325 | === モナドの結合法則
326 |
327 | あとモノイドの重要な特性として、結合法則を満たしている、というのがあります。
328 |
329 | 単一タイプ(a,b,c)間の二項演算 `*` をもつモノイドの結合法則は、
330 |
331 | [stem]
332 | ++++
333 | (a * b) * c = a * b * c = a * (b * c)
334 | ++++
335 |
336 | モナド値 a とモナド関数(f, g)の2タイプ間の二項演算 `*` をもつモナドの結合法則では、
337 |
338 |
339 | [stem]
340 | ++++
341 | (a * f) * g = a * f * g = a * (a \Rightarrow a * f * g)
342 | ++++
343 |
344 | [TIP]
345 | .モナド合成関数 fg
346 | ====
347 | [stem]
348 | ++++
349 | a * f = a
350 | ++++
351 | (二項演算の後ろに来るのは必ずモナド関数だ)
352 | という制約があるため、右辺の結合では、先に
353 | [stem]
354 | ++++
355 | a \Rightarrow a * f * g
356 | ++++
357 | と、モナド関数の合成をしていることに留意してください。
358 |
359 | 合成モナド関数 `fg`
360 | [stem]
361 | ++++
362 | fg = a \Rightarrow a * f * g
363 | ++++
364 | と置き換えた上で、結合法則を書き直せば、
365 |
366 | [stem]
367 | ++++
368 | (a * f) * g = a * f * g = a * fg
369 | ++++
370 |
371 | となります。
372 | ====
373 |
374 |
375 |
376 |
377 | [source,js]
378 | .Array Monad 結合法則
379 | ----
380 | {
381 | const array1 =
382 | [1, 2, 3] <1>
383 | .flatMap(a => [a * 2]) <1>
384 | .flatMap(a => [a + 1]); <1>
385 |
386 | console.log(array1);
387 | }
388 | {
389 | const array1 =
390 | [1, 2, 3] <2>
391 | .flatMap( <2>
392 | a => [a] <3>
393 | .flatMap(a => [a * 2]) <3>
394 | .flatMap(a => [a + 1]) <3>
395 | );
396 |
397 | console.log(array1);
398 | }
399 |
400 | ----
401 |
402 | <1> stem:[a * f * g]
403 | <2> stem:[a * fg]
404 | <3> stem:[a \Rightarrow a * f * g] モナド合成関数 `fg`
405 |
406 |
407 |
408 | [source,js]
409 | .Console
410 | ----
411 | [ 3, 5, 7 ]
412 | [ 3, 5, 7 ]
413 | ----
414 |
415 | というか、実はこれ <>の<>で、やっていたことの繰り返しで、とっくに検証は終わっています。
416 | **モナドの結合法則とは、モナド関数の合成のこと**だったんですね。
417 |
418 | == クライスリトリプル(Kleisli triple)
419 |
420 | このように、
421 |
422 | > https://stackoverflow.com/questions/3870088/a-monad-is-just-a-monoid-in-the-category-of-endofunctors-whats-the-problem[
423 | A monad is just a monoid in the category of endofunctors, what's the problem?]
424 |
425 | **「モナドっていうのは、ただ単に、自己関手(endofunctor)の圏の中におけるモノイドのことなんだよ、なにか問題でも?」**
426 | などと時折言われるわけですが、モナドをモノイドの性質を備える特殊なendofunctorであると捉え、 +
427 | (オブジェクト、左右単位元、二項演算)の3つ組(トリプル)にしたもの
428 |
429 | [stem]
430 | ++++
431 | (endofunctor,unit,flatMap)
432 | ++++
433 |
434 | を、link:https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%B9%E3%83%AA%E5%9C%8F#%E3%82%AF%E3%83%A9%E3%82%A4%E3%82%B9%E3%83%AA%E3%83%88%E3%83%AA%E3%83%97%E3%83%AB%EF%BC%88Kleisli_triple%EF%BC%89[クライスリトリプル(Kleisli triple)]と呼びます。
435 |
436 |
437 | [TIP]
438 | .比較してみよう ふたつのトリプル
439 | ====
440 | 圏論で一般的に定義されるモナド(monad)/トリプル
441 |
442 | [stem]
443 | ++++
444 | (endofunctor, unit, flat)
445 | ++++
446 |
447 | クライスリトリプル(Kleisli triple)
448 | [stem]
449 | ++++
450 | (endofunctor,unit,flatMap)
451 | ++++
452 |
453 | ====
454 |
455 |
456 |
457 | == モナド則(Monad Laws)
458 | さらに推し進め、モノイド則の用語をまるまる踏襲した上でモナドの法則として列挙したのがモナド則(Monad Laws)です。
459 |
460 | すでに書いていますが、再掲すると、
461 |
462 | .左右の単位元
463 |
464 | [stem]
465 | ++++
466 | unit(a)*f = f(a) = f(a)*unit
467 | ++++
468 |
469 | .結合法則
470 |
471 | [stem]
472 | ++++
473 | (a * f) * g = a * f * g = a * (a \Rightarrow a * f * g)
474 | ++++
475 |
476 | ですね。
477 |
478 | [WARNING]
479 | .モナド則解読不能版
480 | ====
481 |
482 | モナド則は、左単位元と右単位元にバラされた上で、右単位元の `f(a)` は 「どうせモナド値なのだから」と実用上の利点はあるにせよ、特に断りもなく単に `m` として簡約され、方程式の左右を入れ替えられたり、いろいろした結果、
483 |
484 | - 左単位元(LEFT IDENTITY) stem:[unit(a)*f = f(a)]
485 |
486 | - 右単位元(RIGHT IDENTITY) stem:[m*unit = m]
487 |
488 | - 結合法則(ASSOCIATIVITY) stem:[(m * f) * g = m * (x \Rightarrow x * f * g)]
489 |
490 | という感じでエンドユーザに提供されることが多いようです。
491 |
492 | とりあえず、モノイドのことを知らない人、知っててもモナドとの関連がわからない人には、特に上のように式変形された結果、対称性も読み取りにくい左右の単位元とか解読不能でしょう。
493 |
494 | あとモナドがプログラミングに導入された例の歴史的経緯により、Haskell特有の二項演算子の表記と、Syntaxで提示されることが多いので、HaskellのSyntaxがわからない人はお手上げとなる可能性が高いです。
495 |
496 | ====
497 |
498 | モナドの二項演算 `*` を `Array.flatMap` メソッドとして具体化して書き直すと、
499 |
500 |
501 |
502 | [source,js]
503 | .Array Monad 左右単位元
504 | ----
505 | const left = unit(a).flatMap(f);
506 | const center = f(a);
507 | const right = f(a).flatMap(unit);
508 | ----
509 |
510 | となりますが、これは `Array.flatMap` に限った構造ではなく、他のモナド実装でも同じ様相になります。もちろん、`unit` `flatMap` などの関数名は実装者の好み、さじ加減1つなので、ケースバイケースです。
511 |
512 | == まとめ
513 |
514 | モナドを知るときは、同時にモノイドのことも知っておこう。
--------------------------------------------------------------------------------
/docs/ch_04.adoc:
--------------------------------------------------------------------------------
1 | [[whatsmonad]]
2 | = モナド(Monad)とは何か?
3 | ifndef::stem[:stem: latexmath]
4 | ifndef::icons[:icons: font]
5 | ifndef::imagesdir[:imagesdir: ./img/]
6 | ifndef::source-highlighter[:source-highlighter: highlightjs]
7 | ifndef::highlightjs-theme:[:highlightjs-theme: tomorrow-night]
8 |
9 | ++++
10 |
17 | ++++
18 |
19 |
20 | > Haskellerにとっては「モナドとはすでに手元にあるもの」であり、手元あるいは、足場となる言語を活用するための学習モティベーションも極めて高い。裏を返すと、Haskellerでないその他大勢のプログラマにとっては以上の事実は逆風となる。 <>
21 |
22 | さらに裏を返すと、JavaScriptプログラマにとっては、JavaScriptですでにモナドが実装されていて、使えればそれなりの恩恵にあずかることがすぐできる、となれば、テンションもあがるんじゃないでしょうか?
23 |
24 | 「全部がモナドではないが一部は確実にモナドである」ってどういう意味?っていうのもここでわかります。
25 |
26 | 全部がモナドではないが一部は確実にモナドである、という事例として、最近のJavaScriptのlink:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array[Array]があげられます。
27 |
28 | > JavaScript の `Array` オブジェクトは、配列を構築するためのグローバルオブジェクトで、配列とは複数の要素の集合を格納管理するリスト構造です。
29 |
30 | モナドを紹介するにあたって、`Array` が優れているのは、
31 |
32 | - すでに手元にある。すぐに触れる。最新のモダンブラウザやNode.jsならすでに実装済みだ。得体のしれない誰かのモナド実装コードを解読する必要なし。
33 |
34 | - 馴染み深い。誰でも知ってる。みんな使える。基本的API。かんたん。
35 |
36 | - 見える。コンソール出力したときの値はそのまま値の構造を表している。どうなっているのか一目でわかるので理解も容易。
37 |
38 | と、まさに早い安いうまいの三拍子揃っています。
39 |
40 |
41 | まずは、`Array` のモナドではない部分を復習して、それからモナドである部分を紹介します。
42 |
43 |
44 | == Array.map
45 |
46 | https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/map[Array.map]のことは、JavaScriptプログラマなら誰でもよく知っているでしょう。
47 |
48 | 配列の構造(リスト構造)を保ったまま、値にある関数を適用した結果の値を返す、というメソッド(オブジェクトに紐付いた関数)です。
49 |
50 | `[1, 2, 3, 4, 5]`
51 | に、 +
52 | 値を2倍する関数 +
53 | `a => a * 2`
54 | を `.map` すると、 +
55 | `[2, 4, 6, 8, 10]` +
56 | が返ってきます。
57 |
58 |
59 | [source,js]
60 | .Array.map.js
61 | ----
62 | const array1 = [1, 2, 3, 4, 5];
63 |
64 | const array2 =
65 | array1
66 | .map(a => a * 2);
67 |
68 | console.log(array2);
69 | ----
70 |
71 | [source,js]
72 | .Console
73 | ----
74 | [ 2, 4, 6, 8, 10 ]
75 | ----
76 |
77 |
78 | ここでポイントは、配列の `Array.map` 前と後で
79 |
80 | 1. **構造を保ったまま、要素を1:1で転写(map)する**
81 | 2. **自分自身= `Array` オブジェクトを返してくる**
82 |
83 | ことです。
84 |
85 | このような性質のメソッドを持つオブジェクトのことを、圏論の用語では +
86 | https://ncatlab.org/nlab/show/endofunctor[endofunctor](自己関手)と呼びます。
87 |
88 | [CAUTION]
89 | .Functor ?? -- **Haskellに寄りすぎ問題**、再度勃発!
90 | ====
91 | 圏論で、link:https://ncatlab.org/nlab/show/functor[functor](関手)の定義は、任意の2つのオブジェクト間の転写(map)をするオブジェクトなので、たとえば、
92 |
93 | Array(配列)→Object(オブジェクト)とすると、
94 |
95 | `[1, 2, 3, 4, 5]` +
96 | ↓ +
97 | `{ first: 1, second: 4, third: 6, fourth: 8, fifth: 10 }`
98 |
99 | でも構わないでしょう。
100 |
101 | 転写(map)先を自分自身である `Array` に限定した特殊なfunctor(関手)がendofunctor(自己関手)となります。
102 |
103 | `Array.map` は、自分自身= `Array` オブジェクトを返してくる、というのは非常に重要な特性があるので、endofunctor(自己関手)で、そこは外せません。しかし、Haskellコミュニティの因習から、単に、functorと呼んでしまうことが多いようです。
104 |
105 | link:https://www.quora.com/Why-is-functor-in-Haskell-defined-like-the-endofunctor-from-category-theory[**Why is functor in Haskell defined like the endofunctor from category theory?**](**なぜ、Haskellのfunctorは、圏論のendofunctorみたいな定義になってるの?**)という極めてまっとうな疑問が出て、"Convenience and history." 便利さと歴史的経緯のせいだ。(中略) endofunctorというタイプクラス(型クラス・type class)名よりも、Functorという名前のほうが" much nicer name"だ。正確じゃないかもしれないが機能している・・・ +
106 | と、こういったノリです。しかし圏論用語だからとだけ聞かされている初学者にとってはそんなことは知ったことではないわけで、この混乱による質問は定期的に出るようです。
107 |
108 | ちなみに、link:http://learnyouahaskell.com/chapters[Learn You a Haskell for Great Good!](無料公開中)(有料日本語訳『すごいHaskellたのしく学ぼう!』)にも、endofuntorのことを意味しながらも単にfunctorとだけ書かれています。どうもHaskellコミュニティでは、一部の(わかってる)人達は「タイプクラスの命名のノリのことだから些細だ」という暗黙の了解としながらも、大勢はタイプクラスの名前と圏論用語がごっちゃになったまま伝播し続けているようです。実際に、**圏論のFunctorとプログラミングのFunctorでは【意味】が違う**とまで言い切っている人も見てきているので、今からでも遅くないのでちゃんと直したらどうか?と思うわけです。厳密に定義づけされた圏論用語ぽいので真に受けて聞いていたら、後で「意味が違う」とか「え?ちょっと待て」と思いますよね。いちいち言葉の定義レベルで話が通じなくなるので困るし、大事なことなのでここでちゃんと書いておきます。
109 |
110 | プログラマ界隈では、ReferenceTransparency(参照透過性)にしろ、もともとの用語の意味が完全に損なわれた不正確な意味で用語を天下り的に教えられて、その不正確な意味を知っていて当然、のようなことが横行しているので要注意。
111 | ====
112 |
113 |
114 | `Array.map` メソッドは自分自身= `Array` オブジェクトを返してくる、というendofunctor(自己関手)の特性の良さにより、メソッドチェーンが可能です。
115 |
116 | [source,js]
117 | .メソッドチェーン
118 | ----
119 | const array2 =
120 | array1
121 | .map(a => a * 2)
122 | .map(a => a + 1);
123 |
124 | console.log(array2);
125 | ----
126 |
127 | [source,js]
128 | .Console
129 | ----
130 | [ 3, 5, 7, 9, 11 ]
131 | ----
132 |
133 | `Array.map` のメソッドチェーンでは、まるでパイプラインの中を `Array` オブジェクトがずっと流れているようで、エコの統一性が保証されています。
134 |
135 | jQueryが便利だ、というのも、モナドどうこう言う以前に、ほぼほぼこのendofunctor(自己関手)がもつ関数型的特性とメソッドチェーンのメリットが大きいです。
136 |
137 |
138 | [NOTE]
139 | .入れ子構造
140 | ====
141 | ただし、構造を保ったまま、といえども、渡す関数を、
142 |
143 | `a => a * 2` +
144 | ではなく、 +
145 | `a => [a * 2]` +
146 | とすることで
147 |
148 |
149 | [source,js]
150 | .Console
151 | ----
152 | [ [ 2 ], [ 4 ], [ 6 ], [ 8 ], [ 10 ] ]
153 | ----
154 |
155 | と、各要素の階層を追加することは可能です。
156 | ====
157 |
158 |
159 | == Array.mapと関数型プログラミングの限界
160 |
161 | そんなにendofunctor(自己関手)の性質が良いのならば、モナドの立場は??モナドの意味は?何が良いの、違うの?となるわけですが、ここの差分をきっちり理解しておくことが重要です。
162 |
163 | [source,js]
164 | .メソッドチェーン
165 | ----
166 | const array2 =
167 | array1
168 | .map(a => a * 2)
169 | .map(a => a + 1);
170 | ----
171 |
172 | という一連のシークエンスを再利用可能とするために関数化します。
173 |
174 | [source,js]
175 | .f関数の定義
176 | ----
177 |
178 | const f = array =>
179 | array
180 | .map(a => a * 2)
181 | .map(a => a + 1);
182 | ----
183 |
184 | 関数を利用します。
185 |
186 |
187 | [source,js]
188 | .f関数の利用
189 | ----
190 | const array1 = [1, 2, 3, 4, 5];
191 |
192 | const array2 = f(array1); <1>
193 |
194 | console.log(array2);
195 | ----
196 |
197 | <1> `f` 関数の利用
198 |
199 | [source,js]
200 | .Console
201 | ----
202 | [ 3, 5, 7, 9, 11 ]
203 | ----
204 |
205 |
206 | 想定通りの振る舞いで何の問題もありません。
207 |
208 | ただし、これまで、`Array` 操作は、`.map` のメソッドチェーンで実現していたのに、`f(array1)` とSyntaxが変わったことが気になります。
209 |
210 |
211 | > `Array.map` のメソッドチェーンでは、まるでパイプラインの中を `Array` オブジェクトがずっと流れているようで、エコの統一性が保証されています。
212 |
213 |
214 | という観点からは。`Array.map` のメソッドチェーンを再利用するための関数 `f` を定義したはいいが、この関数を利用するときは、そのメソッドチェーン(パイプライン)の外でやっているので、本当にこの `Array` エコに合致するのか?その保証がほしいです。
215 |
216 | ひとつの方法としては、TypeScriptを使って、定義した関数の入力値/出力値の両方に `Array` の型付けをして、TypeScriptトランスパイラにチェックさせる方法があり、これは当然推奨されます。
217 |
218 | しかしそれでもなお `Array.map(f)` のメソッドチェーンから飛び出して、`f(array1)` とSyntaxが変わったエコの不整合さは解消されません。
219 |
220 | 適用したい関数 `f` が先きてかっこでくくるのが普通の関数適用、メソッドチェーンでは尻尾に `f` つけていますね。ここは結構重要で、メソッドチェーンのエレガントさは、チェーンの後に、また中間でも、追加、挿入自由自在なところにあります。
221 |
222 | たとえば、複数回連続して、`f` 適用したい場合、 +
223 | `f(f(array))` +
224 | はネストが深くなっていき、可読性も悪く「なんとか地獄」の様相なので +
225 | `Array.map(f).map(f)` +
226 | と連鎖で平らに書けたほうが良いですよね?
227 |
228 |
229 | [TIP]
230 | .ピンと来た人はご名答
231 | ====
232 | ES6+Promiseで、「コールバック地獄」から開放される、とか言ってるのも、まさにこの話に対応しています。
233 | ====
234 |
235 | `f` というのは、そもそもメソッドチェーンの再利用関数だったので、それを再度、メソッドチェーンの中で使うっていうことなので、メソッドチェーンのネスト・入れ子構造って可能なの?ってお話をしています。
236 |
237 | ネスト・入れ子構造っていうのは、関数型プログラミングのお家芸というか、自由自在になんでも組み合わせができてなんぼの関数型プログラミングです。今、関数型プログラミングの限界を試しているところです。我々はどこまで行けるのか?
238 |
239 |
240 |
241 | `Array.map` のメソッドチェーンでいけるかどうか?ダメ元で試してみましょうか。
242 |
243 | [[challenge]]
244 | [source,js]
245 | .f関数の利用@map ダメ元
246 | ----
247 | const array1 = [1, 2, 3, 4, 5];
248 |
249 | const array2 =
250 | array1.map(f); <1>
251 |
252 | console.log(array2);
253 | ----
254 |
255 | <1> `Array.map(f)` のダメ元チャレンジ
256 |
257 | [source,shell]
258 | .Console
259 | ----
260 | TypeError: array.map is not a function
261 | ----
262 |
263 | TypeError つまり型が合いませんでした。
264 |
265 | じゃあ、`.map` 元がとりあえず `Array` にだけなるよう `[]` でくくって再チャレンジ。
266 |
267 | [source,js]
268 | .f関数の利用@map 再チャレンジ
269 | ----
270 | const array1 = [1, 2, 3, 4, 5];
271 |
272 | const array2 =
273 | [array1].map(f); <1>
274 |
275 | console.log(array2);
276 | ----
277 |
278 | <1> `[]` でくくって `[array1]` とする
279 |
280 | [source,shell]
281 | .Console
282 | ----
283 | [ [ 3, 5, 7, 9, 11 ] ]
284 | ----
285 |
286 | いちおう通って `Array` が出てきました!しかし、残念ながら期待していた `[ 3, 5, 7, 9, 11 ]` とはならず、ネストした二重の `Array` になってしまっています。
287 |
288 | もうにっちもさっちもいかないので、ここが `Array.map` の関数型プログラミングでの限界です。
289 |
290 | `Array.map` は、自分自身= `Array` を返すというendofunctor(自己関手)の特性があり、メソッドチェーンが出来るのだが、**メソッドチェーンが入れ子構造になると、自身の構造をコントロールできなくなる** のです。
291 |
292 | 関数型プログラミングにとって、これは結構な大問題だとは思いませんか?
293 |
294 | == Array.flat の登場
295 |
296 | ネストした二重の `Array` を 平坦化するには、その機能をもった `Array` メソッドが必要になってきます。
297 |
298 | モダンブラウザでは、Chrome69/Firefox62などメジャーどころは、ごく最近、2018年9月に入って立て続けに、
299 | https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flat[Array.flat]を実装しました。
300 |
301 | image::./flat-browser.png[]
302 |
303 | Node.jsの最新版でも実装されています。正確なNodeバージョンまでは調査していない。以前までは、これ使いたくても、Polyfillなど使って自前でなんとか拡張する必要があって面倒だったのですが、未だ実験的実装とはいえ歓迎すべきことです。
304 |
305 | `Array.flat` メソッドは、その名の通り、ネストした配列構造をフラット化します。
306 |
307 | [source,js]
308 | .ネストした配列のフラット化
309 | ----
310 | const arr1 = [1, 2, [3, 4]];
311 | arr1.flat();
312 | // [1, 2, 3, 4]
313 |
314 | const arr2 = [1, 2, [3, 4, [5, 6]]];
315 | arr2.flat();
316 | // [1, 2, 3, 4, [5, 6]]
317 | ----
318 |
319 | パラメータを指定することで、フラット化するネストの階層を指定できますが、デフォルトでは `1` で、1階層だけフラット化します。それ以上再帰的に追求しません。そして、この1階層だけフラット化するというデフォルトの挙動が本トピックでは適切な振る舞いなので、そのままにしておきましょう。
320 |
321 | == unit の定義
322 |
323 | JavaScriptは、裸の値を `Array` にしたり、すでにある配列・要素をさらにネストしたいとは、各々の値を `[]` でくくればよいだけなので直感的で良いですが、これはれっきとした、値の変換なので、今後のためにちゃんと関数としておきましょう。
324 |
325 | `unit = a => [a]` と定義しておきます。
326 |
327 | [source,js]
328 | .unit(a) = [a]
329 | ----
330 | const unit = a => [a];
331 |
332 | console.log(
333 | unit(7)
334 | );
335 |
336 | console.log(
337 | unit([7])
338 | );
339 | ----
340 |
341 | [source,js]
342 | .Console
343 | ----
344 | [ 7 ]
345 | [ [ 7 ] ]
346 | ----
347 |
348 | 特に問題ないですね?
349 |
350 | == unit と Array.flat の対称性
351 |
352 | なんでわざわざ `unit` を定義したのか?というと、以下の話をしたいからです。
353 |
354 |
355 | `unit` と `flat` を図式化するとこうなります。
356 |
357 | image::./unitflat1.svg[align="center"]
358 |
359 | **どちらも、関数の出力値は、`Array` 一択** です。ここ重要。
360 |
361 | まあ、対称性があるように見えて単純で美しい構造だと思うのですが、これは何気に奥深くて、まるで論理クイズみたいな様相を呈します。
362 |
363 | - `unit` と `flat` を眺めると、どうも双方は明らかな対称性があるようだ。
364 | - 双方の関数の出力値は、`Array` 一択という強い縛りが効いている。
365 | - ならば、双方は対称にはなりえない。
366 |
367 | 意味わかります?
368 |
369 | この界隈では、「コンテナに入れる」「箱に入れる」「箱から出す」「ラップする」「一枚皮を剥く」「カラに入れる」「カラから出す」はたまた「純粋にする」とか「リフト(アップ)」するとかいろんな言い草がありますが、ここでは単純に「階層」の上下関係で上げる、下げると言いましょう。
370 |
371 | ここでの絶対的ルールは以下の2つだけです。
372 |
373 | 1. `unit` は1階層だけ上げる。(さっき実際そのとおり定義した)
374 |
375 | 2. `flat` は__ネストしていれば__1階層だけ下げる。
376 |
377 | ルール2で `flat` の__ネストしていれば__と、しれっと条件分岐をしている部分が、無条件に1階層上げるという `unit` と非対称です。
378 |
379 | たとえば、
380 |
381 | [source,js]
382 | .Array.flat
383 | ----
384 | console.log(
385 | [[7]].flat() <1>
386 | );
387 |
388 | console.log(
389 | [7].flat() <2>
390 | );
391 | ----
392 |
393 | <1> ネストしてる
394 | <2> ネストしてない
395 |
396 | [source,js]
397 | .Console
398 | ----
399 | [ 7 ] <1>
400 | [ 7 ] <2>
401 | ----
402 | <1> ネストしてたので1階層下げた `Array`
403 | <2> ネストしてなかったので、そのままの `Array`
404 |
405 |
406 | `Array.flat` は、もし `Array` がネストしてたら、1階層下げて `Array` を返しますが、ネストしていなかったらそのままの `Array` を返します。最後の配列の皮を剥いで、裸の値 `7` を返すようなことはありません。
407 |
408 | つまり、`Array.flat` の返り値は必ず `Array` タイプである、中の値を裸では提供はしません、という基底が保証されています。
409 |
410 | `Array.map` はendofunctorで、返り値は必ず `Array` タイプである、という例のメソッドチェーンのエコの部品としてドハマりしますよね?
411 |
412 | `Array.flat` の仕様あるいは、`flat` という共通概念の特性は、 <>チャレンジの結果、裸の値に `.map` してしまいタイプエラーが出るような不整合を未然に防止してくれそうです。
413 |
414 | `flat` しても基底で止まるように条件分岐でしっかり保証!されたところで、あとは、`unit` と `flat` の上下移動の対称性をもって、どの階層にも自由に移動させながら、`Array.map` メソッドが使えるようになる・・・はずです。
415 |
416 | image::./unitflat5.svg[align="center"]
417 |
418 | こうしてみると、`unit` と `flat` は概ね対称的ペアだけど非対称だ、というのがよりはっきりわかると思います。
419 |
420 | また、エコが破綻する裸の値はまずいですが、ネストした構造が別に悪いわけではありません。ネストした `Array` を扱いたいのならば、そのネスト構造を扱うことも含め自由にコントロールしながら、`Array.map` することができる・・・はずです。
421 |
422 | 要するに、`Array.map` こいつ単独ではどうも役不足だ。特にメソッドチェーンでネストしたら途端に構造が破綻するので扱いづらくてかなわん・・・ここはひとつ、構造に直接アプローチできる、対になった `unit` と `flat` ペアを導入してやって、なおかつ、`flat` が裸の値を返さないような安全装置つきなら、言うことないだろう・・・そういう理屈(皮算用)が今進行しているわけです。
423 |
424 | ああ、紹介が遅れましたが、今話しているこれがモナドです。
425 |
426 | 世の常として結果論ですが、結果的にこの理屈はうまく機能します。
427 |
428 | じゃあ実際どうやって上手く機能するんだ?ってことになるわけですが、ポイントは、モナドっていうのは、関数型プログラマコミュニティ(Haskell)がもてはやす前から、圏論で定義される数学的構造として存在していて、それをどうやって上手く使うのか?っていうのは、また別の話なんですね。だから、特にモナドの紹介をするときにIOだのピュアだの言うのは、完全にお門違いです。
429 |
430 | Arrayが自身の構造にアプローチできるモナドになった結果、実際いかに便利になりうるか?というのは、次の章から説明します。
431 |
432 |
433 |
434 | == モナド(Monad)
435 |
436 | なんのことはない、`Array` で言えば、普通の `Array.map` に `Array.flat` を付け加えたものがモナドになります。 `unit` というのは、`[]` なので最初からあるといえばありました。
437 |
438 | 自身の構造をコントロールしながらマップするためには、
439 |
440 | 1. 自分自身のオブジェクト `Array` を返す `Array.map` がベースとしてある endofunctor (`Array` オブジェクト)
441 | 2. 1階層上げる `unit`
442 | 3. (もしネストしていたら)1階層下げる `Array.flat`
443 |
444 | この **3点全部そろったら** `Array` は、Arrayモナド(Monad)になります。
445 |
446 | 念の為に読者へ保証しておきますが、これは、圏論でちゃんと定義づけされているモナド(Monad)のことです。プログラミングのモナドで定義が異なる、という例のトリッキーなアレではありません。
447 |
448 | [NOTE]
449 | .圏論(category theory)用語の紹介
450 | ====
451 | 英語版Wikipediaなどでは、
452 |
453 | .Monad (category theory)
454 |
455 | https://en.wikipedia.org/wiki/Monad_(category_theory)
456 |
457 | > In category theory, a branch of mathematics, a monad is an endofunctor (a functor mapping a category to itself), together with two natural transformations.
458 |
459 | ---
460 |
461 | 圏論(category theory)では、モナド(monad)とは、自己関手(endofunctor=カテゴリを自身に転写するfunctor)で、2つの自然変換(natural transformations)を伴っている。
462 |
463 | ---
464 |
465 | などと書かれていますが、圏論用語@日本語では、
466 |
467 | stem:[C] 上のモナドとは、
468 |
469 | - 自己函手 stem:[T: C \rightarrow C]
470 | - 自然変換 stem:[\eta: Id_C \Rightarrow T]
471 | - 自然変換 stem:[\mu: T \circ T \Rightarrow T]
472 |
473 | からなる3つ組 stem:[\langle T, \eta, \mu \rangle]
474 |
475 | などと表記されることが多いです。
476 |
477 | 逐一、英語 category theroryのことを圏論、endofunctorのことを自己関手、natural transformationを自然変換と和訳してしまった結果、原語以上に難解さを醸し出す効果を持っており、なおかつギリシャ文字が出てきておっと思うわけですが、どう表記されようが、`Array` モナドについて言えば、
478 |
479 | - 自己関手(endofunctor)は、皆よく知ってる 自分自身(オブジェクト)を返してくる `Array.map` メソッドをもつ `Array` オブジェクトのこと
480 |
481 | - stem:[\eta](イータと読む)は、`↑` に似ていて、 `Array` を返す `unit` のこと
482 |
483 | - stem:[\mu](ミューと読む)は、`↓` に似ていて、 `Array` を返す `flat` のこと
484 |
485 | に過ぎないし、それを念頭に式を眺めれば、このstem:[T]ってのは `Array` に対応していて、各関数との関係性が正確に記述されてるなあ、と普通にとわかるはずです。たしかに、
486 |
487 | stem:[C] 上のモナドとは、とか言ってて、
488 |
489 | [stem]
490 | ++++
491 | T: C \rightarrow C
492 | ++++
493 |
494 |
495 | と stem:[T] ( `Array.map` つきの `Array` ) が定義されているのが、気になりますが、この stem:[C] は、Category(圏)の頭文字で、ざっくり領域の限定をしています。
496 |
497 | ここでなんの領域を限定しているのか?というと、今定義してるモナドの範囲限定していて、今の `Arrayモナド` が乗っかってる stem:[C]の範囲とは、**「JavaScriptの値の全体」** ということに他なりません。だから、** stem:[C]は、JavaScript Category(圏)**とでもなんとでも言えば良いんじゃないでしょうか?
498 |
499 | **「JavaScriptの値の全体」**とはもっと正確にいうと、`1` やら `"Hello"` などのすべてのPrimitiveValueさらに、`Object.prototype` から派生するすべてのオブジェクトのことで、これにたしかに今定義している `Array` オブジェクトのモナドも乗っかってますよね?
500 |
501 | `Array` モナドはendofunctorである stem:[T]で、`Array.map` メソッドにより、stem:[C \rightarrow C]と任意のJavaScriptの値を別のJavaScriptの値へマップします。
502 |
503 | 次に `unit` ですが、
504 |
505 | [stem]
506 | ++++
507 | \eta: Id_C \Rightarrow T
508 | ++++
509 |
510 | stem:[C]上にあるなんかの値(裸の値、それからArray自身も含む)を、stem:[T]( `Array` )に上げて返すという `unit` の定義になっている。
511 |
512 |
513 | 最後に、`flat` ですが、
514 |
515 | [stem]
516 | ++++
517 | \mu: T \circ T \Rightarrow T
518 | ++++
519 |
520 | stem:[T]がもし二重にネストしてたら 一つ階層を下げて stem:[T] にして返すという、条件分岐つきの性質を端的に定義しています。
521 |
522 | `flat` (stem:[\mu])は、stem:[TTX \rightarrow TX] などと表記されることも多く、いちいち各流派の方言につきあうのは大変なんですが、ああ `flat` のことを言ってるんだなあ、と思っておけばいいです。
523 |
524 |
525 | ====
526 |
527 | [[conclusion]]
528 | == まとめ
529 |
530 | 圏論のモナド(monad)の定義をまとめると
531 |
532 | 1. ベースとして、オブジェクト自身を返す `map` メソッドを持つendofunctorとしての特性をもつオブジェクトで、さらに以下の2つの関数(メソッド)がある
533 | 2. `unit`
534 | 3. `flat`
535 |
536 | この3つ組(トリプル)
537 |
538 | [stem]
539 | ++++
540 | (endofunctor, unit, flat)
541 | ++++
542 |
543 | をモナドと呼びます。
544 |
545 | 3つ組(トリプル)、オブジェクト、関数、メソッドという言葉遣い、きちんとした意味、さらに、bicategoryのことなどは、6章 <> 以降で詳しく解説します。
546 |
547 |
--------------------------------------------------------------------------------
/docs/ch_05.adoc:
--------------------------------------------------------------------------------
1 | [[howto]]
2 | = リストモナド(List Monad)のつかいかた
3 | ifndef::stem[:stem: latexmath]
4 | ifndef::icons[:icons: font]
5 | ifndef::imagesdir[:imagesdir: ./img/]
6 | ifndef::source-highlighter[:source-highlighter: highlightjs]
7 | ifndef::highlightjs-theme:[:highlightjs-theme: tomorrow-night]
8 |
9 | ++++
10 |
17 | ++++
18 |
19 |
20 |
21 | モナドの超基本的概念とそれに伴う定義付けは終わったので、あとはそれをどう使うか?です。
22 |
23 | `Array.map` に `Array.flat` 追加してモナドになった `Array` は、特に **リストモナド(List Monad)**と呼ばれます。
24 |
25 | 復習しておくと、そもそも、わざわざArrayをモナドにした動機とは、endofunctorだけだと、
26 |
27 | > もうにっちもさっちもいかないので、ここが `Array.map` の関数型プログラミングでの限界です。`Array.map` は、自分自身= `Array` を返すというEndofunctor(自己関手)の特性があり、メソッドチェーンが出来るのだが、**メソッドチェーンが入れ子構造になると、自身の構造をコントロールできなくなる** のです。
28 |
29 | という、限界突破の目的でした。構造をコントロールできるようになりたい。
30 |
31 | あと、メソッドチェーンを入れ子構造になっても統一的に扱いたいという話の流れで、仮に構造コントロールできるようになったとして、そのトレードオフとして別のなにかが出来なくなると、同じ局面でendofunctorとモナドの使い分けが必要ということになってしまいます。こうなるとまた収集がつかなくなるのは目に見えているので、トレードオフは受け入れられません。
32 |
33 | **モナドは、endofunctorの完全な上位互換であってくれないと使い物にはならない、ということです。上位互換を目指します。**
34 |
35 | endofunctorとの互換性を担保するだけならば、理屈は簡単で、 `unit` と `flat` が対称なので、行って来いで、効果を相殺すれば済むことです。その上、`flat` はモナドオブジェクト自身の基底にヒットすると、それ以上階層を下げて裸の値を返すことはない安心保証の性質があるので、それだけでも上位互換となるはずです。
36 |
37 |
38 | image::./unitflat5.svg[align="center"]
39 |
40 |
41 | 実際にこの理論で上手く行くのか?やってみなきゃわからない。やってみよう。
42 |
43 | まず、たたき台となる、普通の `Array.map` だけのパターン
44 | [source,js]
45 | .Array.map
46 | ----
47 | const array1 = [1, 2, 3, 4, 5];
48 |
49 | const array2 = array1
50 | .map(a => a * 2);
51 |
52 | console.log(array2);
53 | ----
54 |
55 | [source,js]
56 | .Console
57 | ----
58 | [ 2, 4, 6, 8, 10 ]
59 | ----
60 |
61 | 想定どおりの挙動です。上位互換となるモナドでも、まったく同じことが出来なければいけません。
62 |
63 | [source,js]
64 | .Array.map + flat
65 | ----
66 | const unit = a => [a];
67 |
68 | const array1 = [1, 2, 3, 4, 5];
69 |
70 | const array2 = array1
71 | .map(a => unit(a * 2)) <1>
72 | .flat(); <2>
73 |
74 | console.log(array2);
75 | ----
76 |
77 | <1> `a => a * 2` の代わりに、`a => unit(a * 2)`
78 |
79 | <2> `flat` で相殺
80 |
81 | [source,js]
82 | .Console
83 | ----
84 | [ 2, 4, 6, 8, 10 ]
85 | ----
86 |
87 | できました。想定した挙動になっています。
88 |
89 | さて、重要なポイントとして、`unit` と `flat` で相殺するには単純に考えると複数のパターンが考えられるはずですが、順番として、なぜ、 `unit` ⇒ `flat` となっているのでしょうか?
90 |
91 | 理由:
92 |
93 | 1. 最後に `flat` することで、裸の値でないモナドオブジェクトを返すことを保証できる
94 |
95 |
96 | 2. あらじめ `unit` で構造を自由に指定した上で、`flat` できる
97 |
98 |
99 | 1,2により、相殺して互換性を保つ以上の上位機能が得られます。
100 | 1についてはこれ以上説明は不要でしょうが、2については今から説明していきます。
101 |
102 | [TIP]
103 | .unit(a) = [a]
104 | ====
105 |
106 | `unit(a)`
107 |
108 | `[a]`
109 |
110 | 同じ意味ですが、あきらかに可読性と構造の直感的把握がしやすいのは、 `[a]` です。
111 |
112 | 特にネストした構造になると、
113 |
114 | `[ [a] ]` は、 `unit(unit(a))` など煩雑になります。
115 |
116 | すでに、`unit` と `flat` の対称性のことは理解出来たと思うので、、特に必要のない限り、`unit(a)` のことは、単純に `[a]` と表記することにします。
117 |
118 | ====
119 |
120 |
121 |
122 |
123 | メソッドチェーンではどうでしょうか?
124 |
125 | まず、たたき台となる、普通の `Array.map` だけのパターン
126 | [source,js]
127 | .Array.map chain
128 | ----
129 | const array1 = [1, 2, 3, 4, 5];
130 |
131 | const array2 = array1
132 | .map(a => a * 2)
133 | .map(a => a + 1);
134 |
135 | console.log(array2);
136 | ----
137 |
138 | [source,js]
139 | .Console
140 | ----
141 | [ 3, 5, 7, 9, 11 ]
142 | ----
143 |
144 | 想定どおりの挙動です。上位互換となるモナドでも、まったく同じことが出来なければいけません。
145 |
146 | [source,js]
147 | .Array.map + flat chain
148 | ----
149 | const array1 = [1, 2, 3, 4, 5];
150 |
151 | const array2 = array1
152 | .map(a => [a * 2]).flat()
153 | .map(a => [a + 1]).flat();
154 |
155 | console.log(array2);
156 | ----
157 |
158 | [source,js]
159 | .Console
160 | ----
161 | [ 3, 5, 7, 9, 11 ]
162 | ----
163 |
164 | 問題なく出来ました。
165 |
166 | == リストモナドでリスト構造をコントロールする
167 |
168 | [source,js]
169 | .要素の数を増やす
170 | ----
171 | const array1 = [1, 2, 3, 4, 5];
172 |
173 | const array2 = array1
174 | .map(a => [a, a]) <1>
175 | .flat(); <2>
176 |
177 | console.log(array2);
178 |
179 | ----
180 |
181 | <1> `a => [a, a]` 返り値としてリスト構造を規定する
182 |
183 | <2> `[ [ 1, 1 ], [ 2, 2 ], [ 3, 3 ], [ 4, 4 ], [ 5, 5 ] ]` を `flat`
184 |
185 | [source,js]
186 | .Console
187 | ----
188 | [ 1, 1, 2, 2, 3, 3, 4, 4, 5, 5 ]
189 | ----
190 |
191 | [source,js]
192 | .要素の数を増やしたいわけじゃない、`[ [ 1, 1 ], [ 2, 2 ], [ 3, 3 ], [ 4, 4 ], [ 5, 5 ] ]` という構造が欲しいので .mapと同じ結果を寄越せ
193 | ----
194 | const array1 = [1, 2, 3, 4, 5];
195 |
196 | const array2 = array1
197 | .map(a => [[a, a]]) <1>
198 | .flat(); <2>
199 |
200 | console.log(array2);
201 | ----
202 |
203 | <1> `a => [ [a, a] ]` 返り値としてリスト構造を規定する
204 |
205 | <2> `[ [ [ 1, 1 ] ],
206 | [ [ 2, 2 ] ],
207 | [ [ 3, 3 ] ],
208 | [ [ 4, 4 ] ],
209 | [ [ 5, 5 ] ] ]` を `flat`
210 |
211 | [source,js]
212 | .Console
213 | ----
214 | [ [ 1, 1 ], [ 2, 2 ], [ 3, 3 ], [ 4, 4 ], [ 5, 5 ] ]
215 | ----
216 |
217 |
218 |
219 | [source,js]
220 | .要素の数を減らす 奇数のみピックアップ
221 | ----
222 | const array1 = [1, 2, 3, 4, 5];
223 |
224 | const array2 = array1
225 | .map(a =>
226 | a % 2 === 1 <1>
227 | ? [a] <2>
228 | : [] <3>
229 | ).flat(); <4>
230 |
231 | console.log(array2);
232 | ----
233 |
234 | <1> 配列要素 `a` を `2` で割って余りが `1` なら奇数
235 | <2> 奇数なら、そのままの構造 `[a]` で返す
236 | <3> 奇数でなかったら、構造を削除したいので、`[]` を返す
237 | <4> `[ [1], [], [3], [], [5] ]` を `flat` して `[ 1, 3, 5 ]`
238 |
239 | [source,js]
240 | .Console
241 | ----
242 | [ 1, 3, 5 ]
243 | ----
244 |
245 |
246 |
247 | == Array.flatMapの登場
248 |
249 | `Array.map(f).flat()` となるモナドメソッドはendofunctorの上位互換として機能することが確認出来ました。もうこの確定したパターンでは、逐一尻尾に `.flat()` くっつけて回るのは、付け忘れる可能性だってある、スマートではないし、見通しも悪く、バグの温床にもなりかねません。
250 |
251 | そこで、もうこの2つの関数を合成してしまって、ひとつの関数として使い回せたほうが便利ですね。それが関数型プログラミングです。
252 |
253 | もちろん合成された関数が `Array` のメソッドとして実装されていないとまた自前でプロトタイプ拡張とかする羽目になって面倒ですが・・・
254 |
255 | ということで、あります。
256 |
257 | https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap[Array.flatMap]
258 |
259 | > flatMap() メソッドは、最初にマッピング関数を使用してそれぞれの要素をマップした後、結果を新しい配列内にフラット化します。これは深さ 1 の flatten が続く map と同じですが、flatMap はしばしば有用であり、2 つのメソッドを 1 つにマージするよりもやや効果的です。
260 |
261 | `Array.flatMap` は 最終的に `Array.flat` する `Array.map` という合成関数です。
262 |
263 | `Array.flatMap` はもちろんモナドのメソッドです。
264 |
265 | `Array` 以外のモナドで、既存のものにせよ、自前で何か実装するにせよ、endofunctor の `map` に `flat` 合成するというパターンはもう決まりきっているので、多くのモナドの実装では、`flat` は独立した関数として分離しておらず、`flat` は、オブジェクト構造の平坦化 stem:[TTX \rightarrow TX] という機能として、 `flatMap` メソッド(概念として。名前は自由。)のコードに組み入れられて渾然一体となっているケースが多いと思います。
266 |
267 |
268 |
269 |
270 | image::./flat-browser.png[]
271 |
272 | よく見ると、`Array.flat` の実装状況と同じで、`Array.flat` と `Array.flatMap` はふたつセットで各ブラウザへ実装されたっぽいことが推察されます。
273 |
274 | Array.map+ flat chain のコードは `Array.flatMap` を使って書き換えられます。
275 |
276 | [source,js]
277 | .Array.flatMap chain
278 | ----
279 | const array1 = [1, 2, 3, 4, 5];
280 |
281 | const array2 = array1
282 | .flatMap(a => [a * 2])
283 | .flatMap(a => [a + 1]);
284 |
285 | console.log(array2);
286 | ----
287 |
288 | [source,js]
289 | .Console
290 | ----
291 | [ 3, 5, 7, 9, 11 ]
292 | ----
293 |
294 | == Array.flatMapとモナド関数
295 |
296 | `Array.flatMap` メソッドの成り立ち、仕組みについて、我々はすでに熟知しているはずなので、あとはどう使いこなすか?です。
297 |
298 | APIの仕様の天下りではなくて、数学的な特性から自然と振る舞いはわかるはずだし、使い方も見えてくるはずです。
299 |
300 | まずベースは、`Array.map` でこの機能は含まれています。
301 | 次に、`Array.flat` を合成したので、この機能も含まれています。これにより、要素の増減がコントロールできるようになりました。
302 |
303 | さらに `Array.flat` は、空集合(配列)の `[]` は要素を削除してしまうので、場合分けすることで、`Array.filter` の機能もあります。
304 |
305 | `Array.flatMap` メソッドをうまく使いこなすことさえできれば、`Array.map` `Array.flat` `Array.filter` が不要になるばかりでなく、これ1つで、なんでもできて、統一的な視点が手に入るはずで、えーっとたしか `Array.filter` っていうAPIがあったな、どういう仕様だったかな?・・・とか、この要素を削除したいがどうすればわからない、とか、ここの `[]` 取ってフラットにしたいんだけど、どのAPI使えばいいのかな?とかGoogle検索しながら頭を悩ませる労力から開放される・・・はずです。
306 |
307 | `Array.flatMap` メソッドの挙動を司るのが、引数として渡す関数です。したがって、`Array.flatMap` メソッドを使いこなすとは、この関数を使いこなすことに他なりません。
308 |
309 | この関数のことを、理由は後で補足するとして、複数の理由から **モナド関数(monadic functions)**と呼ぶことにしましょう。とりあえずひとつの理由は、モナドメソッドである `Array.flatMap` の挙動を司るからです。
310 |
311 | モナド関数だけ設計すれば、なんでもできる。 モナド関数だけ見れば、何やってるのかわかる。そうなるはずなので、ここではモナド関数を研究する必要があるでしょう。
312 |
313 | == モナド関数の動作確認
314 |
315 | まず基本的な動作確認をします。
316 |
317 | ---
318 |
319 | - `Array.map` の互換 同じ階層にマップする
320 |
321 | [source,js]
322 | .Array.flatMap
323 | ----
324 | const array1 = [1, 2, 3, 4, 5];
325 |
326 | const array2 = array1
327 | .flatMap(a => [a * 2]); <1>
328 |
329 | console.log(array2);
330 | ----
331 |
332 | <1> `a => [a * 2]` モナド関数 `flat` と相殺するために返り値に `[]` をつけてモナド関数の中では階層をひとつ上げている
333 |
334 | [source,js]
335 | .Console
336 | ----
337 | [ 2, 4, 6, 8, 10 ]
338 | ----
339 |
340 | ---
341 |
342 | - `Array.map` の互換 階層をひとつ上げる
343 |
344 | [source,js]
345 | .Array.flatMap
346 | ----
347 | const array1 = [1, 2, 3, 4, 5];
348 |
349 | const array2 = array1
350 | .flatMap(a => [[a * 2]]); <1>
351 |
352 | console.log(array2);
353 | ----
354 |
355 | <1> `a => [[a * 2]]` モナド関数 階層を1つ上げたいときは、`[]` を二重にする
356 |
357 | [source,js]
358 | .Console
359 | ----
360 | [ [ 2 ], [ 4 ], [ 6 ], [ 8 ], [ 10 ] ]
361 | ----
362 |
363 | ---
364 |
365 | - `Array.map` にない機能 階層をひとつ下げる
366 |
367 | [source,js]
368 | .Array.flatMap
369 | ----
370 | const array1 = [[1], [2], [3], [4], [5]];
371 |
372 | const array2 = array1
373 | .flatMap(a => a) <1>
374 | .flatMap(a => [a * 2]); <2>
375 |
376 | console.log(array2);
377 | ----
378 |
379 | <1> `a => a` モナド関数 階層を1つ下げたいときは、`[]` なしのままで
380 | <2> `a => [a * 2]` モナド関数
381 |
382 | [source,js]
383 | .Console
384 | ----
385 | [ 2, 4, 6, 8, 10 ]
386 | ----
387 |
388 | [TIP]
389 |
390 | ====
391 | ES6以降のlink:https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment[分割代入 (Destructuring assignment)]を利用して、
392 |
393 | `.flatMap(([a]) => [a * 2])`
394 |
395 | とする手法もありえますが、煩雑に見えるし、手法に統一性がないので、ここでは採用しません。
396 |
397 | また、分割代入を利用すれば、`Array.map` でも階層下げは可能ですが、それはあくまで分割代入によって階層が下げられているだけで、`Array.map` の機能ではありません。
398 |
399 |
400 | ====
401 | ---
402 |
403 | - `Array.map` にない機能 要素を増やす
404 |
405 | [source,js]
406 | .Array.flatMap
407 | ----
408 | const array1 = [1, 2, 3, 4, 5];
409 |
410 | const array2 = array1
411 | .flatMap(a => [a, a * 2]); <1>
412 |
413 | console.log(array2);
414 | ----
415 |
416 | <1> `a => [a, a * 2]` モナド関数 要素を増やすときは、`[]` 内で要素を増やす
417 |
418 | [source,js]
419 | .Console
420 | ----
421 | [ 1, 2, 2, 4, 3, 6, 4, 8, 5, 10 ]
422 | ----
423 |
424 |
425 | ---
426 |
427 | - `Array.map` にない機能 要素を削除
428 |
429 | [source,js]
430 | .Array.flatMap
431 | ----
432 | const array1 = [1, 2, 3, 4, 5];
433 |
434 | const array2 = array1
435 | .flatMap(a => []); <1>
436 |
437 | console.log(array2);
438 | ----
439 |
440 | <1> `a => []` モナド関数 要素を削除するときは、`[]` 空配列を返す
441 |
442 | [source,js]
443 | .Console
444 | ----
445 | [ ]
446 | ----
447 |
448 | == モナド関数は必ずモナドを返す
449 |
450 | 以上のモナド関数の動作確認から、モナド関数は必ずモナドを返している、ということがわかります。
451 |
452 | > 階層を1つ下げたいときは、モナド関数の返り値は、`[]` なしのままで
453 |
454 |
455 | `a => a` だった!?
456 |
457 | と思うかもしれませんが、 元の操作対象となる `Array`(モナド)は +
458 | `[ [1], [2], [3], [4], [5] ]` でこのときの入力値 `a` はその `Array` の各要素で、たとえば `[1]` という `Array` なので、返り値となる `a` も同様に `Array`(モナド)です。
459 |
460 | モナド関数は必ずモナドを返すというのが、モナド関数と呼ぶもう一つの理由です。
461 |
462 | 裏を返せば、モナド関数は、モナドさえ返せばなんであっても構わないでしょう。
463 |
464 | [TIP]
465 | .モナドの構成要素となっている関数はすべてモナドを返す
466 | ====
467 | - `unit`
468 | - `Array.flatMap`
469 | - モナド関数
470 |
471 | モナドの構成要素となっているこれら3つの関数は、3つとも必ずモナドを返す関数であることに注目してください。
472 | ====
473 |
474 |
475 | [NOTE]
476 | .Array.flatMapは必ずしもモナド関数を要請しない
477 | ====
478 | `Array.flatMap` メソッドのモナドとしてのポテンシャルを最大限引き出すためには、モナド関数でコントロールする統一的視点で利用するのは言うまでもありませんが、`Array.flatMap` は必ずしもモナド関数を要請しません。
479 |
480 | たとえば、
481 |
482 | - `Array.map` の互換 同じ階層にマップする
483 |
484 | [source,js]
485 | .Array.flatMap
486 | ----
487 | const array1 = [1, 2, 3, 4, 5];
488 |
489 | const array2 = array1
490 | .flatMap(a => a * 2); <1>
491 |
492 | console.log(array2);
493 | ----
494 |
495 | <1> `a => [a * 2]` モナド関数 .... ではなく、あえて非モナド関数である `a => a * 2` を使う
496 |
497 | [source,js]
498 | .Console
499 | ----
500 | [ 2, 4, 6, 8, 10 ]
501 | ----
502 |
503 | タイプエラーが出ることもなく、`Array.map` と同じ結果が出てきました。なぜでしょうか?
504 |
505 | `a => a * 2` というのは、操作対象が、「ネストしているモナドならば」階層1つ下げたモナド値を返す、というモナド関数となりえますが、今のケースのように操作対象がネストしていないモナドの場合は裸の値を返してしまっているわけでモナド関数ではありません。しかしそれでもタイプエラーが出ないのは、`flat` の仕様:「モナドオブジェクト基底より下げて裸の値は返さない」という安全装置により、変化せずに、そのままのモナド値が返ってきたのです。
506 |
507 | 言い換えると、`Array.flatMap` で単階層限定のモナドを扱いたい場合、意図的に `Array.map` とまったく同じ非モナド関数で `Array.map` と互換性のある運用は可能です。メソッドチェーンの組み換え等その他モナドのアドバンテージもそのまま残るはずですが、あくまで統一的なモナド関数によるモナドのコントロール、というパラダイムからは外れているので、そこは好みでしょう。
508 |
509 |
510 | ====
511 |
512 | [[monadfunction]]
513 | == モナド関数を設計する
514 |
515 | モナド関数は、モナドを返せばなんでも自由だという事が判明したので、モナド関数を自由に設計してみます。
516 |
517 | まず手始めに、もっとも単純な、何もせずに自分自身を返すモナド関数を作ります。
518 |
519 | そして、だいたいモナド関数の感じもつかめてきたので、`Array` に限定しないモナドでも通用しやすい `unit` 表記に戻してやります。
520 |
521 | [source,js]
522 | .モナド関数 identitiy
523 | ----
524 | const unit = a => [a]; <1>
525 |
526 | const identity = a => unit(a); <2>
527 |
528 | const array1 = [1, 2, 3, 4, 5];
529 | const array2 = array1
530 | .flatMap(identity); <3>
531 |
532 | console.log(array2);
533 | ----
534 |
535 | <1> `unit` の定義 `Array` モナドで1階層上げる
536 | <2> `a => unit(a)` というモナド関数 `a => [a]` と等価 自分自身を変化させずに返す
537 | <3> `identity` モナド関数で `flatMap`
538 |
539 | [source,js]
540 | .Console
541 | ----
542 | [ 1, 2, 3, 4, 5 ]
543 | ----
544 |
545 | ---
546 |
547 | [source,js]
548 | .モナド関数 plus9
549 | ----
550 | const plus9 = a => unit(a + 9); <1>
551 |
552 | const array1 = [1, 2, 3, 4, 5];
553 | const array2 = array1
554 | .flatMap(plus9); <2>
555 |
556 | console.log(array2);
557 | ----
558 |
559 | <1> 9を足すモナド関数 `plus9` `a => unit(a + 9)`
560 | <2> `plus9` モナド関数で `flatMap`
561 |
562 | [source,js]
563 | .Console
564 | ----
565 | [ 10, 11, 12, 13, 14 ]
566 | ----
567 |
568 | ---
569 |
570 | [source,js]
571 | .モナド関数 oddFilter
572 | ----
573 | const oddFilter = a => <1>
574 | a % 2 === 1 <2>
575 | ? unit(a) <3>
576 | : []; <4>
577 |
578 | const array1 = [1, 2, 3, 4, 5];
579 | const array2 = array1
580 | .flatMap(oddFilter); <5>
581 |
582 | console.log(array2);
583 | ----
584 |
585 | <1> モナド `a` が奇数ならそのまま返し、奇数でなければ、空のモナド `[]` を返す `oddFilter` というモナド関数
586 | <2> `2` で割って余りが `1` ならば
587 | <3> 奇数なので、`unit(a)` つまり、要素 `a` 自身 をモナド値として返す
588 | <4> 奇数でないので、`[]` 空のモナド値を返し、要素 `a` を削除
589 | <5> `oddFilter` モナド関数で `flatMap`
590 |
591 | [source,js]
592 | .Console
593 | ----
594 | [ 1, 3, 5 ]
595 | ----
596 |
597 | [TIP]
598 | .自分自身を削除するモナド関数
599 | ====
600 | `oddFilter` というモナド関数が面白いのは、「空のモナド `[]` を返すこと」が「自分自身を削除する」という意味になっているところです。
601 |
602 | 一般的にリストモナド関数 `a => []` は自分自身を削除するモナド関数で、データをクリアできます。
603 |
604 | モナドが自分自身の構造をコントロールできる、という意味が実感できるでしょうか?
605 |
606 | 普通の `Array.map` の配列操作では、仮にメソッドチェーンのタイプエラー問題を克服しながら関数化できたとしても、このような芸当は不可能です。
607 |
608 | モナドは、どういったタイプのモナドでも、こういった特異点というか、数字のゼロに対応するような特異なケースでかなりよく振る舞う性質を備えているようです。
609 |
610 | たとえば、エラーを特異な値として持つと大きなメリットがあるなど。
611 |
612 | Javaで悪名高い頻発するnull pointer exceptionは、このような値がないときの振る舞いを設計の段階で上手く規定できていないことが根本的原因ですが、モナドを積極的に取り入れることで問題の多くは解決するんじゃないでしょうか?
613 |
614 | また著者が最近書いたFRPオブジェクトをモナドになるように設計していると、値が `undefined` になればイベントを発生しない、というデザインに自然になってしまいました。人工的な仕様がオブジェクトの構造を要請するのではなくて、数学的な構造が自然と仕様を要請してしまう、というのは驚くべきことです。
615 |
616 | ====
617 |
618 |
619 | ---
620 |
621 | [source,js]
622 | .モナド関数 メソッドチェーン
623 | ----
624 | const array1 = [1, 2, 3, 4, 5];
625 | const array3 = array1
626 | .flatMap(plus9) <1>
627 | .flatMap(oddFilter); <2>
628 |
629 | console.log(array3);
630 | ----
631 |
632 | <1> `plus9` モナド関数で `flatMap`
633 | <2> `oddFilter` モナド関数で `flatMap`
634 |
635 | [source,js]
636 | .Console
637 | ----
638 | [ 11, 13 ]
639 | ----
640 |
641 | ---
642 |
643 | [source,js]
644 | .モナド関数 メソッドチェーンでモナド関数合成
645 | ----
646 | const plus9oddFilter = a => <1>
647 | unit(a) <2>
648 | .flatMap(plus9) <3>
649 | .flatMap(oddFilter); <4>
650 |
651 | const array1 = [1, 2, 3, 4, 5];
652 | const array4 = array1
653 | .flatMap(plus9oddFilter); <5>
654 |
655 | console.log(array4);
656 |
657 | ----
658 |
659 | <1> `plus9oddFilter` というモナド合成関数を作る
660 | <2> ここまで `identity` モナド関数 と一緒 自分自身を返している
661 | <3> `plus9` モナド関数で `flatMap`
662 | <4> `oddFilter` モナド関数で `flatMap`
663 | <5> `plus9oddFilter` モナド関数で `flatMap`
664 |
665 | [source,js]
666 | .Console
667 | ----
668 | [ 11, 13 ]
669 | ----
670 |
671 | モナド関数の設計も合成もすべて、`Array.flatMap` メソッド1本で実現していることに注目してください。
672 |
673 |
674 | == まとめ
675 |
676 | モナドは、`map` メソッドに `flat` メソッドを追加したオブジェクト。
677 |
678 | `map` と `flat` を合成したのが、`flatMap` で当然これもモナドのメソッド。
679 |
680 | JavaScriptの `Array.flatMap` でリストモナドに触れるので慣れよう。
681 |
682 | モナドは構造がコントロールできるので、メリット多数。
683 |
684 | メソッドチェーンがネストしても壊れない堅牢な構造。
685 |
686 | `Array.flatMap` は、`Array.map` の上位互換。これ一本で何でも出来るようになる。
687 |
688 |
689 | モナド関数の構成のことだけ気にしていれば良い。
690 |
691 | `Array.map` で消耗するのはもうやめよう。
692 |
693 |
694 |
--------------------------------------------------------------------------------
/docs/asciidoctor-custom.css:
--------------------------------------------------------------------------------
1 | /* Asciidoctor default stylesheet | MIT License | http://asciidoctor.org */
2 | /* Uncomment @import statement below to use as custom stylesheet */
3 | @import "https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css";
4 | @import "https://fonts.googleapis.com/css?family=Open+Sans:300,300italic,400,400italic,600,600italic%7CNoto+Serif:400,400italic,700,700italic%7CDroid+Sans+Mono:400,700";
5 | article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}
6 | audio,canvas,video{display:inline-block}
7 | audio:not([controls]){display:none;height:0}
8 | script{display:none!important}
9 | html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}
10 | a{background:transparent}
11 | a:focus{outline:thin dotted}
12 | a:active,a:hover{outline:0}
13 | h1{font-size:2em;margin:.67em 0}
14 | abbr[title]{border-bottom:1px dotted}
15 | b,strong{font-weight:bold}
16 | dfn{font-style:italic}
17 | hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}
18 | mark{background:#ff0;color:#000}
19 | code,kbd,pre,samp{font-family:monospace;font-size:1em}
20 | pre{white-space:pre-wrap}
21 | q{quotes:"\201C" "\201D" "\2018" "\2019"}
22 | small{font-size:80%}
23 | sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}
24 | sup{top:-.5em}
25 | sub{bottom:-.25em}
26 | img{border:0}
27 | svg:not(:root){overflow:hidden}
28 | figure{margin:0}
29 | fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}
30 | legend{border:0;padding:0}
31 | button,input,select,textarea{font-family:inherit;font-size:100%;margin:0}
32 | button,input{line-height:normal}
33 | button,select{text-transform:none}
34 | button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}
35 | button[disabled],html input[disabled]{cursor:default}
36 | input[type="checkbox"],input[type="radio"]{box-sizing:border-box;padding:0}
37 | button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}
38 | textarea{overflow:auto;vertical-align:top}
39 | table{border-collapse:collapse;border-spacing:0}
40 | *,*::before,*::after{-moz-box-sizing:border-box;-webkit-box-sizing:border-box;box-sizing:border-box}
41 | html,body{font-size:100%}
42 | body{background:#fff;color:rgba(0,0,0,.8);padding:0;margin:0;font-family:"Source Code Pro", "Source Han Serif JP","DejaVu Serif",serif;font-weight:400;font-style:normal;line-height:1;position:relative;cursor:auto;tab-size:4;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased}
43 | a:hover{cursor:pointer}
44 | img,object,embed{max-width:100%;height:auto}
45 | object,embed{height:100%}
46 | img{-ms-interpolation-mode:bicubic}
47 | .left{float:left!important}
48 | .right{float:right!important}
49 | .text-left{text-align:left!important}
50 | .text-right{text-align:right!important}
51 | .text-center{text-align:center!important}
52 | .text-justify{text-align:justify!important}
53 | .hide{display:none}
54 | img,object,svg{display:inline-block;vertical-align:middle}
55 | textarea{height:auto;min-height:50px}
56 | select{width:100%}
57 | .center{margin-left:auto;margin-right:auto}
58 | .stretch{width:100%}
59 | .subheader,.admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{line-height:1.45;color:#7a2518;font-weight:400;margin-top:0;margin-bottom:.25em}
60 | div,dl,dt,dd,ul,ol,li,h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6,pre,form,p,blockquote,th,td{margin:0;padding:0;direction:ltr}
61 | a{color:#2156a5;text-decoration:underline;line-height:inherit}
62 | a:hover,a:focus{color:#1d4b8f}
63 | a img{border:none}
64 | p{font-family:inherit;font-weight:400;font-size:1em;line-height:1.6;margin-bottom:1.25em;text-rendering:optimizeLegibility}
65 | p aside{font-size:.875em;line-height:1.35;font-style:italic}
66 | h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{font-family:"Open Sans","DejaVu Sans",sans-serif;font-weight:300;font-style:normal;color:#ba3925;text-rendering:optimizeLegibility;margin-top:1em;margin-bottom:.5em;line-height:1.0125em}
67 | h1 small,h2 small,h3 small,#toctitle small,.sidebarblock>.content>.title small,h4 small,h5 small,h6 small{font-size:60%;color:#e99b8f;line-height:0}
68 | h1{font-size:2.125em}
69 | h2{font-size:1.6875em}
70 | h3,#toctitle,.sidebarblock>.content>.title{font-size:1.375em}
71 | h4,h5{font-size:1.125em}
72 | h6{font-size:1em}
73 | hr{border:solid #ddddd8;border-width:1px 0 0;clear:both;margin:1.25em 0 1.1875em;height:0}
74 | em,i{font-style:italic;line-height:inherit}
75 | strong,b{font-weight:bold;line-height:inherit}
76 | small{font-size:60%;line-height:inherit}
77 | code{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;color:rgba(0,0,0,.9)}
78 | ul,ol,dl{font-size:1em;line-height:1.6;margin-bottom:1.25em;list-style-position:outside;font-family:inherit}
79 | ul,ol{margin-left:1.5em}
80 | ul li ul,ul li ol{margin-left:1.25em;margin-bottom:0;font-size:1em}
81 | ul.square li ul,ul.circle li ul,ul.disc li ul{list-style:inherit}
82 | ul.square{list-style-type:square}
83 | ul.circle{list-style-type:circle}
84 | ul.disc{list-style-type:disc}
85 | ol li ul,ol li ol{margin-left:1.25em;margin-bottom:0}
86 | dl dt{margin-bottom:.3125em;font-weight:bold}
87 | dl dd{margin-bottom:1.25em}
88 | abbr,acronym{text-transform:uppercase;font-size:90%;color:rgba(0,0,0,.8);border-bottom:1px dotted #ddd;cursor:help}
89 | abbr{text-transform:none}
90 | blockquote{margin:0 0 1.25em;padding:.5625em 1.25em 0 1.1875em;border-left:1px solid #ddd}
91 | blockquote cite{display:block;font-size:.9375em;color:rgba(0,0,0,.6)}
92 | blockquote cite::before{content:"\2014 \0020"}
93 | blockquote cite a,blockquote cite a:visited{color:rgba(0,0,0,.6)}
94 | blockquote,blockquote p{line-height:1.6;color:rgba(0,0,0,.85)}
95 | @media screen and (min-width:768px){h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2}
96 | h1{font-size:2.75em}
97 | h2{font-size:2.3125em}
98 | h3,#toctitle,.sidebarblock>.content>.title{font-size:1.6875em}
99 | h4{font-size:1.4375em}}
100 | table{background:#fff;margin-bottom:1.25em;border:solid 1px #dedede}
101 | table thead,table tfoot{background:#f7f8f7}
102 | table thead tr th,table thead tr td,table tfoot tr th,table tfoot tr td{padding:.5em .625em .625em;font-size:inherit;color:rgba(0,0,0,.8);text-align:left}
103 | table tr th,table tr td{padding:.5625em .625em;font-size:inherit;color:rgba(0,0,0,.8)}
104 | table tr.even,table tr.alt,table tr:nth-of-type(even){background:#f8f8f7}
105 | table thead tr th,table tfoot tr th,table tbody tr td,table tr td,table tfoot tr td{display:table-cell;line-height:1.6}
106 | h1,h2,h3,#toctitle,.sidebarblock>.content>.title,h4,h5,h6{line-height:1.2;word-spacing:-.05em}
107 | h1 strong,h2 strong,h3 strong,#toctitle strong,.sidebarblock>.content>.title strong,h4 strong,h5 strong,h6 strong{font-weight:400}
108 | .clearfix::before,.clearfix::after,.float-group::before,.float-group::after{content:" ";display:table}
109 | .clearfix::after,.float-group::after{clear:both}
110 | *:not(pre)>code{font-size:.9375em;font-style:normal!important;letter-spacing:0;padding:.1em .5ex;word-spacing:-.15em;background-color:#f7f7f8;-webkit-border-radius:4px;border-radius:4px;line-height:1.45;text-rendering:optimizeSpeed;word-wrap:break-word}
111 | *:not(pre)>code.nobreak{word-wrap:normal}
112 | *:not(pre)>code.nowrap{white-space:nowrap}
113 | pre,pre>code{line-height:1.45;color:rgba(0,0,0,.9);font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;font-weight:400;text-rendering:optimizeSpeed}
114 | em em{font-style:normal}
115 | strong strong{font-weight:400}
116 | .keyseq{color:rgba(51,51,51,.8)}
117 | kbd{font-family:"Droid Sans Mono","DejaVu Sans Mono",monospace;display:inline-block;color:rgba(0,0,0,.8);font-size:.65em;line-height:1.45;background-color:#f7f7f7;border:1px solid #ccc;-webkit-border-radius:3px;border-radius:3px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em white inset;box-shadow:0 1px 0 rgba(0,0,0,.2),0 0 0 .1em #fff inset;margin:0 .15em;padding:.2em .5em;vertical-align:middle;position:relative;top:-.1em;white-space:nowrap}
118 | .keyseq kbd:first-child{margin-left:0}
119 | .keyseq kbd:last-child{margin-right:0}
120 | .menuseq,.menuref{color:#000}
121 | .menuseq b:not(.caret),.menuref{font-weight:inherit}
122 | .menuseq{word-spacing:-.02em}
123 | .menuseq b.caret{font-size:1.25em;line-height:.8}
124 | .menuseq i.caret{font-weight:bold;text-align:center;width:.45em}
125 | b.button::before,b.button::after{position:relative;top:-1px;font-weight:400}
126 | b.button::before{content:"[";padding:0 3px 0 2px}
127 | b.button::after{content:"]";padding:0 2px 0 3px}
128 | p a>code:hover{color:rgba(0,0,0,.9)}
129 | #header,#content,#footnotes,#footer{width:100%;margin-left:auto;margin-right:auto;margin-top:0;margin-bottom:0;max-width:62.5em;*zoom:1;position:relative;padding-left:.9375em;padding-right:.9375em}
130 | #header::before,#header::after,#content::before,#content::after,#footnotes::before,#footnotes::after,#footer::before,#footer::after{content:" ";display:table}
131 | #header::after,#content::after,#footnotes::after,#footer::after{clear:both}
132 | #content{margin-top:1.25em}
133 | #content::before{content:none}
134 | #header>h1:first-child{color:rgba(0,0,0,.85);margin-top:2.25rem;margin-bottom:0}
135 | #header>h1:first-child+#toc{margin-top:8px;border-top:1px solid #ddddd8}
136 | #header>h1:only-child,body.toc2 #header>h1:nth-last-child(2){border-bottom:1px solid #ddddd8;padding-bottom:8px}
137 | #header .details{border-bottom:1px solid #ddddd8;line-height:1.45;padding-top:.25em;padding-bottom:.25em;padding-left:.25em;color:rgba(0,0,0,.6);display:-ms-flexbox;display:-webkit-flex;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap}
138 | #header .details span:first-child{margin-left:-.125em}
139 | #header .details span.email a{color:rgba(0,0,0,.85)}
140 | #header .details br{display:none}
141 | #header .details br+span::before{content:"\00a0\2013\00a0"}
142 | #header .details br+span.author::before{content:"\00a0\22c5\00a0";color:rgba(0,0,0,.85)}
143 | #header .details br+span#revremark::before{content:"\00a0|\00a0"}
144 | #header #revnumber{text-transform:capitalize}
145 | #header #revnumber::after{content:"\00a0"}
146 | #content>h1:first-child:not([class]){color:rgba(0,0,0,.85);border-bottom:1px solid #ddddd8;padding-bottom:8px;margin-top:0;padding-top:1rem;margin-bottom:1.25rem}
147 | #toc{border-bottom:1px solid #efefed;padding-bottom:.5em}
148 | #toc>ul{margin-left:.125em}
149 | #toc ul.sectlevel0>li>a{font-style:italic}
150 | #toc ul.sectlevel0 ul.sectlevel1{margin:.5em 0}
151 | #toc ul{font-family:"Open Sans","DejaVu Sans",sans-serif;list-style-type:none}
152 | #toc li{line-height:1.3334;margin-top:.3334em}
153 | #toc a{text-decoration:none}
154 | #toc a:active{text-decoration:underline}
155 | #toctitle{color:#7a2518;font-size:1.2em}
156 | @media screen and (min-width:768px){#toctitle{font-size:1.375em}
157 | body.toc2{padding-left:15em;padding-right:0}
158 | #toc.toc2{margin-top:0!important;background-color:#f8f8f7;position:fixed;width:15em;left:0;top:0;border-right:1px solid #efefed;border-top-width:0!important;border-bottom-width:0!important;z-index:1000;padding:1.25em 1em;height:100%;overflow:auto}
159 | #toc.toc2 #toctitle{margin-top:0;margin-bottom:.8rem;font-size:1.2em}
160 | #toc.toc2>ul{font-size:.9em;margin-bottom:0}
161 | #toc.toc2 ul ul{margin-left:0;padding-left:1em}
162 | #toc.toc2 ul.sectlevel0 ul.sectlevel1{padding-left:0;margin-top:.5em;margin-bottom:.5em}
163 | body.toc2.toc-right{padding-left:0;padding-right:15em}
164 | body.toc2.toc-right #toc.toc2{border-right-width:0;border-left:1px solid #efefed;left:auto;right:0}}
165 | @media screen and (min-width:1280px){body.toc2{padding-left:20em;padding-right:0}
166 | #toc.toc2{width:20em}
167 | #toc.toc2 #toctitle{font-size:1.375em}
168 | #toc.toc2>ul{font-size:.95em}
169 | #toc.toc2 ul ul{padding-left:1.25em}
170 | body.toc2.toc-right{padding-left:0;padding-right:20em}}
171 | #content #toc{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
172 | #content #toc>:first-child{margin-top:0}
173 | #content #toc>:last-child{margin-bottom:0}
174 | #footer{max-width:100%;background-color:rgba(0,0,0,.8);padding:1.25em}
175 | #footer-text{color:rgba(255,255,255,.8);line-height:1.44}
176 | #content{margin-bottom:.625em}
177 | .sect1{padding-bottom:.625em}
178 | @media screen and (min-width:768px){#content{margin-bottom:1.25em}
179 | .sect1{padding-bottom:1.25em}}
180 | .sect1:last-child{padding-bottom:0}
181 | .sect1+.sect1{border-top:1px solid #efefed}
182 | #content h1>a.anchor,h2>a.anchor,h3>a.anchor,#toctitle>a.anchor,.sidebarblock>.content>.title>a.anchor,h4>a.anchor,h5>a.anchor,h6>a.anchor{position:absolute;z-index:1001;width:1.5ex;margin-left:-1.5ex;display:block;text-decoration:none!important;visibility:hidden;text-align:center;font-weight:400}
183 | #content h1>a.anchor::before,h2>a.anchor::before,h3>a.anchor::before,#toctitle>a.anchor::before,.sidebarblock>.content>.title>a.anchor::before,h4>a.anchor::before,h5>a.anchor::before,h6>a.anchor::before{content:"\00A7";font-size:.85em;display:block;padding-top:.1em}
184 | #content h1:hover>a.anchor,#content h1>a.anchor:hover,h2:hover>a.anchor,h2>a.anchor:hover,h3:hover>a.anchor,#toctitle:hover>a.anchor,.sidebarblock>.content>.title:hover>a.anchor,h3>a.anchor:hover,#toctitle>a.anchor:hover,.sidebarblock>.content>.title>a.anchor:hover,h4:hover>a.anchor,h4>a.anchor:hover,h5:hover>a.anchor,h5>a.anchor:hover,h6:hover>a.anchor,h6>a.anchor:hover{visibility:visible}
185 | #content h1>a.link,h2>a.link,h3>a.link,#toctitle>a.link,.sidebarblock>.content>.title>a.link,h4>a.link,h5>a.link,h6>a.link{color:#ba3925;text-decoration:none}
186 | #content h1>a.link:hover,h2>a.link:hover,h3>a.link:hover,#toctitle>a.link:hover,.sidebarblock>.content>.title>a.link:hover,h4>a.link:hover,h5>a.link:hover,h6>a.link:hover{color:#a53221}
187 | .audioblock,.imageblock,.literalblock,.listingblock,.stemblock,.videoblock{margin-bottom:1.25em}
188 | .admonitionblock td.content>.title,.audioblock>.title,.exampleblock>.title,.imageblock>.title,.listingblock>.title,.literalblock>.title,.stemblock>.title,.openblock>.title,.paragraph>.title,.quoteblock>.title,table.tableblock>.title,.verseblock>.title,.videoblock>.title,.dlist>.title,.olist>.title,.ulist>.title,.qlist>.title,.hdlist>.title{text-rendering:optimizeLegibility;text-align:left;font-family:"Noto Serif","DejaVu Serif",serif;font-size:1rem;font-style:italic}
189 | table.tableblock.fit-content>caption.title{white-space:nowrap;width:0}
190 | .paragraph.lead>p,#preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:1.21875em;line-height:1.6;color:rgba(0,0,0,.85)}
191 | table.tableblock #preamble>.sectionbody>[class="paragraph"]:first-of-type p{font-size:inherit}
192 | .admonitionblock>table{border-collapse:separate;border:0;background:none;width:100%}
193 | .admonitionblock>table td.icon{text-align:center;width:80px}
194 | .admonitionblock>table td.icon img{max-width:none}
195 | .admonitionblock>table td.icon .title{font-weight:bold;font-family:"Open Sans","DejaVu Sans",sans-serif;text-transform:uppercase}
196 | .admonitionblock>table td.content{padding-left:1.125em;padding-right:1.25em;border-left:1px solid #ddddd8;color:rgba(0,0,0,.6)}
197 | .admonitionblock>table td.content>:last-child>:last-child{margin-bottom:0}
198 | .exampleblock>.content{border-style:solid;border-width:1px;border-color:#e6e6e6;margin-bottom:1.25em;padding:1.25em;background:#fff;-webkit-border-radius:4px;border-radius:4px}
199 | .exampleblock>.content>:first-child{margin-top:0}
200 | .exampleblock>.content>:last-child{margin-bottom:0}
201 | .sidebarblock{border-style:solid;border-width:1px;border-color:#e0e0dc;margin-bottom:1.25em;padding:1.25em;background:#f8f8f7;-webkit-border-radius:4px;border-radius:4px}
202 | .sidebarblock>:first-child{margin-top:0}
203 | .sidebarblock>:last-child{margin-bottom:0}
204 | .sidebarblock>.content>.title{color:#7a2518;margin-top:0;text-align:center}
205 | .exampleblock>.content>:last-child>:last-child,.exampleblock>.content .olist>ol>li:last-child>:last-child,.exampleblock>.content .ulist>ul>li:last-child>:last-child,.exampleblock>.content .qlist>ol>li:last-child>:last-child,.sidebarblock>.content>:last-child>:last-child,.sidebarblock>.content .olist>ol>li:last-child>:last-child,.sidebarblock>.content .ulist>ul>li:last-child>:last-child,.sidebarblock>.content .qlist>ol>li:last-child>:last-child{margin-bottom:0}
206 | .literalblock pre,.listingblock pre:not(.highlight),.listingblock pre[class="highlight"],.listingblock pre[class^="highlight "],.listingblock pre.CodeRay,.listingblock pre.prettyprint{background:#f7f7f8}
207 | .sidebarblock .literalblock pre,.sidebarblock .listingblock pre:not(.highlight),.sidebarblock .listingblock pre[class="highlight"],.sidebarblock .listingblock pre[class^="highlight "],.sidebarblock .listingblock pre.CodeRay,.sidebarblock .listingblock pre.prettyprint{background:#f2f1f1}
208 | .literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{-webkit-border-radius:4px;border-radius:4px;word-wrap:break-word;padding:1em;font-size:.8125em}
209 | .literalblock pre.nowrap,.literalblock pre[class].nowrap,.listingblock pre.nowrap,.listingblock pre[class].nowrap{overflow-x:auto;white-space:pre;word-wrap:normal}
210 | @media screen and (min-width:768px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:.90625em}}
211 | @media screen and (min-width:1280px){.literalblock pre,.literalblock pre[class],.listingblock pre,.listingblock pre[class]{font-size:1em}}
212 | .literalblock.output pre{color:#f7f7f8;background-color:rgba(0,0,0,.9)}
213 | .listingblock pre.highlightjs{padding:0}
214 | .listingblock pre.highlightjs>code{padding:1em;-webkit-border-radius:4px;border-radius:4px}
215 | .listingblock pre.prettyprint{border-width:0}
216 | .listingblock>.content{position:relative}
217 | .listingblock code[data-lang]::before{display:none;content:attr(data-lang);position:absolute;font-size:.75em;top:.425rem;right:.5rem;line-height:1;text-transform:uppercase;color:#999}
218 | .listingblock:hover code[data-lang]::before{display:block}
219 | .listingblock.terminal pre .command::before{content:attr(data-prompt);padding-right:.5em;color:#999}
220 | .listingblock.terminal pre .command:not([data-prompt])::before{content:"$"}
221 | table.pyhltable{border-collapse:separate;border:0;margin-bottom:0;background:none}
222 | table.pyhltable td{vertical-align:top;padding-top:0;padding-bottom:0;line-height:1.45}
223 | table.pyhltable td.code{padding-left:.75em;padding-right:0}
224 | pre.pygments .lineno,table.pyhltable td:not(.code){color:#999;padding-left:0;padding-right:.5em;border-right:1px solid #ddddd8}
225 | pre.pygments .lineno{display:inline-block;margin-right:.25em}
226 | table.pyhltable .linenodiv{background:none!important;padding-right:0!important}
227 | .quoteblock{margin:0 1em 1.25em 1.5em;display:table}
228 | .quoteblock>.title{margin-left:-1.5em;margin-bottom:.75em}
229 | .quoteblock blockquote,.quoteblock blockquote p{color:rgba(0,0,0,.85);font-size:1.15rem;line-height:1.75;word-spacing:.1em;letter-spacing:0;font-style:italic;text-align:justify}
230 | .quoteblock blockquote{margin:0;padding:0;border:0}
231 | .quoteblock blockquote::before{content:"\201c";float:left;font-size:2.75em;font-weight:bold;line-height:.6em;margin-left:-.6em;color:#7a2518;text-shadow:0 1px 2px rgba(0,0,0,.1)}
232 | .quoteblock blockquote>.paragraph:last-child p{margin-bottom:0}
233 | .quoteblock .attribution{margin-top:.5em;margin-right:.5ex;text-align:right}
234 | .quoteblock .quoteblock{margin-left:0;margin-right:0;padding:.5em 0;border-left:3px solid rgba(0,0,0,.6)}
235 | .quoteblock .quoteblock blockquote{padding:0 0 0 .75em}
236 | .quoteblock .quoteblock blockquote::before{display:none}
237 | .verseblock{margin:0 1em 1.25em}
238 | .verseblock pre{font-family:"Open Sans","DejaVu Sans",sans;font-size:1.15rem;color:rgba(0,0,0,.85);font-weight:300;text-rendering:optimizeLegibility}
239 | .verseblock pre strong{font-weight:400}
240 | .verseblock .attribution{margin-top:1.25rem;margin-left:.5ex}
241 | .quoteblock .attribution,.verseblock .attribution{font-size:.9375em;line-height:1.45;font-style:italic}
242 | .quoteblock .attribution br,.verseblock .attribution br{display:none}
243 | .quoteblock .attribution cite,.verseblock .attribution cite{display:block;letter-spacing:-.025em;color:rgba(0,0,0,.6)}
244 | .quoteblock.abstract{margin:0 1em 1.25em;display:block}
245 | .quoteblock.abstract>.title{margin:0 0 .375em;font-size:1.15em;text-align:center}
246 | .quoteblock.abstract blockquote,.quoteblock.abstract blockquote p{word-spacing:0;line-height:1.6}
247 | .quoteblock.abstract blockquote::before,.quoteblock.abstract p::before{display:none}
248 | table.tableblock{max-width:100%;border-collapse:separate}
249 | p.tableblock:last-child{margin-bottom:0}
250 | td.tableblock>.content{margin-bottom:-1.25em}
251 | table.tableblock,th.tableblock,td.tableblock{border:0 solid #dedede}
252 | table.grid-all>thead>tr>.tableblock,table.grid-all>tbody>tr>.tableblock{border-width:0 1px 1px 0}
253 | table.grid-all>tfoot>tr>.tableblock{border-width:1px 1px 0 0}
254 | table.grid-cols>*>tr>.tableblock{border-width:0 1px 0 0}
255 | table.grid-rows>thead>tr>.tableblock,table.grid-rows>tbody>tr>.tableblock{border-width:0 0 1px}
256 | table.grid-rows>tfoot>tr>.tableblock{border-width:1px 0 0}
257 | table.grid-all>*>tr>.tableblock:last-child,table.grid-cols>*>tr>.tableblock:last-child{border-right-width:0}
258 | table.grid-all>tbody>tr:last-child>.tableblock,table.grid-all>thead:last-child>tr>.tableblock,table.grid-rows>tbody>tr:last-child>.tableblock,table.grid-rows>thead:last-child>tr>.tableblock{border-bottom-width:0}
259 | table.frame-all{border-width:1px}
260 | table.frame-sides{border-width:0 1px}
261 | table.frame-topbot,table.frame-ends{border-width:1px 0}
262 | table.stripes-all tr,table.stripes-odd tr:nth-of-type(odd){background:#f8f8f7}
263 | table.stripes-none tr,table.stripes-odd tr:nth-of-type(even){background:none}
264 | th.halign-left,td.halign-left{text-align:left}
265 | th.halign-right,td.halign-right{text-align:right}
266 | th.halign-center,td.halign-center{text-align:center}
267 | th.valign-top,td.valign-top{vertical-align:top}
268 | th.valign-bottom,td.valign-bottom{vertical-align:bottom}
269 | th.valign-middle,td.valign-middle{vertical-align:middle}
270 | table thead th,table tfoot th{font-weight:bold}
271 | tbody tr th{display:table-cell;line-height:1.6;background:#f7f8f7}
272 | tbody tr th,tbody tr th p,tfoot tr th,tfoot tr th p{color:rgba(0,0,0,.8);font-weight:bold}
273 | p.tableblock>code:only-child{background:none;padding:0}
274 | p.tableblock{font-size:1em}
275 | td>div.verse{white-space:pre}
276 | ol{margin-left:1.75em}
277 | ul li ol{margin-left:1.5em}
278 | dl dd{margin-left:1.125em}
279 | dl dd:last-child,dl dd:last-child>:last-child{margin-bottom:0}
280 | ol>li p,ul>li p,ul dd,ol dd,.olist .olist,.ulist .ulist,.ulist .olist,.olist .ulist{margin-bottom:.625em}
281 | ul.checklist,ul.none,ol.none,ul.no-bullet,ol.no-bullet,ol.unnumbered,ul.unstyled,ol.unstyled{list-style-type:none}
282 | ul.no-bullet,ol.no-bullet,ol.unnumbered{margin-left:.625em}
283 | ul.unstyled,ol.unstyled{margin-left:0}
284 | ul.checklist{margin-left:.625em}
285 | ul.checklist li>p:first-child>.fa-square-o:first-child,ul.checklist li>p:first-child>.fa-check-square-o:first-child{width:1.25em;font-size:.8em;position:relative;bottom:.125em}
286 | ul.checklist li>p:first-child>input[type="checkbox"]:first-child{margin-right:.25em}
287 | ul.inline{display:-ms-flexbox;display:-webkit-box;display:flex;-ms-flex-flow:row wrap;-webkit-flex-flow:row wrap;flex-flow:row wrap;list-style:none;margin:0 0 .625em -1.25em}
288 | ul.inline>li{margin-left:1.25em}
289 | .unstyled dl dt{font-weight:400;font-style:normal}
290 | ol.arabic{list-style-type:decimal}
291 | ol.decimal{list-style-type:decimal-leading-zero}
292 | ol.loweralpha{list-style-type:lower-alpha}
293 | ol.upperalpha{list-style-type:upper-alpha}
294 | ol.lowerroman{list-style-type:lower-roman}
295 | ol.upperroman{list-style-type:upper-roman}
296 | ol.lowergreek{list-style-type:lower-greek}
297 | .hdlist>table,.colist>table{border:0;background:none}
298 | .hdlist>table>tbody>tr,.colist>table>tbody>tr{background:none}
299 | td.hdlist1,td.hdlist2{vertical-align:top;padding:0 .625em}
300 | td.hdlist1{font-weight:bold;padding-bottom:1.25em}
301 | .literalblock+.colist,.listingblock+.colist{margin-top:-.5em}
302 | .colist td:not([class]):first-child{padding:.4em .75em 0;line-height:1;vertical-align:top}
303 | .colist td:not([class]):first-child img{max-width:none}
304 | .colist td:not([class]):last-child{padding:.25em 0}
305 | .thumb,.th{line-height:0;display:inline-block;border:solid 4px #fff;-webkit-box-shadow:0 0 0 1px #ddd;box-shadow:0 0 0 1px #ddd}
306 | .imageblock.left,.imageblock[style*="float: left"]{margin:.25em .625em 1.25em 0}
307 | .imageblock.right,.imageblock[style*="float: right"]{margin:.25em 0 1.25em .625em}
308 | .imageblock>.title{margin-bottom:0}
309 | .imageblock.thumb,.imageblock.th{border-width:6px}
310 | .imageblock.thumb>.title,.imageblock.th>.title{padding:0 .125em}
311 | .image.left,.image.right{margin-top:.25em;margin-bottom:.25em;display:inline-block;line-height:0}
312 | .image.left{margin-right:.625em}
313 | .image.right{margin-left:.625em}
314 | a.image{text-decoration:none;display:inline-block}
315 | a.image object{pointer-events:none}
316 | sup.footnote,sup.footnoteref{font-size:.875em;position:static;vertical-align:super}
317 | sup.footnote a,sup.footnoteref a{text-decoration:none}
318 | sup.footnote a:active,sup.footnoteref a:active{text-decoration:underline}
319 | #footnotes{padding-top:.75em;padding-bottom:.75em;margin-bottom:.625em}
320 | #footnotes hr{width:20%;min-width:6.25em;margin:-.25em 0 .75em;border-width:1px 0 0}
321 | #footnotes .footnote{padding:0 .375em 0 .225em;line-height:1.3334;font-size:.875em;margin-left:1.2em;margin-bottom:.2em}
322 | #footnotes .footnote a:first-of-type{font-weight:bold;text-decoration:none;margin-left:-1.05em}
323 | #footnotes .footnote:last-of-type{margin-bottom:0}
324 | #content #footnotes{margin-top:-.625em;margin-bottom:0;padding:.75em 0}
325 | .gist .file-data>table{border:0;background:#fff;width:100%;margin-bottom:0}
326 | .gist .file-data>table td.line-data{width:99%}
327 | div.unbreakable{page-break-inside:avoid}
328 | .big{font-size:larger}
329 | .small{font-size:smaller}
330 | .underline{text-decoration:underline}
331 | .overline{text-decoration:overline}
332 | .line-through{text-decoration:line-through}
333 | .aqua{color:#00bfbf}
334 | .aqua-background{background-color:#00fafa}
335 | .black{color:#000}
336 | .black-background{background-color:#000}
337 | .blue{color:#0000bf}
338 | .blue-background{background-color:#0000fa}
339 | .fuchsia{color:#bf00bf}
340 | .fuchsia-background{background-color:#fa00fa}
341 | .gray{color:#606060}
342 | .gray-background{background-color:#7d7d7d}
343 | .green{color:#006000}
344 | .green-background{background-color:#007d00}
345 | .lime{color:#00bf00}
346 | .lime-background{background-color:#00fa00}
347 | .maroon{color:#600000}
348 | .maroon-background{background-color:#7d0000}
349 | .navy{color:#000060}
350 | .navy-background{background-color:#00007d}
351 | .olive{color:#606000}
352 | .olive-background{background-color:#7d7d00}
353 | .purple{color:#600060}
354 | .purple-background{background-color:#7d007d}
355 | .red{color:#bf0000}
356 | .red-background{background-color:#fa0000}
357 | .silver{color:#909090}
358 | .silver-background{background-color:#bcbcbc}
359 | .teal{color:#006060}
360 | .teal-background{background-color:#007d7d}
361 | .white{color:#bfbfbf}
362 | .white-background{background-color:#fafafa}
363 | .yellow{color:#bfbf00}
364 | .yellow-background{background-color:#fafa00}
365 | span.icon>.fa{cursor:default}
366 | a span.icon>.fa{cursor:inherit}
367 | .admonitionblock td.icon [class^="fa icon-"]{font-size:2.5em;text-shadow:1px 1px 2px rgba(0,0,0,.5);cursor:default}
368 | .admonitionblock td.icon .icon-note::before{content:"\f05a";color:#19407c}
369 | .admonitionblock td.icon .icon-tip::before{content:"\f0eb";text-shadow:1px 1px 2px rgba(155,155,0,.8);color:#111}
370 | .admonitionblock td.icon .icon-warning::before{content:"\f071";color:#bf6900}
371 | .admonitionblock td.icon .icon-caution::before{content:"\f06d";color:#bf3400}
372 | .admonitionblock td.icon .icon-important::before{content:"\f06a";color:#bf0000}
373 | .conum[data-value]{display:inline-block;color:#fff!important;background-color:rgba(0,0,0,.8);-webkit-border-radius:100px;border-radius:100px;text-align:center;font-size:.75em;width:1.67em;height:1.67em;line-height:1.67em;font-family:"Open Sans","DejaVu Sans",sans-serif;font-style:normal;font-weight:bold}
374 | .conum[data-value] *{color:#fff!important}
375 | .conum[data-value]+b{display:none}
376 | .conum[data-value]::after{content:attr(data-value)}
377 | pre .conum[data-value]{position:relative;top:-.125em}
378 | b.conum *{color:inherit!important}
379 | .conum:not([data-value]):empty{display:none}
380 | dt,th.tableblock,td.content,div.footnote{text-rendering:optimizeLegibility}
381 | h1,h2,p,td.content,span.alt{letter-spacing:-.01em}
382 | p strong,td.content strong,div.footnote strong{letter-spacing:-.005em}
383 | p,blockquote,dt,td.content,span.alt{font-size:1.0625rem}
384 | p{margin-bottom:1.25rem}
385 | .sidebarblock p,.sidebarblock dt,.sidebarblock td.content,p.tableblock{font-size:1em}
386 | .exampleblock>.content{background-color:#fffef7;border-color:#e0e0dc;-webkit-box-shadow:0 1px 4px #e0e0dc;box-shadow:0 1px 4px #e0e0dc}
387 | .print-only{display:none!important}
388 | @page{margin:1.25cm .75cm}
389 | @media print{*{-webkit-box-shadow:none!important;box-shadow:none!important;text-shadow:none!important}
390 | html{font-size:80%}
391 | a{color:inherit!important;text-decoration:underline!important}
392 | a.bare,a[href^="#"],a[href^="mailto:"]{text-decoration:none!important}
393 | a[href^="http:"]:not(.bare)::after,a[href^="https:"]:not(.bare)::after{content:"(" attr(href) ")";display:inline-block;font-size:.875em;padding-left:.25em}
394 | abbr[title]::after{content:" (" attr(title) ")"}
395 | pre,blockquote,tr,img,object,svg{page-break-inside:avoid}
396 | thead{display:table-header-group}
397 | svg{max-width:100%}
398 | p,blockquote,dt,td.content{font-size:1em;orphans:3;widows:3}
399 | h2,h3,#toctitle,.sidebarblock>.content>.title{page-break-after:avoid}
400 | #toc,.sidebarblock,.exampleblock>.content{background:none!important}
401 | #toc{border-bottom:1px solid #ddddd8!important;padding-bottom:0!important}
402 | body.book #header{text-align:center}
403 | body.book #header>h1:first-child{border:0!important;margin:2.5em 0 1em}
404 | body.book #header .details{border:0!important;display:block;padding:0!important}
405 | body.book #header .details span:first-child{margin-left:0!important}
406 | body.book #header .details br{display:block}
407 | body.book #header .details br+span::before{content:none!important}
408 | body.book #toc{border:0!important;text-align:left!important;padding:0!important;margin:0!important}
409 | body.book #toc,body.book #preamble,body.book h1.sect0,body.book .sect1>h2{page-break-before:always}
410 | .listingblock code[data-lang]::before{display:block}
411 | #footer{padding:0 .9375em}
412 | .hide-on-print{display:none!important}
413 | .print-only{display:block!important}
414 | .hide-for-print{display:none!important}
415 | .show-for-print{display:inherit!important}}
416 | @media print,amzn-kf8{#header>h1:first-child{margin-top:1.25rem}
417 | .sect1{padding:0!important}
418 | .sect1+.sect1{border:0}
419 | #footer{background:none}
420 | #footer-text{color:rgba(0,0,0,.6);font-size:.9em}}
421 | @media amzn-kf8{#header,#content,#footnotes,#footer{padding:0}}
422 |
423 | th,td {
424 | border: solid 0px;
425 | }
426 |
427 | p>code {background-color: #aaaaaa};
428 | td>code {background-color: #aaaaaa};
--------------------------------------------------------------------------------