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.

no_picture

@soudai1025 がFizzBuzzのエントリで再帰についてかいてた。なんか違和感を感じた。

@soudai1025 が書いたブログ記事にPythonでFizzBuzzとかしてみたというエントリーがあって、まーいろいろ、あって突っ込みをいれました。 この中で再帰に関する記述があります。 で最後はみんな大好き再帰。 折角なのでループ文を使わずに与えられたintまでの1からの和を出してみました。 def sum(num, answer = 0): answer = answer + num num -= 1 if num == 0: return answer return sum(num, answer) 「この再帰なんかおかしくね?」って直感的に思った。 おかしいというのはなんか複雑すぎないだろうか?ということである。answerって引数なくても実装できるよね。引き継ぎたい値がひとつしかないから戻り値で対応できる。 def sum(num): if num == 1: return 1 return num + sum(num-1) うん。これだ。これが正しい。 再帰を使う場合は、まず終了条件を考えます。この場合、num から 1へおりていくので、1で終了です。 次にのこったそれ以外のときのことを考えます。num と n-1 の和を足せば求める値がもとめられることに気づくことができればあとはそのままかくだけです。 再帰を使うと今作成している関数が動いている前提で考えることができます。そのあたりに慣れるとさくさくかけます。 再帰というのは スタック付きのループと見なせます。なのでループでできることはなんでも出きます。外のスコープの変数を引き継ぎたいときは、引数を増やせば伝搬できます。ただし、戻り値があるので、ひとつだけであれば伝搬可能です。この戻り値をリストにすることで複数の値を返すこともできます。 ちなみに、普通の関数プログラマの場合、こういうときは reduce を使います。 再帰より読みやすいですからね。 たぶん。

no_picture

@souda1025 に PythonでFizzBuzzとかしてみた に対抗しろって煽られたので。

@soudai1025 が書いたブログ記事にPythonでFizzBuzzとかしてみたというエントリーがあるのですが、Facebookでこういうコメントをみた。 多分、ひむひむが対抗してくるはず。 全力でお答えしましょう。 とりあえず、普通 FizzBuzz かくならこうかくだろう。 def fizzbuzz(number): if number % 15 == 0: # number % 5 == 0 and number % 3 == 0 return "FizzBuzz" elif number % 5 == 0: return "Buzz" elif number % 3 == 0: return "Fizz" else: return str(number) if __name__ == '__main__': number = int(raw_input("Please enter an integer: ")) print fizzbuzz(number) 数値を入れると 数値の文字列 か “Fizz” か “Buzz” か “FizzBuzz” を返す関数を用意するほうが柔軟性があり、わかりやすいです。 さて、もとのコードを確認していきましょう。 int = int(raw_input("Please enter an integer: ")) def do_fizz(int): if (int % 3) == 0: return 1 return 0 def do_buzz(int): if (int % 5) == 0: return 2 return 0 def do_answer(fizz, buzz): flag = fizz + buzz if flag == 0: print int #引数に居なくても外のintを参照出来る elif flag == 1: print "Fizz" elif flag == 2: print "Buzz" elif flag == 3: print "FizzBuzz" do_answer(do_fizz(int), do_buzz(int)) さて、気になる点をあげていこう。 do_answer 関数が外のスコープにアクセスしている。 よくわからないフラグ処理がされている。 do_answer の引数が意味不明。 関数が外のスコープにアクセスしている 関数が外のスコープにアクセスしてしまうとその関数だけみたときに他の部分を確認しないといけないのでよくない。 それぐらいなら引数を追加しましょう。 よくわからないフラグ処理がされている do_fizz と do_buzz が関数名から何をするのかさっぱりわからない。 do_fizz は 3で割り切れる場合 1 を返し、それ以外の場合は 0 を返す関数である do_buzz は 5で割り切れる場合 2 を返し、それ以外の場合は 0 を返す関数である ということはコードをよまなければわからない。ならば、関数の頭にコメントをかくか、そのような名前の関数にすべきだと思う。 do_ という接頭辞が着いている以上何かする関数だと想像するので、ここで print されているのであれば、まだ良いと思うけど, iPhoneで閲覧していたらこの命名のせいで混乱しました。 do_answer の引数が意味不明 fizz って何?