n人目の所業だがRubyでMaybe monad (Option monad) を作った。当たり前だが実用ではない (要らない)。
List monadを作るのは辛さうと云ふ丈の理由だ。HaskellとScalaを参照した。
# coding=utf-8 # license: Public Domain # Rubyでcurry化しない関数合成 http://c4se.hatenablog.com/entry/2014/07/27/140057 def compose *fs; proc{|*args| fs.inject(args){|args, f| f.call *args } }; end class Maybe include Comparable include Enumerable # <$> # @param [Proc<s,t>] f # @param [Maybe<s>] v # @return [Maybe<t>] def self.fmap f, v; v.nothing? ? v : new(f.call v.from_just); end # def self.fmap f, v; apply pure(f), v; end # def self.fmap f, v; bind v, compose(f, method(:return)); end class << self alias lift_a fmap alias lift_m fmap end # @param [s] v # @return [Maybe<s>] def self.pure v; new v; end class << self alias point pure alias return pure alias unit pure end # <*> # @param [Maybe<Proc<s,t>>] f # @param [Maybe<s>] v # @return [Maybe<t>] # def self.apply f, v; f.nothing? || v.nothing? ? new : new(f.from_just.call v.from_just); end def self.apply f, v; f.nothing? ? f : fmap(f.from_just, v); end # def self.apply f, v; bind f, ->(f){ fmap f, v }; end # >>= # @param [Maybe<s>] v # @param [Proc<s,Maybe<t>>] f # @return [Maybe<t>] def self.bind v, f; v.nothing? ? v : f.call(v.from_just); end class << self alias join bind end def initialize v = nil; @v = v; end def just?; @v != nil; end def nothing?; @v == nil; end def from_just; @v; end def <=> v v = v.from_just return false if @v.is_a?(Maybe) ^ v.is_a?(Maybe) @v <=> v end def === v; self == v; end def each &p; p.call @v; end def collect &p; self.class.fmap p, self; end alias map collect def ap f; self.class.apply f, self; end def try method_name, *args; map{ @v.method(method_name).call *args }; end end
applyを見れば、Applicative FunctorはほとんどFunctorの流儀である事がわかる。また、unit (return, pure, point) と join (bind) が有れば、fmap (lift_a, lift_m, map) とapply (ap) は簡単に作れるが、逆はできない事もわかる。又pureとapplyが有ればfmapを簡単に作れるが、逆は此れも復たできない。完膚無くFunctor < Applicative < Monadである。
testは此う。
require 'test/unit/assertions' include Test::Unit::Assertions assert !Maybe.new.just? assert Maybe.new.nothing? assert Maybe.new(42).just? assert !Maybe.new(42).nothing? assert_equal 42, Maybe.fmap(->(n){ n + 1 }, Maybe.new(41)).from_just assert Maybe.fmap(->(n){ n + 1 }, Maybe.new).nothing? assert !Maybe.pure(nil).just? assert Maybe.pure(nil).nothing? assert Maybe.pure(42).just? assert !Maybe.pure(42).nothing? assert_equal 42, Maybe.apply(Maybe.new(->(n){ n + 1 }), Maybe.new(41)).from_just assert_equal 42, Maybe.apply( Maybe.fmap(->(n, m){ n + m }.curry, Maybe.new(41)), Maybe.new(1) ).from_just assert Maybe.apply(Maybe.new, Maybe.new(42)).nothing? assert Maybe.apply(Maybe.new(->(n){ n + 1 }), Maybe.new).nothing? assert_equal 42, Maybe.bind(Maybe.new(41), ->(n){ Maybe.new n + 1 }).from_just assert Maybe.bind(Maybe.new, ->(n){ Maybe.new n + 1 }).nothing? assert_equal 42, Maybe.bind( Maybe.bind( Maybe.new(6), ->(n){ Maybe.new n + 1 } ), ->(n){ Maybe.new n * 6 } ).from_just assert Maybe.bind( Maybe.bind( Maybe.new(6), ->(n){ Maybe.new } ), ->(n){ Maybe.new n * 6 } ).nothing? assert_equal Maybe.new(42), Maybe.new(42) assert_not_equal Maybe.new(42), Maybe.new(41) assert_equal Maybe.new, Maybe.new assert_not_equal Maybe.new, Maybe.new(42) assert_not_equal Maybe.new(42), Maybe.new assert_not_equal Maybe.new(42), 42 assert_not_equal 42, Maybe.new(42) assert_not_equal Maybe.new(42), Maybe.new(Maybe.new(42)) assert_not_equal Maybe.new(Maybe.new(42)), Maybe.new(42)
各Functor則、Applicative則、Monad則をを確かめてみる。
id = ->(v){ v } succ = ->(n){ n + 1 } six_times = ->(n){ n * 6 } succ_m = ->(n){ n == nil ? Maybe.new : Maybe.new(n + 1) } six_m = Maybe.new(6) ans_m = Maybe.new(42) # Rubyでcurry化しない関数合成 http://c4se.hatenablog.com/entry/2014/07/27/140057 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 ans_m, Maybe.fmap(id, ans_m) # associative # 関数を合成してからFunctor値にfmapで適用した結果は、fmapで順番に関数を適用した結果に等しい。 # fmap (f . g) = fmap f . fmap g assert_equal Maybe.fmap(six_times, Maybe.fmap(succ, six_m)), Maybe.fmap(compose(succ, six_times), six_m) # Applicative則 # identityAp # Applicative値にApplicativeな恒等関数idを適用した結果は、元のApplicative値に等しい。 # pure id <*> v = v assert_equal ans_m, Maybe.apply(Maybe.pure(id), ans_m) # composition # pure (.) <*> u <*> v <*> w = u <*> (v <*> w) assert_equal Maybe.apply(Maybe.pure(compose succ, six_times), six_m), Maybe.apply(Maybe.pure(six_times), Maybe.apply(Maybe.pure(succ), six_m)) # 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_m.call(41), Maybe.bind(Maybe.return(41), succ_m) # leftIdentity # m >>= return == m assert_equal ans_m, Maybe.bind(ans_m, ->(v){ Maybe.return v }) # associativeBind # m >>= (\x -> k x >>= h) == (m >>= k) >>= h assert_equal Maybe.bind(Maybe.bind(Maybe.new(6), ->(n){ Maybe.new n + 1 }), ->(n){ Maybe.new n * 6 }), Maybe.bind(Maybe.new(6), ->(n){ Maybe.bind Maybe.new(n + 1), ->(n){ Maybe.new n * 6 } })
似た効果のActiveSupportのtryっぽいのは此う。Functorのfmapが有ればtryを実装できるとわかる。ActiveSupportで構はんと思ふが。
require 'active_support/all' assert_equal 42, 6.try(:+, 1).try(:* ,6) assert_equal nil, nil.try(:+, 1).try(:* ,6) assert_equal 42, Maybe.new(6).try(:+, 1).try(:* ,6).from_just assert Maybe.new.try(:+, 1).try(:* ,6).nothing?
Rubyでcurry化しない関数合成を見てゐたら連鎖性 concatenative なFactorっぽい何かが作れさうに見えたが、別のものができた。復た挑戦する。