意外な似たもの同士

今は昔でもなくつい最近。とある西新宿の新宿グランドタワー38階に居た正直な40代のシステムエンジニアは34階に席を移してまだいました。←僕のことです。菊池です。

ということで、みなさんごきげんようご無沙汰しております。 2016年12月20日 金のプログラミング言語( gold-lang ) 以来の登場です。早期引退を目標にまだ現役でやってますよー。

40代も半ばを過ぎると昔話をしたくなるもので、紙に清書されたCOBOLをダム端末から入力して、JCL書いてマシン室行ってイエローリングを付けた磁気テープをセットしてストックフォームに結果をバチバチ出力したり、モデムのランプが定期的にピカピカ光るのを見て遠くのホストコンピューターからポーリングされてるのを目で見たり、そういえば静電気防止靴履いてたこともあったな。

プログラムは「順次」「選択」「繰り返し」の組み合わせで書けるよ!というとこから、時は流れてオブジェククト指向の波がやってきて、確か初めて読んだのがカモノハシが表紙のオブジェクト指向入門の本。カモノハシは哺乳類なのに卵を産むんですよ!この本を読んだことが無くても、なんとなく本の内容を想像できちゃいますよね。

もともとそこにあるものを似たもの同士で分類して整理すると、前よりずっとわかりやすくなるんですよね。それと、実は似たもの同士だったのか!というような発見もあったり。

ということで、今日は最近僕が知った 意外な似たもの同士 を紹介してみたいと思います。

f:id:AdwaysEngineerBlog:20180507173419p:plain

似たもの紹介

足し算

いきなり突拍子もないものが出て来た感がありますが、足し算だけの式ってどこから計算しても結果は変わりませんよね。

1 + 2 + 3 = 6
(1 + 2) + 3 = 6
1 + (2 + 3) = 6

ちなみに、足し算の世界の 0 って特別でどこに 0 を入れても結果に影響しません。

6 + 0 = 6
0 + 6 = 6

掛け算

ところでですよ、掛け算だけの式もどこから計算しても結果は変わりませんよね。

1 * 2 * 3 = 6
(1 * 2) * 3 = 6
1 * (2 * 3) = 6

ちなみに、掛け算の世界の 1 って特別でどこに 1 を入れても結果に影響しません。

1 * 6 = 6
6 * 1 = 6

文字列の連結

ここからプログラミングでおなじみの世界からいくつか紹介しますが、文字列の連結だけの式ってどこから連結しても結果はかわりませんよね。

"Hello " + "World" + "!" = "Hello World!"
("Hello " + "World") + "!" = "Hello World!"
"Hello " + ("World" + "!") = "Hello World!"

ちなみに、文字列の連結の世界の "" 空文字って特別でどこに "" 空文字を入れても結果に影響しません。

"" + "Hello World!" = "Hello World!"
"Hello World!" + "" = "Hello World!"

ハッシュテーブルのマージ

ハッシュテーブルっていうのは Ruby でいうところの Hash で { a: 1, b: 2 } とかのアレです。このハッシュテーブルの merge だけの式も同じですよね。どこから merge しても結果はかわりません。

{ a: 10, b: 2 }.merge(b: 20).merge(c: 30) = {:a=>10, :b=>20, :c=>30}
({ a: 10, b: 2 }.merge(b: 20)).merge(c: 30) = {:a=>10, :b=>20, :c=>30}
{ a: 10, b: 2 }.merge({ b: 20 }.merge(c: 30)) = {:a=>10, :b=>20, :c=>30}

このハッシュテーブルのマージだけの式で、どこに入れても結果に影響しない値とはいったい!? そうそれは {} 空のハッシュテーブルですよね。

{}.merge(:a=>10, :b=>20, :c=>30) = {:a=>10, :b=>20, :c=>30}
{:a=>10, :b=>20, :c=>30}.merge({}) = {:a=>10, :b=>20, :c=>30}

ブーリアン型の論理積

プログラミングの世界では基本となる true / false ですが、このブーリアン型の論理積だけの式って、どこから計算しても結果は変わりませんよね。

true && true && false = false
(true && true) && false = false
true && (true && false) = false

