入門
関数型-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: '') # => :ngWebリクエスト
検証の成功/失敗を戻り値で表してみる
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_validWebリクエスト
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を繰り返すと型が行方不明 
関数型プログラミングするなら
型付けてぇ
入門 関数型-ish プログラミング on Ruby
By 黒曜
入門 関数型-ish プログラミング on Ruby
名古屋Ruby会議04
- 4,526
 
   
   
  