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

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

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

音樂は SoundCloud に公開中です。

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

Programming は GitHub で開發中です。

n人目の所業だがRubyでMaybe monad (Option monad) を作った

n人目の所業だがRubyでMaybe monad (Option monad) を作った。当たり前だが実用ではない (要らない)。
List monadを作るのは辛さうと云ふ丈の理由だ。HaskellScalaを参照した。

# 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っぽい何かが作れさうに見えたが、別のものができた。復た挑戦する。