入門
関数型-ish プログラミング
on Ruby
片月亭 黒曜
(@kokuyouwind)
$ whoami
-
森 俊介 / 黒曜
-
@kokuyouwind
-
株式会社Misoca
-
一応Rubyエンジニア
-
最近はTerraformとかDockerをよく触っている
片月亭
片月(へんげつ)= 弦月
片月亭(へんげつてい)?
片月見(かたつきみ)
中秋の名月(八月十五夜)と、
豆名月(九月十三夜)の
片方だけ見ること
片月亭(かたつきてい)
片月亭(かたつきてい)
け
型付けてぇ!!!
本日のお題
-
関数型プログラミングとは
-
Rubyの「関数」っぽいものの話
-
Rubyで「関数型プログラミング」っぽく書くとどうなるか
-
まとめ
本日のお題
-
関数型プログラミングとは
-
Rubyの「関数」っぽいものの整理
-
Rubyで「関数型プログラミング」っぽく書くとどうなるか
-
まとめ
入門
関数型-ish プログラミング
on Ruby
片月亭 黒曜
(@kokuyouwind)
🔥
※個人の見解です
関数型プログラミング
入力と出力の対応を定義する「関数」と、
それに基づく変換処理を行う「関数適用」とを
基本単位とするプログラミングパラダイム
3
incr
4
sqrt
2
関数型プログラミング
「関数」自体も、
「関数」の入力や出力になったり、
変数に束縛したりできる
(第一級関数)
[1, 4, 9]
sqrt
map
[1, 2, 3]
ちなみに
関数型プログラミング
Functional Programing
型システム
Type System
日本語だと「型」が入るが、
型システムとは関係ない
入門
関数型-ish プログラミング
on Ruby
片月亭 黒曜
(@kokuyouwind)
ish = 〜のような、〜っぽい
Rubyに「関数」はない
関数「っぽいもの」はある
関数「っぽいもの」を使った
プログラミングの話
本日のお題
-
関数型プログラミングとは
-
Rubyの「関数」っぽいものの話
-
Rubyで「関数型プログラミング」っぽく書くとどうなるか
-
まとめ
Ruby
「レシーバ」オブジェクトに対する
「メソッド呼び出し」が
処理の基本単位
-1
abs
1
関数との違い
関数は変数に束縛して、後から適用できる
オブジェクトは変数に束縛できるが、
メソッド呼び出しするには名前が必要
let abs2 = abs
abs2(-2) (* => 2 *)
abs2 = Math.abs # Error!!!
abs2 = Math
abs2.abs(-2)
# 大事なことなので2回(ry
関数っぽいものを作る際の問題点
関数適用→関数+引数
function(arg1, arg2, ...)
メソッド呼び出し→レシーバ+メソッド+引数
receiver.method(arg1, arg2, ...)
そもそも要素の数があわない!
解1. レシーバを固定
モジュール関数
Math.sqrt(4)
自身のメソッド呼び出し
[self.]private_method(1,2,3)
レシーバを意識せず、入力と出力にフォーカスできる
このままだと結局持ち運びはできない
解2. メソッド名を固定
Proc, Lambda
proc { | n | n * n }.call(2)
lambda { | n | n * n }.call(2)
Method
Math.method(:sqrt).call(4)
オブジェクトなので受け渡せる!
関数絡みの糖衣構文
-
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
油断するとcall stack死
# 収束のしきい値を 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)
数学以外の例
Webリクエスト
パラメタのHashからモデルを作って検証
問題なければ保存する
Model = Struct.new(
:name, keyword_init: true
)
validate = ->(model) {
raise if model.name == ''
}
save = ->(model) { p model }
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: '')
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
Webリクエスト
検証の成功/失敗を戻り値で表してみる
Succ = Struct.new(:model)
Fail = Struct.new(:message)
validate = ->(model) {
model.name == ''
? Fail.new(:not_valid)
: Succ.new(model)
}
Webリクエスト
saveにはSuccかFailが渡ってくるので、
パターンマッチ(Ruby 2.7)で値を取り出す
save = ->(maybe_model) {
case maybe_model
in Succ(model); p model
in Fail(message); p message
end
}
Webリクエスト
requestが関数合成だけでできる! すごい!
request = Model.:new >> validate >> save
request.(name: 'test')
# => <struct Model name="test">
request.(name: '') # => :not_valid
Webリクエスト
ValidateとSaveをcallで呼べるオブジェクトにする
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とかは関数を渡すのに近い
-
「操作の合成」の概念があると簡潔になる
-
わかったこと2
-
Lambdaは広域で束縛しにくい
-
定数に束縛はできるが、見た目が微妙
-
defに入れると毎回生成される
-
-
callを持ったオブジェクトを作って>>で
合成するのは面白そう -
パターンマッチ便利
-
関数合成やcurryを繰り返すと型が行方不明