圧倒亭グランパのブログ

30年後の自分にもわかるように書くブログ

Crystalのmacro紹介 "def_equals"

この記事は、 Crystal Advent Calendar 2017 の6日目の記事です。

crystalには便利なmacroがあります。macroを使えば、記述量も削減でき表現力も向上します。しかし、ただcrystalを書いているだけでは、なかなかそれらのmacroに出会うことができません。

ということで、アドベント・カレンダーの日数稼ぎも兼ねて、macroの紹介をしたいと思います。

そもそも「macroってなんだ」という場合は、下記の本家docsを参照してください。

Macros · GitBook

 

今回は、 def_equals を紹介します。

def_equals

crystal-lang.org/api - Object - def_equals

オブジェクトの同値性比較を行う == メソッドを定義します。この==メソッドは、

引数に与えたfieldに対して `==` での比較をし、全てtrueならtrueを返す

というものです。コードで見てみましょう。

通常、==の定義はこうです。

struct User
  def initialize(@name : String, @age : Int32)
  end

  # 同値性比較のメソッドを定義
  def ==(other : self)
    return false unless @name == other.@name
    return false unless @age == other.@age
    true
  end
end

user1 = User.new "Taro", 30
user2 = User.new "Taro", 30

p user1 == user2 # => true

これをmacroのdef_equalsを使うとこうなります。

struct User
  def initialize(@name : String, @age : Int32)
  end

  # 同値性比較のメソッドを定義
  def_equals @name, @age
end

user1 = User.new "Taro", 30
user2 = User.new "Taro", 30

p user1 == user2 # => true

とても簡単ですね。macro素晴らしい。

 

def_hash

同値比較がtrueを返すオブジェクト同士でも、それらがhashのkeyとして扱われた場合、「keyとして同値」とは判定されません。

class User
  def initialize(@name : String, @age : Int32)
  end

  # 同値性比較のメソッドを定義
  def_equals @name, @age
end

user1 = User.new "Taro", 30
user2 = User.new "Taro", 30

p user1 == user2 # => true

hash = {user1 => 1}

p hash.has_key?(user2) # => false

keyの比較には#hashメソッドが使用されるからです。そんな時は def_hash macroを使いましょう。

lass User
  def initialize(@name : String, @age : Int32)
  end

  # 同値性比較のメソッドを定義
  def_equals @name, @age
  # #hashメソッドを定義
  def_hash @name, @age
end

user1 = User.new "Taro", 30
user2 = User.new "Taro", 30

p user1 == user2 # => true

hash = {user1 => 1}

p hash.has_key?(user2) # => true

比較できるようになりました。

 

def_equals_and_hash

同値性やkeyの比較は、一緒に実装することが多いでしょう。そんな時は def_equalsdef_hash の両方の意味を持つ def_equals_and_hash を使います。

class User
  def initialize(@name : String, @age : Int32)
  end

  # 同値性 & key比較
  def_equals_and_hash @name, @age
end

user1 = User.new "Taro", 30
user2 = User.new "Taro", 30

p user1 == user2 # => true

hash = {user1 => 1}

p hash.has_key?(user2) # => true

簡単に定義できました。

ぜひ使ってみてください。