Slide 1

Slide 1 text

functionalなアプローチで 動的要素を排除する 2024/01/18 東京Ruby会議12 @ryopeko

Slide 2

Slide 2 text

● 関口亮一 ● @ryopeko ● Backend Engineer

Slide 3

Slide 3 text

目次 ● 動的なアプローチ ● Rubyにおける関数型向けの機能 ● functionalなアプローチ ● ユースケース ● 双方の比較 ● まとめ

Slide 4

Slide 4 text

動的なアプローチ

Slide 5

Slide 5 text

Rubyにおける強力な武器のひとつ

Slide 6

Slide 6 text

動的なアプローチのための機能 ● public_send ● define_method ● etc…

Slide 7

Slide 7 text

メタプログラミング + 動的評価

Slide 8

Slide 8 text

強力だが強い責任が伴う機能

Slide 9

Slide 9 text

動的なアプローチのメリットデメリット

Slide 10

Slide 10 text

動的なアプローチのメリット ● 記述量の低減 ● 多少の差分を吸収できる ● カッコいい ● etc…

Slide 11

Slide 11 text

動的なアプローチのデメリット ● 読み解くのに知識が必要 ● 実行時になって動作が確定する ● grepが効かないケースがある ● カッコいい ● etc…

Slide 12

Slide 12 text

動的なあれこれ class Foo %w/a b/.each do |c| define_method ("method_#{c}".to_sym) do # do something end end end

Slide 13

Slide 13 text

動的なあれこれ public_send(:method_name, "foo")

Slide 14

Slide 14 text

動的なあれこれ types = ["a", 1] types.each do |type| case type when String public_send(:method_for_string) when Integer public_send(:method_for_integer) else raise ArgumentError end end

Slide 15

Slide 15 text

Rubyにおける関数型向けの機能

Slide 16

Slide 16 text

Rubyにおける関数型向けの機能 ● Lambda, Proc ● curry (カリー化) ● Proc#<<,>>(関数合成) ● etc…

Slide 17

Slide 17 text

関数オブジェクトの生成 lambda {} Proc.new {} -> {}

Slide 18

Slide 18 text

カリー化 f = -> (arg1, arg2) { arg1 + arg2 }.curry(2).call(1) f.call(2) #=> 3

Slide 19

Slide 19 text

関数合成 f1 = -> (arg1, arg2) { arg1 + arg2 }.curry(2).call(1) f2 = -> (arg3) { arg3.odd? } f = f1 >> f2 f.call(2) #=> true

Slide 20

Slide 20 text

おまけ(method objectもcurry化できる) def method_a(arg1, arg2) # do something end f_method = method(:method_a).curry(2) f_method.call("foo").call("bar")

Slide 21

Slide 21 text

ユースケース

Slide 22

Slide 22 text

関数型アプローチを使うかどうか考えるポイント ● 安易に動的にしようとしてないか一度立 ち止まる ● 実行時でないと評価できない? ● 処理 or 定義

Slide 23

Slide 23 text

定義 or 処理

Slide 24

Slide 24 text

定義 or 処理 ● パイプライン的処理 ● 必要なデータが揃っていなくても 一部評価可能な場合(ちょっと説明がムズイ)

Slide 25

Slide 25 text

もう少し具体的な例

Slide 26

Slide 26 text

少し具体的な例 : 顧客データ import機能 A B C D 1 2 3 4 元データをサービスDBにimportしたいが 変換が必要なケース

Slide 27

Slide 27 text

A B C D 1 2 3 4 rows

Slide 28

Slide 28 text

A B C D 1 2 3 4 rows cols

Slide 29

Slide 29 text

A B C D 1 2 3 4 カラムごとの変換のルールは事前にわかる

Slide 30

Slide 30 text

A B C D 1 2 3 4 行ごとに各々のカラムを変換ルールに 基づき変換していく

Slide 31

Slide 31 text

定義としての変換ロジックの例

Slide 32

Slide 32 text

例1: デフォルト値をセットする処理 DEFAULT_VALUE = "foo" f = -> (default_value, value) { value.nil? ? default_value : value }.curry(2).call(DEFAULT_VALUE) f.call(nil) #=> "foo"

Slide 33

Slide 33 text

例2: 単純な変換をする処理 f = -> (value) { value.upcase } f.call("foo") #=> "FOO"

Slide 34

Slide 34 text

例3: マッピングを行う処理 RULES = { type1: { conversion: { “FOO” => "HOGE", “BAR” => "FUGA" } } }

Slide 35

Slide 35 text

例3: マッピングを行う処理 rule = RULES[:type1][:conversion] f = -> (conversion_rule, value) { conversion_rule[value] }.curry(2).call(rule) f.call(“foo”) #=> "hoge"

Slide 36

Slide 36 text

例4: 1,2,3全ての処理を行う処理 DEFAULT_VALUE = "foo" f1 = -> (default_value, value) { value.empty? ? default_value : value }.curry(2).call(DEFAULT_VALUE)

Slide 37

Slide 37 text

例4: 1,2,3全ての処理を行う処理 f1 = -> (default_value, value) { value.nil? ? default_value : value }.curry(2).call("foo") f2 = ->(value) { value.upcase } f3 = -> (conversion_rule, value) { conversion_rule[value] }.curry(2).call(rule)

Slide 38

Slide 38 text

例4: 1,2,3全ての処理を行う処理 f = f1 >> f2 >> f3 f.call(nil) #=> “HOGE”

Slide 39

Slide 39 text

アプローチの比較

Slide 40

Slide 40 text

動的なアプローチのメリットデメリット

Slide 41

Slide 41 text

動的なアプローチのメリットデメリット ● 記述が少なく繰り返しを排除できる ● コードを読むだけでは見通しが立ちにくい ● 実行されるまでどう処理されるかわからない ● grepが効かないことが多い

Slide 42

Slide 42 text

functionalなアプローチの メリットデメリット

Slide 43

Slide 43 text

functionalなアプローチのメリットデメリット ● 処理を定義としてアウトプット可能 ● 定義にデータを流すことでテスト可能 ● メタプロが発生しないのでgrep可能 ● パターンが網羅されていないと記述量で不利にな る場合がある(ただし...)

Slide 44

Slide 44 text

まとめ

Slide 45

Slide 45 text

まとめ ● メリットデメリットはどちらにもある ● 処理 or 定義でアプローチや表現は変わる ● Rubyといえば動的、なわけではない武器はたくさ んある