さて、この場合どこに入れても結果に影響しないブーリアン型の値って何でしょう?あるんでしょうか?あるんです。その値は true です。

true && true = true
true && false = false
false && true = false

ブーリアン型の論理和

ひょっとして?と思ってますよね。そうなんです、ブーリアン型の論理和だけの式ですがどこから計算しても結果は変わりませんよね。

true || true || false = true
(true || true) || false = true
true || (true || false) = true

この場合、どこに入れても結果に影響しない値とはいったい。そうです、その値は false です。

false || false = false
false || true = true
true || false = true

他にもあるので調べてみてね

  • リスト
  • 配列
  • 集合
  • ...

似たものの特徴をコードにしてみよう

これまで見て来たいくつかの例から

  • どこから計算しても結果が同じになる
  • 計算の途中に入れても結果に影響しない値がある

という特徴があるので、それぞれ

  • mappend
  • mempty

としてコードにしてみましょう。試しに Ruby のコードにしてみました。

#
# monoid.rb
#
# Created by Jun Kikuchi <kikuchi.jun@adways.net>
# Copyright (c) 2018 Jun Kikuchi. All rights reserved.
#
module Monoid
  def mappend(a)
    return if self.class == a.class
    raise "#{self.class}#{a.class} は違うので mappend できません!"
  end
  
  def self.mempty
    raise "実装してね!"
  end
end

class Integer
  # 足し算
  class Sum
    include Monoid

    attr_reader :val

    def initialize(n)
      @val = n
    end

    def mappend(a)
      super
      self.class.new(val + a.val)
    end

    def self.mempty
      new(0)
    end
  end

  # 掛け算
  class Product
    include Monoid

    attr_reader :val

    def initialize(n)
      @val = n
    end

    def mappend(a)
      super
      self.class.new(val * a.val)
    end

    def self.mempty
      new(1)
    end
  end
end

# 文字列の連結
class String
  include Monoid

  def mappend(a)
    super
    self + a
  end

  def self.mempty
    ""
  end
end

# ハッシュテーブルのマージ
class Hash
  include Monoid

  def mappend(a)
    super
    self.merge(a)
  end

  def self.mempty
    {}
  end
end

class Bool
  # ブーリアン型の論理積
  class Sum
    include Monoid

    attr_reader :val

    def initialize(b)
      @val = b
    end

    def mappend(a)
      super
      self.class.new(val && a.val)
    end

    def self.mempty
      new(true)
    end
  end

  # ブーリアン型の論理和
  class Product
    include Monoid

    attr_reader :val

    def initialize(b)
      @val = b
    end

    def mappend(a)
      super
      self.class.new(val || a.val)
    end

    def self.mempty
      new(false)
    end
  end
end

これを monoid.rb というファイルにして、試しに irb で実行してみましょう。

$ irb -r "./monoid.rb"
# 1 + 2 + 3
irb(main):001:0> Integer::Sum.new(1).mappend(Integer::Sum.new(2)).mappend(Integer::Sum.new(3)).val
=> 6

# (1 + 2) + 3
irb(main):002:0> (Integer::Sum.new(1).mappend(Integer::Sum.new(2))).mappend(Integer::Sum.new(3)).val
=> 6

# 1 + (2 + 3)
irb(main):003:0> Integer::Sum.new(1).mappend(Integer::Sum.new(2).mappend(Integer::Sum.new(3))).val
=> 6

# 0 + 6
irb(main):004:0> Integer::Sum.mempty.mappend(Integer::Sum.new(6)).val
=> 6

# 6 + 0
irb(main):005:0> Integer::Sum.new(6).mappend(Integer::Sum.mempty).val
=> 6

他のクラスでも mappendmempty が使えることを確認してみてください。それぞれ mappendmempty できる仲間になりましたね。

さいごに

Ruby の世界でこういうのを使う機会はあまりなさそうなんですが、普段よく目にしているものも視点を変えると「実は同じ仲間だったのか!」そんな気づきの面白さが伝われば幸いです。興味のある人はモノイドや半群と単位元といったキーワードで調べてみるとさらに楽しめると思います。

そしてこれはほんの触りで、実はこの先にもっと楽しい世界が広がっているのだ!