Esm lt threading_macro

Preview:

Citation preview

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話についてesm LT 2016/10/07

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

自己紹介▸ 久納 工▸ 2006 年入社▸ IT サービス事業部

Clojure▸ Clojure ( 発音は /'klouʒər/[2], クロージャー ) はプログラミング言語であり、 LISP 系の言語の方言の一つである。関数型プログラミングのプログラミングスタイルでのインタラクティブな開発を支援し、マルチスレッドプログラムの開発を容易化する汎用言語である。 Clojure 言語のプログラムは Java 仮想マシンと Microsoft .NET 共通言語ランタイムで動作する。 Clojure 言語は「データとしてのプログラムコード」 ( 英語 : 「 code as data 」 ) という思想で設計されており、洗練されたマクロ機構を持つ。

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

Wikipedia より

Clojure▸ Clojure ( 発音は /'klouʒər/[2], クロージャー ) はプログラミング言語であり、 LISP 系の言語の方言の一つである。関数型プログラミングのプログラミングスタイルでのインタラクティブな開発を支援し、マルチスレッドプログラムの開発を容易化する汎用言語である。 Clojure 言語のプログラムは Java 仮想マシンと Microsoft .NET 共通言語ランタイムで動作する。 Clojure 言語は「データとしてのプログラムコード」 ( 英語 : 「 code as data 」 ) という思想で設計されており、洗練されたマクロ機構を持つ。

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

Wikipedia より

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

スレッディングマクロとはClojure の S 式だと(reduce + 100 (map #(* %1 2) (filter even? [1 2 3 4])))

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

スレッディングマクロとはClojure の S 式だと(reduce + 100 (map #(* %1 2) (filter even? [1 2 3 4]))) こう実行されて

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

スレッディングマクロとはClojure の S 式だと(reduce + 100 (map #(* %1 2) (filter even? [1 2 3 4])));;=> 112

こうなる

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

スレッディングマクロとはスレッディングマクロだと(->> [1 2 3 4] (filter even?) (map #(* %1 2)) (reduce + 100))

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

スレッディングマクロとはスレッディングマクロだと(->> [1 2 3 4] (filter even?) (map #(* %1 2)) (reduce + 100))

こう実行されて

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

スレッディングマクロとはスレッディングマクロだと(->> [1 2 3 4] (filter even?) (map #(* %1 2)) (reduce + 100));;=> 112

こうなる

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

スレッディングマクロの利点スレッディングマクロ(->> [1 2 3 4] (filter even?) (map #(* %1 2)) (reduce + 100))

S 式のみ(reduce + 100 (map #(* %1 2) (filter even? [1 2 3 4])))

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

スレッディングマクロの利点スレッディングマクロ(->> [1 2 3 4] (filter even?) (map #(* %1 2)) (reduce + 100))

S 式のみ(reduce + 100 (map #(* %1 2) (filter even? [1 2 3 4])))

スレッディングマクロを使うと評価の時系列順に上から読むことが出来る

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

他にもあるスレッディングマクロの利点Clojure の S 式だと(reduce + 100 (map #(* %1 2) (filter even? [1 2 3 4])))

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

他にもあるスレッディングマクロの利点Clojure の S 式だと(reduce + 100..(map #(* %1 2)....(filter even?......[1 2 3 4])))

インデントはこうなる

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

他にもあるスレッディングマクロの利点スレッディングマクロだと(->> [1 2 3 4] (filter even?) (map #(* %1 2)) (reduce + 100))

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

他にもあるスレッディングマクロの利点スレッディングマクロだと(->>..[1 2 3 4]..(filter even?)..(map #(* %1 2))..(reduce + 100))

インデントはこうなる

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

他にもあるスレッディングマクロの利点スレッディングマクロ(->> [1 2 3 4] (filter even?) (map #(* %1 2)) (reduce + 100))

S 式のみ(reduce + 100 (map #(* %1 2) (filter even? [1 2 3 4])))

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

他にもあるスレッディングマクロの利点スレッディングマクロ(->>..[1 2 3 4]..(filter even?)..(map #(* %1 2))..(reduce + 100))

S 式のみ(reduce + 100..(map #(* %1 2)....(filter even?......[1 2 3 4])))

コードのインデントが深くならない

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

スレッディングマクロの利点

他にも有るけど割愛

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話についてesm LT 2016/10/07

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話についてesm LT 2016/10/07

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

第二部

esm LT 2016/10/07

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

Ruby のラムダ式で S 式相当の処理を書くと…

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

Ruby のラムダ式で S 式相当の処理を書くと…-> (collection) { collection.reduce(100, :+) }. call(-> (collection) { collection.map {|e| e * 2 } }. call(-> (collection) { collection.select(&:even?) }. call([1, 2, 3, 4])))

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

Ruby のラムダ式で S 式相当の処理を書くと…-> (collection) { collection.reduce(100, :+) }. call(-> (collection) { collection.map {|e| e * 2 } }. call(-> (collection) { collection.select(&:even?) }. call([1, 2, 3, 4])))

これをスレッディングマクロっぽく書けるようにする

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

基本のアイディア[ -> (collection) { collection.select(&:even?) }, -> (collection) { collection.map {|e| e * 2 } }, -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

基本のアイディア[ -> (collection) { collection.select(&:even?) }, -> (collection) { collection.map {|e| e * 2 } }, -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}

Proc の配列に対して

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

基本のアイディア[ -> (collection) { collection.select(&:even?) }, -> (collection) { collection.map {|e| e * 2 } }, -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}

Proc の配列に対して

reduce して、処理結果に順々に適用する

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

基本のアイディア[ -> (collection) { collection.select(&:even?) }, -> (collection) { collection.map {|e| e * 2 } }, -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}=> 112

Proc の配列に対して

reduce して、処理結果に順々に適用する

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

とりあえずここまで実装してみるdef thread_last(*procs) procs[1..-1].reduce(procs.first) {|acc, proc| proc.call acc }end

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

とりあえずここまで実装してみるdef thread_last(*procs) procs[1..-1].reduce(procs.first) {|acc, proc| proc.call acc }end

thread_last( [1,2,3,4], -> (collection) { collection.select(&:even?) }, -> (collection) { collection.map {|e| e * 2 } }, -> (collection) { collection.reduce(100, :+) })

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

とりあえずここまで実装してみるdef thread_last(*procs) procs[1..-1].reduce(procs.first) {|acc, proc| proc.call acc }end

thread_last( [1,2,3,4], -> (collection) { collection.select(&:even?) }, -> (collection) { collection.map {|e| e * 2 } }, -> (collection) { collection.reduce(100, :+) })

=> 112

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

とりあえずここまで実装してみるdef thread_last(*procs) procs[1..-1].reduce(procs.first) {|acc, proc| proc.call acc }end

thread_last( [1,2,3,4], -> (collection) { collection.select(&:even?) }, -> (collection) { collection.map {|e| e * 2 } }, -> (collection) { collection.reduce(100, :+) })

=> 112 出来た!

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

基本のアイディア[ -> (collection) { collection.select(&:even?) }, -> (collection) { collection.map {|e| e * 2 } }, -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

こんなときはどうする?[ -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n } }, -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

こんなときはどうする?[ -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n } }, -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)} n を指定したい

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

こんなときはどうする?[ -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n } }, -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}ArgumentError: wrong number of arguments (given 1, expected 2)

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

こんなときはどうする?[ -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n } }, -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}ArgumentError: wrong number of arguments (given 1, expected 2)

一つの引数にだけProc を適用している

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

こんなときは[ -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n } }, -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

こんなときはこうする![ -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n } }.curry[2], -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

こんなときはこうする![ -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n } }.curry[2], -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}=> 112

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

こんなときはこうする![ -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n } }.curry[2], -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}

curry とは?

60sec で分かる ( と良いなと思う )カリー化と部分適用

60sec で分かる ( と良いなと思う ) カリー化と部分適用

カリー化とは関数がただ一つの引数だけをとるようにすること。この状態の関数をカリー化されていると呼ぶ。

2引数の関数はカリー化することで、引数の数が一つである関数と、その関数を返す引数の数が一つの関数に分けられる。

60sec で分かる ( と良いなと思う ) カリー化と部分適用

カリー化とはRuby で書くと…->(x, y) { x + y }.call(2, 3)=> 5

60sec で分かる ( と良いなと思う ) カリー化と部分適用

カリー化とはRuby で書くと…->(x, y) { x + y }.call(2, 3)=> 5

->(x) { -> (y) { x + y } }.call(2).call(3)=> 5

カリー化 ( 手動 )

60sec で分かる ( と良いなと思う ) カリー化と部分適用

カリー化とはRuby で書くと…->(x, y) { x + y }.call(2, 3)=> 5

->(x, y) { x + y }.curry[2][3]=> 5

カリー化

60sec で分かる ( と良いなと思う ) カリー化と部分適用

カリー化とはRuby で書くと…->(x, y) { x + y }.call(2, 3)

->(x, y) { x + y }.curry[2][3]

``-> (y) { 2 + y }`` 相当の Proc が返ってくる

カリー化

60sec で分かる ( と良いなと思う ) カリー化と部分適用

カリー化とは任意の数の引数の Proc をカリー化出来る

->(x, y, z) { x + y + z }.curry[2][3][4]=> 9->(x, y, z, xx, yy, zz) { x + y + z + xx + yy + zz }.curry[2][3][4][5][6][7]=> 27

60sec で分かる ( と良いなと思う ) カリー化と部分適用

カリー化とは途中で変数に取ることも出来る

partialized_proc = ->(x, y) { x + y }.curry[2]=> #<Proc:0x007feaa2f4c6f0 (lambda)>

partialized_proc[3]=> 5

60sec で分かる ( と良いなと思う ) カリー化と部分適用

カリー化とは途中で変数に取ることも出来る

partialized_proc = ->(x, y) { x + y }.curry[2]=> #<Proc:0x007feaa2f4c6f0 (lambda)>

partialized_proc[3]=> 5全ての引数が渡されると処理結果が返ってくる

60sec で分かる ( と良いなと思う ) カリー化と部分適用

部分適用とは部分適用とは、関数に対して全ての引数を一度に渡さず、一部の引数だけ渡すことができる仕組み。

60sec で分かる ( と良いなと思う ) カリー化と部分適用

部分適用とはpartialized_proc = ->(x, y) { x + y }.curry[2]=> #<Proc:0x007feaa2f4c6f0 (lambda)>partialized_proc[3]=> 5

60sec で分かる ( と良いなと思う ) カリー化と部分適用

部分適用とはpartialized_proc = ->(x, y) { x + y }.curry[2]=> #<Proc:0x007feaa2f4c6f0 (lambda)>partialized_proc[3]=> 5これが部分適用された状態の Proc

60sec で分かる ( と良いなと思う )カリー化と部分適用

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

⚠ 注意事項

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

⚠ 注意事項Clojure ではスレッディングマクロの実現にTransducers という機構を使用しています。

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

⚠ 注意事項Clojure ではスレッディングマクロの実現にTransducers という機構を使用しています。

が、ここでは Transducers については触れません。

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

この書き方を[ -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n } }.curry[2], -> (collection) { collection.reduce(100, :+) }].reduce([1, 2, 3, 4]) { |acc, proc| proc.call(acc)}

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

thread_last に適用すると…thread_last( [1,2,3,4], -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n }.curry[2] }, -> (collection) { collection.reduce(100, :+) })

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

thread_last に適用すると…thread_last( [1,2,3,4], -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n }.curry[2] }, -> (collection) { collection.reduce(100, :+) })=> 112

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

thread_last に適用すると…thread_last( [1,2,3,4], -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n }.curry[2] }, -> (collection) { collection.reduce(100, :+) })=> 112 利用者が curry しないといけない

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

いちいち curry したくない……

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

いちいち curry したくない……class Proc def |(arg) self.curry[arg] endend

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

いちいち curry したくない……class Proc def |(arg) self.curry[arg] endend

Proc に | メソッドを生やす

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

いちいち curry したくない……class Proc def |(arg) self.curry[arg] endend

thread_last( [1,2,3,4], -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n } } | 2, -> (collection) { collection.reduce(100, :+) })

Proc に | メソッドを生やす

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

いちいち curry したくない……class Proc def |(arg) self.curry[arg] endend

thread_last( [1,2,3,4], -> (collection) { collection.select(&:even?) }, -> (n, collection) { collection.map {|e| e * n } } | 2, -> (collection) { collection.reduce(100, :+) })

=> 112

Proc に | メソッドを生やす

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

thread_last(->>) が出来た!

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

thread_last(->>) が出来た!

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

色々なスレッディングマクロ‣ ->‣ ->>‣ as->‣ some->‣ some->>‣ cond->‣ cond->>

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

色々なスレッディングマクロ‣ ->‣ ->>‣ as->‣ some->‣ some->>‣ cond->‣ cond->>

まだまだ戦いは続く……

▸ 一番最初の値に Proc が来ることもある。ので、最初が Proc ならば実行する。もちろん引数の数が不定なのでcall ではなく curry で。

▸ Proc に生やした | メソッドで即時 curry していたけど、 thread_first(->) のときは実行する寸前までカリー化したくないので、実行時まで配列で持っているようにした。▸ thread_as(as->) するときは引数の挿入場所を選べる。シンボルで表現したいが Proc の結果がシンボルになることも有るので、表現しきれない。ので Proc に新たなメソッド & を生やした。かつ、挿入位置を表す型を用意した。▸ map とか reduce とかするラムダ式を一々書きたくない……ので、引数で受け取った Collection をレシーバーとして map を実行するメソッド _map(proc) が include したクラスの self に生えるようにした。 reduce,

select に関しても同様。▸ ↑ で作ったメソッドで、 Proc を渡したいときと Block を渡したいときがある。

▸ Clojure の ->> と同じようなメソッドのエイリアスを設けたいが、 - 始まりのメソッドは書けない。マルチバイトはいけるので、色々考えた結果これが一番近いと思う。 `` ー✈✈ ``

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

戦いの記録 ( ダイジェスト版 )

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

色々やった結果-> (collection) { collection.reduce(100, :+) }. call(-> (collection) { collection.map {|e| e * 2 } }. call(-> (collection) { collection.select(&:even?) }. call([1, 2, 3, 4])))

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

色々やった結果-> (collection) { collection.reduce(100, :+) }. call(-> (collection) { collection.map {|e| e * 2 } }. call(-> (collection) { collection.select(&:even?) }. call([1, 2, 3, 4])))

thread_last( [1, 2, 3, 4], _select(&:even?), _map(-> (e) { e * 2 }), _reduce(100, &:+))

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

色々やった結果-> (collection) { collection.reduce(100, :+) }. call(-> (collection) { collection.map {|e| e * 2 } }. call(-> (collection) { collection.select(&:even?) }. call([1, 2, 3, 4])))

thread_last( [1, 2, 3, 4], _select(&:even?), _map(-> (e) { e * 2 }), _reduce(100, &:+)) ここまで短く簡潔に書ける!!!

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

なお……

thread_last( [1, 2, 3, 4], _select(&:even?), _map(-> (e) { e * 2 }), _reduce(100, &:+))

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

なお……

thread_last( [1, 2, 3, 4], _select(&:even?), _map(-> (e) { e * 2 }), _reduce(100, &:+))

このコードをラムダ式を使わずに普通に書くと……

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

なお……[1, 2, 3, 4]. select(&:even?). map {|e| e * 2}. reduce(100, :+)

thread_last( [1, 2, 3, 4], _select(&:even?), _map(-> (e) { e * 2 }), _reduce(100, &:+))

このコードをラムダ式を使わずに普通に書くと……

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

結論。

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

結論。

素の Ruby すごい

Clojure のスレッディングマクロっぽいものを Ruby で実装してみた話について

第二部

esm LT 2016/10/07