森 俊介 / 黒曜
@kokuyouwind
株式会社Misoca
一応Rubyエンジニア
最近はTerraformとかDockerをよく触っている
中秋の名月(八月十五夜)と、
豆名月(九月十三夜)の
片方だけ見ること
関数型プログラミングとは
Rubyの「関数」っぽいものの話
Rubyで「関数型プログラミング」っぽく書くとどうなるか
まとめ
関数型プログラミングとは
Rubyの「関数」っぽいものの整理
Rubyで「関数型プログラミング」っぽく書くとどうなるか
まとめ
関数型プログラミングとは
Rubyの「関数」っぽいものの話
Rubyで「関数型プログラミング」っぽく書くとどうなるか
まとめ
「レシーバ」オブジェクトに対する
「メソッド呼び出し」が
処理の基本単位
let abs2 = abs
abs2(-2) (* => 2 *)
abs2 = Math.abs # Error!!!
abs2 = Math
abs2.abs(-2)
# 大事なことなので2回(ry
call
proc.()
proc[]
lambda
->(x, y) { x + y }
Method
Math.:sqrt (Ruby 2.7から導入)
カリー化
add = ->(x, y) { x + y }.curry
# 引数を分けて適用できる
add.(2).(3) # => 5
# 引数を一部だけ適用して使い回せる
# (部分適用)
add10 = add.(10)
add10.(5) # => 15
add10.(100) #=> 110
カリー化
add = ->(x, y) { x + y }.curry
# なぜか元と同じ呼び方もできる
add.(2, 3) # => 5
# なぜか空callを何回も呼べる
add.().().(2).().().(3) # => 5
関数合成(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 >> add10
# (1 * 2) + 10 = 12
double_then_add_10.(1)
関数合成(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 << add10
# (1 + 10) * 2 = 22
double_after_add_10.(1)
関数型プログラミングとは
Rubyの「関数」っぽいものの話
Rubyで「関数型プログラミング」っぽく書くとどうなるか
まとめ
関数の出力を0にするような入力(の近似)を求めるアルゴリズム
適当な初期値から初めて、接線とx軸の交点を次の近似値にする
2点近似で求める
x0
x0-h
x0+h
f(x0-h)
f(x0+h)
h = 1e-5
d = ->(f, x) {
(f.(x + h) - f.(x - h)) / (2 * h)
}.curry
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
# 次の近似値を求める
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
# ニュートン法は、「次の近似値を求める」操作を
# 収束するまで繰り返す
newton = ->(f) { fix.(newton_step.(f)) }
# 例: 次の関数が0になる点を求める
f = ->(x) { x*x - 2*x + 1 }
# f(1) = 0
p newton.(f).(0) # => 0.9985865949537768
# 収束のしきい値を 1e-6 から 1e-10 にしてみる
nearly = ->(x, y) { (x - y).abs < 1e-10 }
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 (SystemStackError)
p newton.(f).(0)
Model = Struct.new(
:name, keyword_init: true
)
validate = ->(model) {
raise if model.name == ''
}
save = ->(model) { p model }
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: '')
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
Succ = Struct.new(:model)
Fail = Struct.new(:message)
validate = ->(model) {
model.name == ''
? Fail.new(:not_valid)
: Succ.new(model)
}
save = ->(maybe_model) {
case maybe_model
in Succ(model); p model
in Fail(message); p message
end
}
request = Model.:new >> validate >> save
request.(name: 'test')
# => <struct Model name="test">
request.(name: '') # => :not_valid
class Validate
def self.call(model)
model.name == '' ? Fail.new(:not_valid) : Succ.new(model)
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
関数型プログラミングとは
Rubyの「関数」っぽいものの話
Rubyで「関数型プログラミング」っぽく書くとどうなるか
まとめ
Rubyで無理に関数型プログラミングは
しなくてよいのでは???
素直に関数型プログラミング言語を使おう
流儀を知ってると応用できることがある
mapとかreduceとかは関数を渡すのに近い
「操作の合成」の概念があると簡潔になる
Lambdaは広域で束縛しにくい
定数に束縛はできるが、見た目が微妙
defに入れると毎回生成される
callを持ったオブジェクトを作って>>で
合成するのは面白そう
パターンマッチ便利
関数合成やcurryを繰り返すと型が行方不明