今は昔でもなくつい最近。とある西新宿の新宿グランドタワー38階に居た正直な40代のシステムエンジニアは34階に席を移してまだいました。←僕のことです。菊池です。
ということで、みなさんごきげんようご無沙汰しております。 2016年12月20日 金のプログラミング言語( gold-lang ) 以来の登場です。早期引退を目標にまだ現役でやってますよー。
40代も半ばを過ぎると昔話をしたくなるもので、紙に清書されたCOBOLをダム端末から入力して、JCL書いてマシン室行ってイエローリングを付けた磁気テープをセットしてストックフォームに結果をバチバチ出力したり、モデムのランプが定期的にピカピカ光るのを見て遠くのホストコンピューターからポーリングされてるのを目で見たり、そういえば静電気防止靴履いてたこともあったな。
プログラムは「順次」「選択」「繰り返し」の組み合わせで書けるよ!というとこから、時は流れてオブジェククト指向の波がやってきて、確か初めて読んだのがカモノハシが表紙のオブジェクト指向入門の本。カモノハシは哺乳類なのに卵を産むんですよ!この本を読んだことが無くても、なんとなく本の内容を想像できちゃいますよね。
もともとそこにあるものを似たもの同士で分類して整理すると、前よりずっとわかりやすくなるんですよね。それと、実は似たもの同士だったのか!というような発見もあったり。
ということで、今日は最近僕が知った 意外な似たもの同士 を紹介してみたいと思います。
似たもの紹介
足し算
いきなり突拍子もないものが出て来た感がありますが、足し算だけの式ってどこから計算しても結果は変わりませんよね。
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
他のクラスでも mappend
と mempty
が使えることを確認してみてください。それぞれ mappend
と mempty
できる仲間になりましたね。
さいごに
Ruby の世界でこういうのを使う機会はあまりなさそうなんですが、普段よく目にしているものも視点を変えると「実は同じ仲間だったのか!」そんな気づきの面白さが伝われば幸いです。興味のある人はモノイドや半群と単位元といったキーワードで調べてみるとさらに楽しめると思います。
そしてこれはほんの触りで、実はこの先にもっと楽しい世界が広がっているのだ!