奇妙なものを望む人のために:Pythonのモナド

良い一日!



最近、Haskellを研究し始めて、彼はモナドに近づこうと何度か試みましたが、彼らが言うように、すべてのnikikはスレッドをつかむことができませんでした(基本的な知識の不足かもしれません)。 素晴らしい本は、あなたが偉大な善のためにHaskellを学ぶのを助けました。

私はそれを読み、それに入り、同僚/友人にそれを伝えることにしました。 Pythonで開発しているので、少なくともfilter / map / reduceを超えて、「このすべての機能」を深く掘り下げる必要はないようです。 しかし、視野を広げることは間違いなく便利なので、Pythonでいくつかのモナドを実装することにしました。 もちろん、私はPythonベースのモナドの最初の実装でも最後の実装でもありませんが、いくつかの実装がありますが、私が出会った実装はすべて完全にPythonでないか、概念自体から遠く離れた人にとっては理解が難しいものでした。 私は自分の自転車を発明しなければなりませんでしたが、それはあなたが本質を把握することを可能にします...





すぐに注意しますが、私自身は主要な基礎であるカテゴリー理論の研究の非常に最初の段階にあるので、この実装は非常に単純です。 建設的な批判を期待しています(そして破壊的なものが役に立つでしょう)。



Haskellのモナドの道をたどりました。歴史的にモナドは、適用ファンクターの前に現れました。 私も(おそらくまだ)ファンクタ/アプリケーションファンクタを実装しました。 しかし、トピックが興味深いと思われる場合は試すことができます。



最初に、お気に入りのMaybeモナドを実装しました。 Pythonに、関数が失敗した結果として返す可能性のある値を持たせたい場合があります。 一般的なケースでは、このようなタスクに適していないものはありません-これはかなりの結果です。

多くの人が答えます:「Pythonには例外があります。」 私は同意しますが、ときどきtry / 100 / xなどの単純な表現を除いてラッピングすることは、やや面倒です。 これは、関数の結果をタプル(Bool、結果)のようなものでラップするオプションを頼みます。最初の項は実行の失敗の兆候です。 これは、多分モナドの文脈におけるモナドの意味が判明する場所です。 関数のこのような動作自体は便利な場合もありますが、もっと欲しい-バインディング(>> =)が必要です。 バインディングを使用すると、一連のアクションを介して逐次計算を実装できますが、各アクションは失敗する可能性があります。 同時に、シーケンスの各要素は前の要素の結果に従いません。バインディング操作自体は、前の手順が失敗した場合、後続のすべてを無意味としてスキップします。 これはすべてそのようなクラスになりました:



class _MaybeMonad(object): def __init__(self, just=None, nothing=False): self._just = just self._nothing = nothing def __rshift__(self, fn): if not self._nothing: res = fn(self._just) assert isinstance(res, _MaybeMonad), ( "In Maybe context function result must be Maybe") self._just = res._just self._nothing = res._nothing return self @property def result(self): return (self._nothing, self._just)
      
      







インスタンス化時に、コンストラクターは、コンテキストに該当する値(just = xを使用)または失敗した結果のサイン(nthing = Trueを使用)を受け取ります。 この場合、何も優先されず、論理的です。

モナド値(このクラスのインスタンス)からの結果は、既に説明したタプルの形式でresultプロパティを通じて取得できます。



このクラスを使用するには、いくつかの略語を使用する方が便利です。



 nothing = lambda: _MaybeMonad(nothing=True) just = lambda val: _MaybeMonad(just=val)
      
      





最初は失敗した結果を作成し、2番目は値で成功します



計算シーケンスの初期値をパッケージ化するために、エイリアスを作成しました。



 returnM = just
      
      







これで、モナドの結果を返す単純な関数を作成できます。



 def divM(value, divider): '''  .   . "" ''' if divider: return just(value // divider) return nothing() div100by = lambda x: divM(100, x) # ,    def sqrtM(value): if value >= 0: return just(math.sqrt(value)) return nothing()
      
      







バインダーを使用すると、次のことができます。

 do = returnM(4) >> div100by >> sqrtM # 4 -  error, result = do.result
      
      







この一連のアクションは、通常、パラメータ(math.sqrtがつまずく)の代わりに負の値をダイジェストします。 除算演算は通常、除数として0を取り、何も返しません()。これは式全体の結果です。

したがって、極端な場合には例外が残ります。



この機能を追加できますし、追加すべきです:

 lift = lambda fn: lambda val: just(fn(val))
      
      







怖いですが、使い方は簡単です:

lift(str)は、単に通常のstr()関数をコンテキストに「引っ張る」、つまり 通常の関数によって返される結果は、モナド値にパックされます。



最後に、部分的なアプリケーションカレーを追加します (ありがとう、 修正済み ):



 curried = lambda fn, *cargs: lambda *args: fn(*(cargs + args))
      
      







最後に、より包括的な例:



 def calc(x): do = ( returnM(x) >> curried(maybe_div, 100) >> lift(lambda x: x+75) >> lift(int) ) #   failed, result = do.result if failed: print "  ((" else: print ": %s" % result calc(0) #   100 / 0 calc(4)
      
      







特にラムダ上のラムダでは、非常にPython的ではないことが判明しましたが、これは修正可能です。

また、リストモナドの実装についても書きたかったのですが、これも私はとても気に入っていますが、1つの記事には少し多くのように思えました。 関心があります-記事があります。



All Articles