Upgrade to Pro — share decks privately, control downloads, hide ads and more …

入門 関数型-ish プログラミング on Ruby

入門 関数型-ish プログラミング on Ruby

名古屋Ruby会議04に登壇したときの発表資料です。

大須演芸場の高座に登っての発表ということで、落語を意識した構成になっています。

初歩的な関数型プログラミングをRubyで実演する、入門向けの内容です。 「関数型プログラミングとはなにか」「Rubyで関数型プログラミングに使える道具」といった部分にもスライドを割いています。

この発表内容を作るまでの舞台裏をブログにまとめました。

kokuyouwind

June 08, 2019
Tweet

More Decks by kokuyouwind

Other Decks in Programming

Transcript

  1. $ whoami 森 俊介 / 黒曜 @kokuyouwind 株式会社Misoca ⼀応Ruby エンジニア

    最近はTerraform とかDocker を よく触っている
  2. 解2. メソッド名を固定 Proc, Lambda proc { | n | n

    * n }.call(2) lambda { | n | n * n }.call(2) Method Math.method(:sqrt).call(4) オブジェクトなので受け渡せる!
  3. 関数絡みの糖⾐構⽂ call proc.() proc[] lambda ->(x, y) { x +

    y } Method Math.:sqrt (Ruby 2.7 から導⼊)
  4. 関数絡みのメソッド カリー化 add = ->(x, y) { x + y

    }.curry # 引数を分けて適⽤できる add.(2).(3) # => 5 # 引数を⼀部だけ適⽤して使い回せる # ( 部分適⽤) add10 = add.(10) add10.(5) # => 15 add10.(100) #=> 110 1 2 3 4 5 6 7 8 9 10
  5. 関数絡みのメソッド カリー化 add = ->(x, y) { x + y

    }.curry # なぜか元と同じ呼び⽅もできる add.(2, 3) # => 5 # なぜか空call を何回も呼べる add.().().(2).().().(3) # => 5 1 2 3 4 5 6 7
  6. 関数絡みのメソッド 関数合成(Ruby 2.6) Math.:asin << Math.:sin Math.:acos >> Math.:cos double

    = ->(x) { x * 2 } add10 = ->(x) { x + 10 } # 2 倍してから10 を⾜す double_then_add_10 = double >> add1 # (1 * 2) + 10 = 12 double_then_add_10.(1) 1 2 3 4 5 6 7
  7. 関数絡みのメソッド 関数合成(Ruby 2.6) Math.:asin << Math.:sin Math.:acos >> Math.:cos double

    = ->(x) { x * 2 } add10 = ->(x) { x + 10 } # 10 を⾜してから2 倍する double_after_add_10 = double << add # (1 + 10) * 2 = 22 double_after_add_10.(1) 1 2 3 4 5 6 7
  8. 微分 2 点近似で求める f (x ) ≃ ′ 0 2h

    f(x +h)−f(x −h) 0 0 x0 x0-h x0+h f(x0-h) f(x0+h)
  9. 微分 f (x ) ≃ ′ 0 2h f(x +h)−f(x

    −h) 0 0 h = 1e-5 d = ->(f, x) { (f.(x + h) - f.(x - h)) / (2 }.curry 1 2 3 4
  10. 微分 f = ->(x) { x * x - 2

    * x + 1 } # d は関数を受け取って、1 次微分を返す関数 df = d.(f) # f' = 2x - 2 p df.(0) # => -1.9999999999964488 p df.(1) # => 0.0 1 2 3 4 5 6 7 8
  11. ニュートン法 x = n+1 x − n f (x )

    ′ n f(x ) n # 次の近似値を求める newton_step = ->(f, x0) { x0 - f.(x0) / d.(f).(0) }.curry # fix(f, x) は、収束するまでf の適⽤を繰り返す nearly = ->(x, y) { (x - y).abs < 1e-6 fix = ->(f, x) { nearly.(x, f.(x)) ? x : fix.(f, f.(x }.curry 1 2 3 4 5 6 7 8 9 10
  12. ニュートン法 # ニュートン法は、「次の近似値を求める」操作を # 収束するまで繰り返す newton = ->(f) { fix.(newton_step.(f))

    # 例: 次の関数が0 になる点を求める f = ->(x) { x*x - 2*x + 1 } # f(1) = 0 p newton.(f).(0) # => 0.9985865949537768 1 2 3 4 5 6 7 8 9
  13. 油断するとcall stack 死 # 収束のしきい値を 1e-6 から 1e-10 にしてみ nearly

    = ->(x, y) { (x - y).abs < 1e-1 fix = ->(f, x) { nearly.(x, f.(x)) ? x : fix.(f, f.(x }.curry newton = ->(f) { fix.(newton_step.(f)) f = ->(x) { x*x - 2*x + 1 } # => stack level too deep (SystemStack p newton.(f).(0) 1 2 3 4 5 6 7 8 9 10
  14. Web リクエスト パラメタのHash からモデルを作って検証 問題なければ保存する Model = Struct.new( :name, keyword_init:

    true ) validate = ->(model) { raise if model.name == '' } save = ->(model) { p model } 1 2 3 4 5 6 7 8
  15. Web リクエスト とりあえず素直に作ってみる request = ->(params) do model = Model.new(params)

    validate.(model) save.(model) rescue p :ng end # => #<struct Model name="test"> request.(name: 'test') # => :ng request.(name: '') 1 2 3 4 5 6 7 8 9 10 11 12
  16. Web リクエスト 関数合成にしてみる validate = ->(model) { raise if model.name

    == ''; model } request = ->(params) do (Model.:new >> validate >> save).(params) rescue p :ng end # => #<struct Model name="test"> request.(name: 'test') request.(name: '') # => :ng 1 2 3 4 5 6 7 8 9 10 11 12 13
  17. Web リクエスト 検証の成功/ 失敗を戻り値で表してみる Succ = Struct.new(:model) Fail = Struct.new(:message)

    validate = ->(model) { model.name == '' ? Fail.new(:not_valid) : Succ.new(model) } 1 2 3 4 5 6 7 8
  18. Web リクエスト save にはSucc かFail が渡ってくるので、 パターンマッチ(Ruby 2.7) で値を取り出す save

    = ->(maybe_model) { case maybe_model in Succ(model); p model in Fail(message); p message end } 1 2 3 4 5 6
  19. Web リクエスト request が関数合成だけでできる! すごい! request = Model.:new >> validate

    >> s request.(name: 'test') # => <struct Model name="test"> request.(name: '') # => :not_valid 1 2 3 4 5
  20. Web リクエスト Validate とSave をcall で呼べるオブジェクトにする class Validate def self.call(model)

    model.name == '' ? Fail.new(:not_valid) : Succ.new(mod end end class Save def self.call(maybe_model) case maybe_model in Succ(model); p model in Fail(message); p message end end end request = Model.:new >> Validate >> Save 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16