Skip to content

Instantly share code, notes, and snippets.

  • Save ken-okabe/bac4f8e53dd7d593b41e to your computer and use it in GitHub Desktop.
Save ken-okabe/bac4f8e53dd7d593b41e to your computer and use it in GitHub Desktop.
事象の地平面(イベント・ホライズン) オブジェクト指向という情報のブラックホール、アンチパラダイムとしての純粋関数型HaskellモナドをUNIXの標準ストリームとconsole.logで読み解く
#事象の地平面(イベント・ホライズン) オブジェクト指向という情報のブラックホール、アンチパラダイムとしての純粋関数型HaskellモナドをUNIXの標準ストリームとconsole.logで読み解く
##UNIXの標準ストリーム
####物質世界(ハードウェア)と論理世界(ソフトウェア)
![enter image description here](http://upload.wikimedia.org/wikipedia/commons/thumb/7/70/Stdstreams-notitle.svg/535px-Stdstreams-notitle.svg.png)
>[標準ストリーム(英: standard streams)](http://ja.wikipedia.org/wiki/%E6%A8%99%E6%BA%96%E3%82%B9%E3%83%88%E3%83%AA%E3%83%BC%E3%83%A0)は、UNIXおよびUnix系オペレーティングシステムや一部のプログラミング言語インタフェースにおいて、プログラムとその環境(通常は端末)を実行前から接続している入出力チャネルである。現在では3つの入出力があり、標準入力 (standard input)、標準出力 (standard output)、標準エラー出力 (standard error) と呼ばれている。かつては通信回線に対応する標準補助入出力 (stdaux)、帳票印字を出力する装置に対応する標準プリンタ出力 (stdprn)も用意されていた。
>背景
UNIX以前の多くのオペレーティングシステムでは、プログラムは明示的に適当な入出力に接続する必要があった。多くのシステムにはOS固有の複雑な事情があり、環境設定をしたり、ローカルなファイルテーブルにアクセスしたり、必要なデータセットを指定したり、カードリーダー、磁気テープドライブ、ディスクドライブ、ラインプリンタ、カードパンチ、対話型端末などを正しく扱うといったプログラミング以前のハードルが多数存在した。
**UNIXはこの状況に対していくつかの重要な進化を遂げている。その1つが「抽象デバイス (abstract device)」である。**これはプログラム自体がやり取りするデバイスに関する知識を持たなくて済むようにしたものである。古いOSでは、プログラマはレコード構造を知っておく必要があり、直交性のないことが多いデータ意味論やデバイス制御を扱う必要があった。UNIXはデータストリームという概念によってこのような複雑さを排除した。データストリームとは、逐次的なデータバイト列であり、End Of File までリード可能である。プログラムはまた、好きなだけバイト列を出力でき、事前にバイト数を宣言しておく必要もないし、それらがどのようにグループ化されているかを宣言する必要もない。
もう1つのUNIXの成し遂げたブレークスルーは、自動的に入力と出力を関連付けることであり、典型的な入力-処理-出力型プログラムでは入出力の設定を何もする必要がない。対照的にそれ以前のOSでは、複雑なジョブ制御言語を使ってコネクションを確立するか、それとほぼ同等のことをプログラム本体で行う必要があった。
UNIXが標準ストリームを提供したことで、そのC言語実行環境もそれをサポートするようになった。結果として、多くのC言語実行環境(および派生言語の実行環境)はOSが何であっても同等な機能を提供するようになっている。
##JavaScriptの`console`というオブジェクト
JavaScriptの`console`は、
UNIXの
- 標準出力 (standard output)、
- 標準エラー出力 (standard error)
を司る、オブジェクト指向のオブジェクトです。
[MDN>開発者向けのWeb技術>Web API インターフェイス>console](https://developer.mozilla.org/ja/docs/Web/API/console)
>console **オブジェクト**は、ブラウザのデバッグコンソールへアクセスする機能を提供します。この**オブジェクトの詳細な動作**はブラウザによって異なりますが、一般的に共通の機能セットがサポートされています。
>**console.log()**
一般タイプのログ情報を出力します。このメソッドでは、文字列置換および追加の引数を使用することができます。『文字列置換』をご覧ください。
>**console.error()**
エラーメッセージを出力します。このメソッドでは、文字列置換および追加の引数を使用することができます。『文字列置換』をご覧ください。
##アラン・ケイによるオブジェクト指向
「もの」===「オブジェクト」に
指示を与える方法(メッセージング)を指向する。
[オブジェクト指向はメッセージングが一番重要](http://qiita.com/tokomakoma123/items/a33feffe947a958a2d3a#1-3)
>[アラン・ケイ](http://ja.wikipedia.org/wiki/%E3%82%A2%E3%83%A9%E3%83%B3%E3%83%BB%E3%82%B1%E3%82%A4)が書いた有名な話の中で彼は「オブジェクト指向プログラミングの概念は完全に間違って理解されているのだ。**オブジェクト指向プログラミングの正しい概念とはオブジェクトとクラスに関してではなく、すべてがメッセージングということなのだ。**」と言っています。
彼はオブジェクト指向プログラミングではクラスとメソッドに関することがあまりに強調されすぎて、メッセージについては強調されないことが問題だと言っています。そして、もしメッセージ関してその性質やメリットをもっと多く語ることができれば状況は今より状況がよくなるのだと主張しています。オリジナルの[Smalltalk](http://ja.wikipedia.org/wiki/Smalltalk)はオブジェクト自身に関して、また、**これらのオブジェクトに対してメッセージを送って、これらオブジェクトが送り返されたメッセージによってどのように反応するか**について常にそのメリットを語っています。
コンピューティングで、オブジェクトとは、
メモリを確保して**物質的に実体化した状態**を指します。
この実体のことをインスタンスとも呼びます。
また、実体化することをインスタンス化といいます。
メッセージングのオブジェクト指向は、
命令型プログラミングパラダイムです。
##事象の地平面(イベント・ホライズン) 情報伝達の境界
>**[事象の地平面(じしょうのちへいめん、event horizon)](http://ja.wikipedia.org/wiki/%E4%BA%8B%E8%B1%A1%E3%81%AE%E5%9C%B0%E5%B9%B3%E9%9D%A2)は、物理学・相対性理論の概念で、情報伝達の境界面である。**
シュヴァルツシルト面また、シュバルツシルト面と言われることもある。空間を2次元に単純化したモデルを考え、事象の地平線(じしょうのちへいせん)ということもある。
情報は光や電磁波などにより伝達され、その最大速度は光速であるが、光などでも到達できなくなる領域(距離)が存在し、**ここより先の情報を我々は知ることができない。この境界を指し「事象の地平面」と呼ぶ。**
##因果律を破壊する特異点と事象の地平面(イベント・ホライズン)とブラックホール
![enter image description here](http://upload.wikimedia.org/wikipedia/commons/thumb/c/cd/Black_Hole_Milkyway.jpg/426px-Black_Hole_Milkyway.jpg)
>1960年代、**時空のもつ大域的構造の研究に取り組んだホーキングとペンローズによって証明された[特異点定理](http://ja.wikipedia.org/wiki/%E7%89%B9%E7%95%B0%E7%82%B9%E5%AE%9A%E7%90%86)**には、いくつかのヴァージョンがある。
簡単に説明すると、「光的捕捉面 (trapped null surface) が存在しエネルギー密度が負ではない場合、有限で延長不可能な測地線が存在する」というステートメントである。後半は**時空多様体における「特異点」の数学的な定義**である。ほとんど一般的な状況で成立するので、一般相対性理論のもとでは特異点の存在は避けられない、と理解してよい。ただし、特異点定理は、特異点の存在について述べるだけであり、特異点の形状や位置を特定するものではない。
物理法則の視点からは、**特異点の存在は、因果律を破壊する原因になる**ので避けたいものである。**ブラックホールなどの特異点は、事象の地平面で覆われる**ことで問題にならないが、事象の地平面で覆われない「裸の特異点」が出現すれば物理的に厄介である。ペンローズ はこの立場から、宇宙検閲官仮説 (cosmic censorship conjecture) を提唱した。自然界には裸の特異点は存在しないだろう、という予想である。しかし、この仮説の真偽については、明らかではなく、特殊な状況の数値シミュレーションでは裸の特異点が出現する、という報告もある。
##命令型プログラミングは情報のブラックホール 常に一方通行
##オブジェクト指向のカプセル化という情報のブラックホール 事象の地平面(イベント・ホライズン)のあちら側にある
![enter image description here](http://www.blogging4jobs.com/wp-content/uploads/2013/09/resume-black-hole-300x239.jpg)
カプセル化で隠蔽されているので情報は見えません。
事象の地平面(イベント・ホライズン)のあちら側にあります。
参照透過じゃない、ってことです。
##オブジェクト指向で`console.log`をやると情報がブラックホールに吸い込まれて消滅してしまう
特に、オブジェクトの中でも、
JavaScriptの`console`は、
UNIXの
- 標準出力 (standard output)、
- 標準エラー出力 (standard error)
を司る、オブジェクト指向のオブジェクトです。
事象の地平面(イベント・ホライズン)のあちらがわは
もはや論理世界の手が及ばない、
触るとコツンと反応がある物質世界にある物質です。
 
実際に、ChromeブラウザのDeveloperツールでやると、こうなります。
```
console.log('hello');
undefined
```
`hello`って`console.log`メソッドで引数を与えてやったのに、
**undefined**
なんと、`console`**オブジェクト様にガン無視されました!**
console.log関数の返り値は**undefined**です。
`hello`はもはや**論理世界では消滅してしまった**んですね。
仕方がありません。
これが**命令型プログラミング**というもので、
これが**オブジェクト指向**というものなんだから。
とにかくオブジェクトに向けてメッセージング=命令したいだけなので、
**関数の返り値なんてものを期待してはいけません。**
##事象の地平面(イベント・ホライズン)のあちら側
**情報伝達の境界面**、
**ここより先の情報を論理世界ではけして知ることができない**
事象の地平面(イベント・ホライズン)の**あちら側**のはなし。
consoleオブジェクトが司る**物質世界**では、
**標準出力 ディスプレイというハードウェア**で、突如、
`hello`が出力されました。
##プログラミングでやらかす副作用とは?
「副作用」っていう言葉を聞いて、
ギークではない一般人がまず思い浮かべるのは、
医薬品による[副作用](http://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8)だと思います。
>**副作用 (ふくさよう、Side Effect) とは、医薬品の使用に伴って生じた治療目的に沿わない作用全般を指す。**狭義には、医薬品の使用に伴って発現した好ましくないできごとのうち当該医薬品との因果関係が否定できないものを指す。この好ましくない作用を厳密に指す場合には、薬物有害反応(Adverse Drug Reaction:ADR)の用語が用いられる。一般に副作用といった場合には、両者が混合して用いられている。
[副作用 (プログラム)](http://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8_%28%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0%29)
は、医薬品の[副作用](http://ja.wikipedia.org/wiki/%E5%89%AF%E4%BD%9C%E7%94%A8)と言葉が同じで、根本は同じ概念です。
名は体をあらわす。
「副作用」の「作用」っていうのは、
プログラミング、数学でいう「関数」のことです。
[関数型プログラミングに目覚めた!IQ145で美少女JKの先輩から受けた特訓 5日間](http://qiita.com/kenokabe/items/618692858044a89adbc0)でも説明していますが、これを継承して説明します。
 
**数学・プログラミング用語として、英語ではfunction、それを和訳したものが関数**で、
そもそもの**functionとは、機能、動作、操作、作用、という意味**です。
![関数イメージ画像](http://upload.wikimedia.org/wikipedia/commons/thumb/3/3b/Function_machine2.svg/485px-Function_machine2.svg.png)
この図では、
x
↓f
f(x)
という構図になっています。
関数fは、何でもよくて、
たとえば炊飯器であるならば、
米と水
↓炊飯器
炊きたてのご飯 = 炊飯器(米と水)
時間変化に着目して、BeforeAfterで言い換えるならば、
Beforeが、様々な問題を抱えた家 
で「匠」という関数が作用する
After は、なんということでしょう!ってなるリフォームされた家
様々な問題を抱えた家 
↓匠
なんということでしょう!! = 匠(様々な問題を抱えた家)
医薬品が関数ならば、
病気の身体要素
↓医薬品
治癒された身体要素
です。
医薬品の**副作用**っていうのは、
病気の身体要素
↓医薬品
治癒された身体要素  + **作用対象外なのに作用変化した身体要素**
ってことです。
炊飯器の**副作用**であるならば、
米と水
↓炊飯器
炊きたてのご飯   + **勝手に隣の鍋のお湯が沸騰する**
劇的BeforeAfterの**副作用**ならば、
様々な問題を抱えた家 
↓匠
なんということでしょう!!  + **依頼されていない隣の家がリフォーム**
ってことです。
「関数」に
「入力」**されていない要素**が、
「出力」とも**関係ない場所で変化する**、っていうのが
「副作用」です。
コードで書くとこうなります。
```
var a = true;
var f = function(x)
{
a = false;
return x;
};
```
`x`
**↓** `f`
`x`
という作用に伴って、
この関数`f`に**入力もされていない、まったく関係のない**、
`a`が`true`から`false`に**変化**する**副作用がある**コードです。
##**論理世界**とまったく関係のない`console`オブジェクトの事象の地平面のあちら側の**物質世界**では`hello`が出現した、だから入出力(IO)っていうのは「副作用」
筆者がよく「論理世界」とか「物質世界」とか語ると、
露骨に嫌そうな顔(たぶんね)をして、
言葉遊びだ、などと文句を言う訳知り顔の人らがいますが、違います。
入出力(IO)っていうのは「副作用」っていうのは、
論理世界の中では完結し得ない話で、
UNIXの標準ストリームの末端のデバイス、
ソフトウェアと対になって存在する、
ハードウェアという物質世界の事象があってはじめて成立する話です。
##物質世界にあるオブジェクトという情報のブラックホールを、論理でカプセル化する 論理世界への一元化するパラダイムシフト
物質世界にあるオブジェクト`console`に`log`とかいうメソッドで命令(メッセージング)する方法(指向)はたいへん筋が悪い。
論理の世界であるはずのプログラミングで、
物質世界のオブジェクトなんてものが実体化して、
**物質の一元論のオブジェクト指向という世界観を**
**とっていることがそもそもの間違い。**
`console`というUNIX標準入出力の抽象デバイスがあるのは、
揺るぎない現実として認める他はない。
しかし、そこで開き直り「`console`オブジェクト様だ」とやって、
`hello`をガン無視するのは我慢ならない!
**そこでまったく逆の世界観を採用し、論理の一元論とする**
ように天動説からの地動説へ**パラダイムシフト**します。
**物質としてのオブジェクトのほうをカプセル化**して、**論理としてしまう。**
##JavaScriptのPromise
`console`オブジェクトをカプセル化するのは、
JavaScript-ES6で標準化される`Promise`で簡単に出来ます。
```js
var def = Promise.defer();
def.promise.then(console.log);
def.resolve('hello') // hello
```
`Promise.defer`
で、たしかに`console`は論理になって、
あとから論理的に`resolve`することで、
コンソールにhelloを表示できるようになった。
**物質としてのオブジェクトのほうをカプセル化**して、**論理としてしまう。**
という目的を達成したのですが、
`def.resolve('hello')`
の返り値を調べてみると、
undefined
であり、やはりガン無視されているのには変わらないようです。
こういうのはよろしくないです。
##HaskellのIOモナド
「Promiseはモナドである」と論証している人もいます。
[モナドについて考えた記録](http://javascripter.hatenablog.com/entry/2013/11/02/%E3%83%A2%E3%83%8A%E3%83%89%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6%E8%80%83%E3%81%88%E3%81%9F%E8%A8%98%E9%8C%B2)
>Promiseってなんとなくモナドっぽいなと思って、ジェネレータ使ってHaskellのdoを再現できないかなあというのがはじまり。結論からいうと、できない。
そもそもPromiseがモナドであるかを考える。
JavaScriptのコードで数学的に論証
>よって、Promiseはモナドである。
Promiseっていうのは、future, promise, delayというように、
当然もともと遅延評価の文脈で出てきたもので、
そして同時に、PromiseはFRPです。
正確には、FRPのごく一部の実装。
[Haskell IOモナド 超入門](http://qiita.com/7shi/items/d3d3492ddd90d47160f2)
に結構詳しく書かれているので参考になります。
##HaskellのIOモナドをJavaScriptで実装する
IOモナドをJavaScriptで
ものすごくシンプルにさっくりと実装してしまった人がいて、
[純粋関数型JavaScriptのつくりかた](http://qiita.com/hiruberuto/items/810ecdff0c1674d1a74e)
http://aurorscript.orionhub.org:8000/aurorscript.js
これが**めちゃくちゃ秀逸**で、たいへん感心してしまったのですが、
-----
### フレームワーク
>まずは `pure`, `bind`, `exec`
>という3つのシンプルな関数を実装します。
```js
var pure = a=>_=>a
// pure :: a -> IO a
var bind = m=>f=>_=>f(m())()
// bind :: IO a -> (a -> IO b) -> IO b
var exec = m=>m()
// exec :: IO a -> a
```
>`pure`, `bind`, `exec`は
>それぞれHaskellのIOモナドの
> `pure`, `>>=`, `unsafePerformIO`
> に相当します。
```js
var wrap = f=>a=>_=>f(a)
// wrap :: (a -> b) -> (a -> IO b)
```
この`wrap`関数をもって、
`console`オブジェクトをカプセル化します。
```
var put = wrap(console.log.bind(console))
// put :: a -> IO ()
```
そして、純粋論理を物質世界に結びつけるエントリポイント
`main`を準備しておきます。
```
// -- runtime --
Object.defineProperty(window, "main", { set: exec });
```
以上が、IOモナド・ライブラリとしての骨格になるみたいですね。
###Hello world
``` 
main = put('hello'); //hello
```
というように、`main`という物質世界へのエントリポイントに
破壊的代入かましてやることで、
あとはすべて純粋論理の世界でイミュータブルに参照透過、
副作用を起こす関数もない、ってことになります。
`hello`**はガン無視されるどころか、**
**物質世界**の`main`**へ値が返されるという構図になっており、**
**非常に美しい**です。
念の為ですが、あくまで論理世界に完結せず、
物質世界に副作用が起こったのは起こった
場合によっては
`main`じゃなくて
`realworld`とか`hardware`みたいに名前変えてもいいかもしれません。
個人的には是非その路線の短い名前の何かにしたいところです。
`IO`とか。
###数学的証明
>本当に参照透明な言語になっているか確認しておきましょう。
>JavaScriptに元から存在する副作用のある操作はすべて禁止されていますから、あとは先ほど定義したいくつかの関数が参照透明であるかどうか確認すれば充分です。
>たとえば `put` は関数ですが、`put("hoge")` というように呼び出しても、
```js
put("hoge") = wrap(console.log.bind(console))("hoge")
= (f=>a=>_=>f(a))(console.log.bind(console))("hoge")
= (a=>_=>console.log.bind(console)(a))("hoge")
= _=>console.log.bind(console)("hoge")
```
>ですから、単に関数(それがアクションです)が返ってくるだけで、何の副作用もありません。したがって`put`は参照透明な関数です。
>こんな感じで残りの関数も調べていくと、それぞれすべて参照透明な関数であることが確認できます。したがってAurorScript全体は参照透明です。
http://aurorscript.orionhub.org:8000/aurorscript.js
にはさらに網羅的な数学的な証明があります。
```
/*
A proof that (pure,bind) is a Monad:
law1: (return x) >>= f == f x
bind(pure(x))(f)
= (m=>f=>_=>f(m())())(pure(x))(f)
= (f=>_=>f(pure(x)())())(f)
= _=>f(pure(x)())()
= f((pure)(x)())
= f((a=>_=>a)(x)())
= f((_=>x)())
= f(x)
bind(pure(x))(f) = f(x)
law2: m >>= return == m
bind(m)(pure)
= (m=>f=>_=>f(m())())(m)(pure)
= ( f=>_=>f (m())()) (pure)
= ( _=>pure(m())())
= ( _=>(a=>_=>a)(m())())
= ( _=>(_=>(m())) ())
= ( _=>((m())) )
= _=>(m())
bind(m)(pure)() = m()
bind(m)(pure) = m
law3: (m >>= f) >>= g == m >>= (\x -> f x >>= g)
bind(bind(m)(f))(g)
= bind( bind (m) (f) )(g)
= bind( (n=>h=>_=>h(n())()) (m) (f) )(g)
= bind( ( h=>_=>h(m())()) (f) )(g)
= bind ( _=>f(m())()) (g)
= bind (_=>f(m())()) (g)
= (n=>h=>_=>h (n())()) (_=>f(m())()) (g)
= (n=>h=>_=>h( n ())()) (_=>f(m())()) (g)
= ( h=>_=>h( (_=>f(m())()) ())()) (g)
= ( _=>g( (_=>f(m())()) ())())
= ( _=>g( f(m())() )())
= (_=>g(f(m())())())
bind(m)(x => bind(f(x))(g))
= bind(m)(x => (n=>h=>_=>h(n())()) (f(x)) (g))
= bind(m)(x => (n=>h=>_=>h(n ())()) (f(x)) (g))
= bind(m)(x => ( h=>_=>h((f(x)) ())()) (g))
= bind(m)(x => ( h=>_=>h((f(x)) ())()) (g))
= bind(m)(x => ( _=>g((f(x)) ())()) )
= bind(m)(x => (_=>g((f(x))())()))
= (n=>h=>_=>h(n())()) (m) (x => (_=>g((f(x))())()))
= ( h=>_=>h(m())()) (x => (_=>g((f(x))())()))
= ( h=>_=>h (m())()) (x => (_=>g((f(x))())()))
= ( _=>(x => (_=>g((f(x))())())) (m())())
= ( _=>(x => (_=>g((f(x ))())())) (m())())
= ( _=>( (_=>g((f((m()) ))())())) ())
= (_=>((_=>g((f((m())))())()))())
= (_=>(_=>g(f(m())())())())
= (_=>g(f(m())())())
bind(bind(m)(f))(g) = bind(m)(x => bind(f(x))(g))
*/
```
##IOモナドは遅延評価とFRP(関数リアクティブプログラミング)の数学的基礎
[なにが起きてるの?](http://qiita.com/hiruberuto/items/810ecdff0c1674d1a74e#%E3%81%AA%E3%81%AB%E3%81%8C%E8%B5%B7%E3%81%8D%E3%81%A6%E3%82%8B%E3%81%AE)で作者が詳細に解説されているとおり、
これは遅延評価でFRPの実装です。
JavaScriptのPromiseが遅延評価の実装でFRPであるのとまったく同じです。
これまで筆者は、Haskellのモナドって、
遅延評価でFRPの実装だろうと漠然と思っていて、
FRPで代替できるのだから、
とさほど興味もなく掘り下げもしなかったのですが、
今回のこの秀逸なIOモナドJavaScriptライブラリを見て、
IOモナドは遅延評価、FRPの数学的基礎だったんだな、
と感銘を受けると共に認識した次第です。
##アロー表記じゃないnodejsで動作確認できるコード
JavaScriptのアロー表記は
```js
var pure = a=>_=>a
// pure :: a -> IO a
var bind = m=>f=>_=>f(m())()
// bind :: IO a -> (a -> IO b) -> IO b
var exec = m=>m()
// exec :: IO a -> a
var wrap = f=>a=>_=>f(a)
// wrap :: (a -> b) -> (a -> IO b)
```
nodeやChromeブラウザなどではまだ動かしにくいので、
以下、アロー表記じゃないnodejsで動作確認できるコードです。
展開してみると、このような論理をガンガン取り回すコードでは、
アロー表示で論理が簡潔に見通しがよく短く書ける事を痛感します。
```js
"use strict";
// -- stdlib --
var pure = function(a)
{
return function(_)
{
return a;
};
};
// pure :: a -> IO a
var bind = function(m)
{
return function(f)
{
return function(_)
{
return f(m())();
};
};
};
// bind :: IO a -> (a -> IO b) -> IO b
var exec = function(m)
{
return m();
};
// exec :: IO a -> a
var wrap = function(f)
{
return function(a)
{
return function(_)
{
return f(a);
};
};
};
// wrap :: (a -> b) -> (a -> IO b)
var put = wrap(console.log.bind(console));
// put :: a -> IO ()
var get = wrap(function(url)
{// get :: string -> IO string
var xhr = new XMLHttpRequest();
xhr.open("get", url, false);
xhr.send();
return xhr.responseText;
});
// -- runtime --
Object.defineProperty(global, "main",
{
set: exec
});
main = put('hello'); //hello
```
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment