no_picture

内包表記とPythonと… - #LT駆動 21

LT駆動開発21でLTしてきました。 タイトル「内包表記とPythonと…Option」です。 PyCon Mini Hiroshima用に用意してたネタですが、参加できなかったので放出しました。 Pythonの内包表記でScalaのOptionのようなものをつくってみました。 Pythonの内包表記はScalaではfor式で表現ができます。 Scalaではリスト以外のものでもfor式が使えます。 代表格としてOptionが上げられます。 ということで、PythonにもOptionクラスをつくり内包表記で利用できるようにしてみました。 スライドに登場するように内包表記に対応するために、__iter__とnextメソッドを実装をしています。 class Option: def __init__(self, value): self.value = value def __iter__(self): return self def next(self): if self.value == None: raise StopIteration else: ret = self.value self.value = None return ret def __str__(self): if self.value == None: return "Nothing" else: return "Some(%d)" % self.value def add(x, y): return [ x_+ y_ for x_ in x for y_ in y ] add(Option(1),

no_picture

内包表記について、すごい合同勉強会で話した

すごい合同勉強会2014 in 広島でセッションしたので内容を公開しておく。 今回は「私がモナドの内包表記という名前を知った時の感覚を伝えよう」というのが目的でした。 さりげなく「私がモナドに感じている効能を伝える」というのもしているのですが、そこは本当にさりげなく。 内包表記。その意味を知らずに5年前ぐらいにpythonで利用していて、forやif文字通りにうけとっており、その動作を正しく理解できてないときがありました。 現在とその間にHaskellを学び、その5年前の自分に内包表記を伝えるにはという観点で話を進めました。 まず、リストの内包表記ですが、リストを生成を簡単にしてくれる機能です。 内包表記は、どうやら数学の集合の記法である内包的記法に由来するそうで、「関数プログラミング入門 ―Haskellで学ぶ原理と技法―」か何かで読んだ記憶があります。 その対になる記法として外延的記法があります。 これは具体的な中身を列挙する方法で、普段のリテラル表記ともみなすことができます。 リテラルで地道にかくのではなく、てプログラミングで自動生成しようというのが内包表記と言えそうです。 Haskellの内包表記は ジェネレータとガードと呼ばれる真偽値を並べることで作成します。 x <- [1..9] の部分がジェネレータです。あと真偽値を返す式がガードになります。 参考 3 Expressions - Haskell 2010 pythonではジェネレータとガードが for と if で表現されています。 直感的だし、キーワードの使いまわしとも言えそうです。 (関係ないけど、C++はキーワードの使いまわしたいへんそうだなぁって思った) あとはジェネレータを並べた際にどうなるか、というのがわかればリストの内包表記はうまく使えるのではないかと思います。 直積をとる。つまり、全部のパターンをつくる。 あとはフィルタで、致しているものを求めるだけですね。 そういえば、リストモナドでできることですね。複数答えがある場合にリストモナドを使うとすべての回答が得られます。 よく内包表記がmapやfilterと比較されることがありますが、そもそも同一に扱っても面白いことは特にない気がします。 目的しだいではmapやfilterを使うより便利だと考えてよいと思います。 蛇足ですが、モナドの有効性として、コードが斜めに述びる性質がある際に真っ直ぐに伸ばすことができるみたいなイメージを持っています。 それをさりげなく言っていたのですが、後ではなださんのセッションで実例がでてきました。 さて、ここまでくると内包表記とSQLの類似性が簡単に説明できるし、具体例にしやすいので、SQLと絡めた話をしました。 あとはモナドの内包表記へと一般化する話です。具体例のリストから、Maybeへと繋ぎ一般化して終わりです。 Rubyの例でflattenしている部分がありますが、あの辺はリストモナドがいつも勝手にやってくれてるところで、さりげなく強調していたりしますね。 モナドはなんか怖いとか言われますが、それはさておいて内包表記は便利なので知っておいて損はないと思います。 会場はわりとポカーンとしていましたが「誰かの何かに役に立てばいいなぁ」ということでスライドと簡単な解説を残しておきます。 登場したコード コピペしやすいように置いておきます。 主に対話環境用に。 Haskell [(x,y) | x <- [1..9], y <- [1..9], x * y == 24] [(x,y,z) | x <- [1..9], y <- [1..9], z <- [1..9], x * y * z == 24] :set -XTransformListComp [ (x,y) | x <- [1..9], y <- [1..9], x * y == 24, then take 2] :set -XTransformListComp :m GHC.Exts [ (x,y) | x <- [1..9], y <- [1..9], x * y == 24, then sortWith by y] :set -XMonadComprehensions [ (x,y) | x <- Just 3, y <- Just 8, x * y == 24] Python [(x,y) for x in range(1,10) for y in range(1,10) if x * y == 24] Ruby [*1..9].map do |x| [*1..9].map do |y| [x,y] end end.flatten(1).select do |x,y| x * y == 24 end [*1..9].map do |x| [*1..9].map do |y| [*1..9].map do |z| [x,y,z] end end end.flatten(2).select do |x,y,z| x * y * z == 24 end [*1..9].product([*1..9],[*1..9]).select do |x,y,z| x * y * z == 24 end SQL SELECT x,y FROM generate_series(1,9) AS X, generate_series(1,9) AS Y WHERE x * y = 24; SELECT x,y,z FROM generate_series(1,9) AS X, generate_series(1,9) AS Y, generate_series(1,9) AS Z WHERE x * y * z = 24; 参考文献 内包と外延 - Wikipedia 内包的記法の出展 - 集合 - Wikipedia 5.