c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud に公開中です。

考察は現在は主に Scrapbox で公表中です。

Programming は GitHub で開發中です。

Ruby自体をMaybe関手と見做して、ActiveSupportのtryを実装する

cf. n人目の所業だがRubyでMaybe monad (Option monad) を作った http://c4se.hatenablog.com/entry/2014/07/28/034752
cf. RubyにてMaybe関手を使って、ActiveSupportのtryを実装する http://c4se.hatenablog.com/entry/2014/08/02/222156
Rubynil checkを追究した。

関手 (Functor) とは

圏 (Category) とは対象 (object) と射 (morphism) の類 (class) であって、言い換へれば、なんらかの「対象」と名附けられるものとなんらかの「射」と名附けられるものが定められれば、其れを圏と呼べる。
大抵の場合は以下の有向グラフの様なものとおもへば足りる。対象を頂点とし射を辺とする。此の時、

  1. 圏に属する任意の二つの射 f:A→B, g:B→C に於いて h:A→C なる射hが必ず存在し、f・g=h として射の合成を定義できる。圏は射の合成に就いて閉じてゐる。
  2. 射の合成は結合的である。即ち任意の射に対して f・(g・h)=(f・g)・h が成立する。
  3. 全ての対象A, B, ...に就いて、射の合成の単位と成るやうな射id_A, id_B, ...が存在し、此れを「恒等射」と呼ぶ。

「対象」や「射」や「射の合成」は、然ふ名附けられればなんでもいいのであって、「対象」っぽくなくても「射」っぽくなくても構いやしない。尤も今回扱ふのはいかにも「射」っぽい射に成る。
cf. はじめての圏論 その第1歩:しりとりの圏 - 檜山正幸のキマイラ飼育記 http://d.hatena.ne.jp/m-hiyama/20060821/1156120185

関手とは、圏から圏への射の樣なものであり、圏C, Dに対してCの任意の対象cをDの対象F(c)に写し、Cの任意の射f:c→eをDの射F(f):F(c)→F(e)に写す対応付けを関手F:C→Dと言ふ。対象を写し、対象間の射の関係を保存するものを言ふのである。

Rubyに於ける関手とは

今回扱ふ圏をRubyと名付ける。HaskellではHaskと呼ぶらしいが、翻訳が面倒なのでRubyを使ふ。Rubyに於いて「型 (type)」とはなにかと云ふと、わたしにははっきりと答へられないが、此所ではRubyのclassと其の組み (tuple) をそのまま型と云ふ事にする。此の型を圏Rubyの対象とし、関数 (method) を圏Rubyの射とする。次のやうなclass ModNumberを定義すると、

class ModNumber
  def.self fmap f
    ->(*nums){ self.class.new f.call(*nums.map(&:to_i)) } 
  end

  def initialize n; @n = n; end

  def to_i; @n % 10; end
end

twice = ->(n){ n * 2 }
p twice.call(8)
p ModNumber.fmap(twice).call(ModNumber.new 8).to_i

method ModNumber#to_iは、ModNumberをFixnumに写す射である。まるで厳密ではないが、今回の目的には足る。
では関手とは何か。此の場合はModNumber.newとModNumber.fmapの組を以て関手と呼ぶ事が出来る。即ちRubyの部分圏FixnumとおなじくRubyの部分圏ModNumberに就いて、ModNumber.newは圏Fixnumの対象Fixnumを圏ModNumberの対象ModNumberに写し、ModNumber.fmapは関数Fixnum→FixnumをModNumber→ModNumberに写す事ができる。此れを、「ModNumberはFixnumからModNumberへの関手である」と言ふ。

Ruby自体をMaybe関手と見做す

n人目の所業だがRubyでMaybe monad (Option monad) を作ったに於いて作った関手Maybeは、圏Rubyから、圏Rubyの部分圏Maybeへの関手であった。然しRubyの事情を考へると此んな仕掛けは必要ない。先ずRubyにはnilが有る。且つRubyの型checkは実行時に行なはれる。此の辺りが前節で、Rubyの型の定義に就いて濁した理由であるが、Rubyの型に就いて正確な事を言おうとすれば、恐らく「或るmethodと引数に就いて洞んな観測できる反応 (返り値、例外) を返すかの総体を、其のobjectの型と言ふ」とでも言ふ他無いと思ふ。例へば==と言ふmethodに対してnilと云ふ引数を与へ、==(nil)がtrueを返すならば、其れは「==(nil)に対してtrueを返す型」だと言へる事に成る。此れは其のobjectがNilClassに属するか否かとは無関係に決まると言ってよい。
此う云った事情が有る為、Haskell等と違ってnil値をMaybeに閉じ込める意味が無い。ならば圏Ruby自体をMaybeの樣に扱へる筈である。

# coding=utf-8
# license: Public Domain

module Maybe
  def self.fmap f, v; v == nil ? nil : f.call(v); end

  def self.apply f, v; f == nil ? nil : fmap(f, v); end

  def self.pure v; v; end

  def self.return v; v; end

  def self.bind v, f; v == nil ? nil : f.call(v); end
end

対象→対象の関係は恒等関数に成る (pure, return)。射→射の関係は、nil checkを挟む関数に成る (fmap)。又、関手とMonadは等しく成る。pureやreturnが恒等関数だからだ。
Functor則、Applicative則、Monad則を満たす。

id = ->(v){ v }
succ = ->(n){ n + 1 }
six_times = ->(n){ n * 6 }
def compose *fs; proc{|*args| fs.inject(args){|args, f| f.call *args } }; end

# Functor則
# identity
# Functor値に恒等関数idをfmapで適用した結果は、元のFunctor値に等しい。
# fmap id  =  id
assert_equal 42, Maybe::fmap(id, 42)
# associative
# 関数を合成してからFunctor値にfmapで適用した結果は、fmapで順番に関数を適用した結果に等しい。
# fmap (f . g)  =  fmap f . fmap g
assert_equal Maybe::fmap(six_times, Maybe::fmap(succ, 6)),
             Maybe::fmap(compose(succ, six_times), 6)

# Applicative則
# identityAp
# Applicative値にApplicativeな恒等関数idを適用した結果は、元のApplicative値に等しい。
# pure id <*> v = v
assert_equal 42, Maybe::apply(Maybe::pure(id), 42)
# composition
# pure (.) <*> u <*> v <*> w = u <*> (v <*> w)
assert_equal Maybe::apply(Maybe::pure(compose succ, six_times), 6),
             Maybe::apply(Maybe::pure(six_times), Maybe::apply(Maybe::pure(succ), 6))
# homomorphism
# pure f <*> pure x = pure (f x)
assert_equal Maybe::pure(succ.(41)),
             Maybe::apply(Maybe::pure(succ), Maybe::pure(41))
# interchange
# u <*> pure y = pure ($ y) <*> u
assert_equal Maybe::apply(Maybe::pure(succ), Maybe::pure(41)),
             Maybe::apply(Maybe::pure(->(f){ f.(41) }), Maybe::pure(succ))

# Monad則
# rightIdentity
# return a >>= k  ==  k a
assert_equal succ.call(41), Maybe::bind(41, succ)
# leftIdentity
# m >>= return  ==  m
assert_equal 42, Maybe::bind(42, ->(v){ Maybe::return v })
# associativeBind
# m >>= (\x -> k x >>= h)  ==  (m >>= k) >>= h
assert_equal Maybe::bind(Maybe::bind(6, ->(n){ n + 1 }), ->(n){ n * 6 }),
             Maybe::bind(6, ->(n){ Maybe::bind (n + 1), ->(n){ n * 6 } })

全部自明だ。

Ruby自体をMaybe関手と見做して、ActiveSupportのtryを実装する

できる。

# coding=utf-8
# license: Public Domain

module PublicMethod
  def public_method name; respond_to?(name) ? super : nil; end
end
class Object; prepend PublicMethod; end

module Maybe
  def self.fmap f, v; v == nil ? nil : f.call(v); end

  def self.apply f, v; f == nil ? nil : fmap(f, v); end

  def self.pure v; v; end

  def self.return v; v; end

  def self.bind v, f; v == nil ? nil : f.call(v); end
end

class Object
  def try method_name, *args
    Maybe::fmap ->(f){ f.call *args },
      Maybe::fmap(->(v){ v.public_method method_name }, self)
  end

  def try! method_name, *args
    Maybe::fmap ->(v){ v.public_send method_name, *args }, self
  end
end

require 'test/unit/assertions'
include Test::Unit::Assertions

class Sample
  def f u, v; "f #{u} #{v}"; end

  def method_missing method_name, *args
    if method_name == :g
      "g #{args[0]} #{args[1]}"
    else
      super
    end
  end
end
assert_equal 'f a b', Sample.new.f('a', 'b')
assert_equal 'g a b', Sample.new.g('a', 'b')
assert_raise{ Sample.new.h 'a', 'b' }

assert_equal 42, 6.try(:+, 1).try(:* ,6)
assert_equal nil, nil.try(:+, 1).try(:* ,6)
assert_equal nil, 42.try(:nomethod)
assert_equal 'f a b', Sample.new.try(:f, 'a', 'b')
assert_equal nil, Sample.new.try(:g, 'a', 'b')
assert_equal nil, Sample.new.try(:h, 'a', 'b')
assert_equal 42, 6.try!(:+, 1).try!(:* ,6)
assert_equal nil, nil.try!(:+, 1).try!(:* ,6)
assert_raise{ 42.try! :nomethod }
assert_equal 'f a b', Sample.new.try!(:f, 'a', 'b')
assert_equal 'g a b', Sample.new.try!(:g, 'a', 'b')
assert_raise{ Sample.new.try! :h, 'a', 'b' }

tryの実装

Maybe::fmap ->(f){ f.call *args },
  Maybe::fmap(->(v){ v.public_method method_name }, self)

は、Monadを使へば

Maybe::bind self, -> v do
  Maybe::bind Maybe::return(v.public_method method_name),
    ->(f){ Maybe::return f.call(*args) }
end

と成る。