LoginSignup
141
113

More than 1 year has passed since last update.

Python使いがRubyを触るために理解するべきこと

Last updated at Posted at 2021-05-20

概要

スクリプト言語的なものとしては、もっぱらPythonを使っていたが、
このごろRubyを初めてややガッツリ触る機会があった。

すぐ慣れるだろうと甘くみていたら、予想以上にクセが強くて苦戦したので、
Python使いの視点からRubyの特徴を簡単にメモっておきたいと思う。

まだまだ勉強不足なので網羅的な説明にはならないと思うが、
最低限これだけ把握すれば大体不都合なくRubyを読み書きできるというポイントをまとめる。

省略文化

Pythonと似たようなものだろうと思っていた自分にまず面食らわせてきたのがこれ。
イカした特徴だと思うが、慣れないと大変。

returnの省略

returnは省略可。
例: ゲッターメソッド

def name
  @name
end

メソッドの最後の行とは限らない。
例: case文

def hoge(arg)
  case arg
  when A
    "Hoge"
  when B
    "Fuga"
  else
    "Foo"
  end
end

途中で強制的にreturnさせる場合

def hoge(arg)
  return "Hoge" if arg.nil?
  "Fuga"
end

Rubyのコーディング規約として、returnの省略は行わない方が良いという文献もあった。

return省略カッコいいけど、可読性とか保守性の面で気にする人も少なくなさそう。
チームや製品の文化に合わせることが大事そう。

メソッドの括弧省略

引数ゼロのメソッドの宣言/コールは丸括弧を省略する。

def hoge
  "Hoge"
end

p hoge

これによってゲッターメソッドが自然に書けるという、結構イカした仕様。

class Person
  def initialize(name)
    @name = name
  end

  def name
    @name
  end
end

person = Person.new("Alice")
p person.name

実際は自動プロパティ的な機能attr系があるので、そちらを使うことの方が多いかも。
そこら辺は後述。

コールの際は引数があってもカッコを省略することがある。
命令風に書きたい時に使われるのかな?
以下は全く同じ。

hoge "Hoge", "Fuga"
hoge("Hoge", "Fuga")

メソッドオブジェクト

さて、メソッドのカッコを省略できると聞いてまず思うのは、
じゃあ関数ポインタどう書くの?
ということ。
以下のようにシンボルを使って書き、コールにはcallメソッドを使う。

def hoge(arg)
  p arg
end

fuga = method(:hoge)
fuga.call("hello")

このmethod(symbol)によって生成されるオブジェクトをメソッドオブジェクトとよぶ。
後述するProcオブジェクトとの違いが非常に難しい。。。

ディクショナリ(ハッシュ)のカッコ省略

最後の引数がハッシュの際はハッシュの波括弧を省略できる。
それだけなんだけど、、これがなんて恐ろしい、、、

def hoge(hash)
  p hash["key1"]
end

hoge("key1" => "value1")
hoge({"key1" => "value1"})
hoge("key1" => "value1", "key2" => "value2")
hoge({"key1" => "value1", "key2" => "value2"})

# キーがシンボルの場合
hoge(key1: "value1")
hoge({key1: "value1"})
hoge(key1: "value1", key2: "value2")
hoge({key1: "value1", key2: "value2"})

ハッシュとシンボルについての説明は後述。
最初見た時ラムダ式でも渡しているのかと思ってた。。。

恐ろしいのは上例の後半の書き方。
この戦慄がお分かり頂けるだろうか、、、

# キーワード引数
def hoge(hoge: "a", foo: "b")
  p hoge + foo
end

hoge(hoge: "a", foo: "b")

# ハッシュ引数
def hoge(**args)
  p args[:key1]
end

hoge(key1: "value1", key2: "value2")

Rubyの引数の受け渡しにおける壮大な思想の一面が見えた気がする、、

普通の引数も、キーワード引数も、ハッシュ引数も、
渡す側からしたら区別がつかない ということ。

区別する必要がない方が本質的だろ?
Rubyに語りかけられているみたい、

他の人がどう考えているのかと思って少し調べたら、
とても分かりやすく引数についてまとめてくださっている記事があった。

こういう仕様ならばハッシュ引数**argsはなぜ必要なのかと疑問に思ったが、
多分以下のように特定のキーワード引数と分けて定義をするのに必要。

def hoge(a, b: "v1", **args)
  # omit
end

# 特定のキーワード引数を分ける必要なければこれでいい
def hoge(a, hash)
  # omit
end

とにかく、この最後の引数のハッシュは波括弧が省略可能という仕様は、
非常によく考えられた深い仕様のように感じる。

命名規則

大体Pythonと同じかなと。

術語メソッド(predicate method)の?

bool返すメソッドは名前の末尾を?にして、
その分無理に疑問系にしない。
つまり、is_とかhas_とかは基本つけない。

あくまでメソッド名の話。変数名の話ではない。

# good
def connected?
  # omit
end

# bad
def is_connected?
  # omit
end

一見センス良く見えるが、このせいで以下のような不都合が出ているのかなとも。。。

# 三項演算子: ?が2回続いて紛らわしい
a = hoge.connected? ? "a" : "b"

# null条件演算子: ?はメソッド名に使うからRubyでは&なのかな
hoge&.close

破壊的メソッドの!

破壊的メソッドとそうでないメソッドの両者がある場合は、
破壊的メソッドの方の末尾に!をつける。

これはセンスが良いと感じた。

class Point
  def initialize(x, y)
    @x = x
    @y = y
  end

  def origin_symmetry
    Point.new(-@x, -@y)
  end

  def origin_symmetry!
    @x *= -1
    @y *= -1
  end
end

ただしこれはあくまで慣習であり、!がついていないから安心と思ってはいけない。

(↑コメントでご指摘頂きありがとうございました!)

制御構文

unless

ifの反対。これを利用してできるだけ条件をシンプルにすることが求められる。

# good
loop do
  break unless hoge
end

# bad
loop do
  break if !hoge
end

ifとunlessの後置

すでに例で何度も出しているが、以下のように一行で書ける。

# good
loop do
  break unless hoge
end

# bad
loop do
  unless hoge
    break
  end
end

Pythonに慣れているとあまりやりたくならないかもだが、
Rubyだと一行にできるときはそうした方が良いと考える人が多そうなイメージ。

繰り返し

  • continueではなくてnext
  • 繰り返しにおけるPythonのようなelseは無い。

  • for-in(for-each)

values.each do |v|
  p v
end
  • 集合処理系
# 写像
[1, 2, 3, 4].map {|v| 2 * v}
  # => [2, 4, 6, 8]

# 絞り込み
[1, 2, 3, 4].select {|v| v % 2 == 0}
  # => [2, 4]

[1, 2, 3, 4].reject {|v| v % 2 == 0}
  # => [1, 3]

[1, 2, 3, 4].find {|v| v % 2 == 0}
  # => 2

ブロック, yield, Procオブジェクト, proc, lambda

モジュールと並んで最初最も難しい概念と思う。
以下参考にさせていただきました。

ここでは、自分なりに要点を整理しようと思います。

ブロック

do end{}で囲っているよく見るアレ。
|a, b|のようにして引数を受け取れる: ブロック変数

ブロック単体はメソッドに渡すために使う。
Procオブジェクトに包むことでオブジェクト化でき、無名関数のように扱える(後述)。

どちらも基本は同じ。
(厳密には引数に渡す時に挙動の違いがあったりする)

使い分けの目安としては、

  • 一行で収まるときは{}、複数行はdo end
  • 後続でメソッドチェーンするときは{}

ブロックとyield

Pythonyieldとは主従が逆!

  • メソッドはデフォルトでブロックを1つ受け取ることができる
    • method(args) block という感じになっている
    • 通常はブロックを受け取ることについて明示する必要はない
    • ブロック引数を用いて明示的に受け取ることも一応できる(後述)
  • 受け取ったブロックをyieldで呼び出す
  • yieldで渡された値がブロック変数に来る(複数可能, 受け取らなくてもいい)

1行ずつ読んでyieldする例

def read_lines
  io = FileIOMock.new
  loop do
    line = io.read_line
    break if line.nil?
    yield line
  end
end

read_lines {|line| p line}

んー、遅延評価とかメソッドチェーンとかやりにくい気がする、、、


動作確認用に作ったMock
class FileIOMock
  def initialize
    @lines = [
      "hoge",
      "fuga",
      "foo"
    ]
    @i = 0
  end

  def read_line
    line = @lines[@i]
    @i += 1 unless line.nil?
    line
  end
end


Procオブジェクト

  • Rubyにおける無名関数のようなもの。
  • method(symbol)で作ったメソッドオブジェクトに似て非なる存在。
  • proclambdaの2種類に分けられる。

メソッドオブジェクトと対比させるなら、これはブロックオブジェクトみたいなもの。
ブロックを直接メソッドに渡さず、オブジェクトとして扱いたい時に用いる。

proc

2通りの生成方法

  • Proc.new (block)
  • proc (block)
my_print_proc1 = Proc.new {|v| p "Hello #{v}"}
my_print_proc2 = proc {|v| p "Hello #{v}"}

lambda

my_print_lambda = lambda {|v| p "Hello #{v}"}

ブロック引数

メソッドと受け渡しするブロックを、Procオブジェクトとして扱うためのもの。
意外と紛らわしいので、焦らずちゃんと理解した方が良い。

受け取り側

定義側で引数の最後に&をつけてブロック引数を1つだけ定義することができる
すると、渡されたブロックをProcオブジェクトとしてメソッド内で扱える。

先述したyieldの例は、yieldを使わずに以下のように書ける。

def read_lines(&block)
  io = FileIOMock.new
  loop do
    line = io.read_line
    break if line.nil?
    block.call(line)
  end
end

read_lines {|line| p line}

メソッドコール側に変化はない。

渡す側

逆に、ブロックではなくてProcオブジェクトをメソッドに渡したい場合は、
コール側で引数の最後に&をつけてブロックとして渡すことができる。

def read_lines
  io = FileIOMock.new
  loop do
    line = io.read_line
    break if line.nil?
    yield line
  end
end

my_print_proc2 = proc {|v| p "Hello #{v}"}
read_lines(&my_print_proc2)

メソッド定義側に変化はない。

ちなみに、この方法でメソッドオブジェクトも同様に渡すことができた。
(後述するto_procメソッドが呼ばれてProcオブジェクトに変換されていると思われる)

to_procメソッドと&

(コメントで教えて頂き追記しました)

渡す側で&をつけて渡す際には、渡したオブジェクトのto_procメソッドが呼ばれる。

元々Procオブジェクトを渡した際には何の意味もないが、
メソッドオブジェクトや、その他to_procメソッドを実装しているオブジェクトに効果がある。

class Hoge
  def to_proc
    proc {p "hoge"}
  end
end

def fuga
  yield
end

fuga(&Hoge.new)
  # => "hoge"

この仕様のおかげで、シンボルやハッシュも&で渡して利用できる。

ちなみに、to_procはおそらくproc化するのでなくて、Procオブジェクト化するためのメソッド。
実際にprocになるのかlambdaになるのかは実装次第と思われる。

シンボルをProcオブジェクト化して、特定メソッドをコールさせる

シンボルのto_procは、第一引数にそのメソッドのレシーバを受け取って自身をコールさせるようなProcオブジェクトになる。
これにより、シンボルを&で渡すことでyieldされる各要素の特定のメソッドをコールさせる記述をスッキリ書ける。

["hoge", "", "fuga"].reject(&:empty?)
  # => ["hoge", "fuga"]

# これと同じということ
["hoge", "", "fuga"].reject {|v| v.empty?}
  # => ["hoge", "fuga"]

:empty?.to_procは、{|receiver| receiver.empty?} のようなものになっていそう。

使い所

シンボルとかハッシュとか、Procオブジェクト化して使えるのは面白いし便利そう。

それ以外だと、慣れていない自分にとってはちょっと難しい。

紛らわしいが、普通に無名関数を渡したい文脈では、
ブロック引数なんて意識せず普通に以下のようにすればいい。

def read_lines(line_process)
  io = FileIOMock.new
  loop do
    line = io.read_line
    break if line.nil?
    line_process.call(line)
  end
end

my_print_proc2 = proc {|v| p "Hello #{v}"}
read_lines(my_print_proc2)

デフォルトのブロックの受け渡しは1つしかできないので、
複数のコールバックを渡す必要がある場合にもこのような方法になるだろう。

ていうか、いつもこうすればブロック引数だとかyieldだとかに混乱しなくて済むのでは?

そんなこと言ったら人類の進化は止まっちゃいますね。
せっかく綺麗に書ける仕組みがあるので、コールバックが1つの時は積極的にyield使おう。

そうなると、複数メソッドを跨いでコールバックを受け渡す必要がある場合に、
ブロック引数が必要になってくるはず。

両側のブロック引数の使い方を盛り込んだ例

def read_lines(&block) # もらったブロックを子メソッドに跨がせたいのでprocとしてもらう
  io = FileIOMock.new
  loop do
    line = read_inner(io, &block) # 子メソッドのブロックへprocを渡す
    break if line.nil?
  end
end

def read_inner(io)
  line = io.read_line
  yield line unless line.nil?
  line
end

def line_process(time)
  case time
  when "morning"
    proc {|v| p "Good morning #{v}"}
  when "night"
    proc {|v| p "Good night #{v}"}
  else
    proc {|v| p "Hello #{v}"}
  end
end

read_lines(&line_process("morning")) # 渡す処理を動的に変更したいのでprocとして渡したい

例外処理

基本形

begin
rescue => e
ensure
end

メソッド全体にかける場合はbeginendは省略できる

def hoge
  p "main process"
  raise "just for test"
rescue => e
  p "error occurred: #{e.message}"
ensure
  p "this line is always called"
end

特定の例外を捕捉しretryする例
(retryrescue句の中でしか使えない)

count = 0
begin
  case count
  when 0
    raise IOError, "fuga"
  when 1
    raise "foo"
  else
    p "success!"
  end
rescue IOError => e
  p e
  count += 1
  retry
rescue => e
  p e
  count += 1
  retry
end

tips

  • raiseには例外クラスとメッセージを別々に渡す
    • 次のように例外インスタンスを渡すのは良くないらしい
      • raise IOError.new("msg")
    • 理由は前者の方が実はbacktraceに関する第三引数が省略可能で隠れていることにあるらしい
    • ではメッセージだけ渡すケースについてはどう考えれば???
      • ちなみに例外クラスとしては自動でRuntimeErrorになるらしい
  • Exceptionクラスはrescueしてはダメ
    • rescueして良い最も標準的なクラスはStandardError
      • 実は rescue => e = rescue StandardError => e
  • ensure句の中でreturnしてはダメ
    • 例外が潰されてしまう
    • 実はensure句の中ではreturn省略は無効。明示的にreturnを書かなければ大丈夫。

参考: https://techracho.bpsinc.jp/hachi8833/2017_03_21/37192

クラス

インスタンス変数

  • @を先頭につける
  • クラス外からは参照できない

クラスメソッド、クラス変数、クラスインスタンス変数

static系。

  • クラスメソッド: 先頭にself.をつける
    • クラス名.でも良いが、冗長で好ましくないだろう
  • クラス変数: 先頭に@@をつける
  • クラスインスタンス変数: staticに宣言したインスタンス変数
    • クラスもClassクラスのインスタンスらしい、、
    • クラス直下やクラスメソッド内でインスタンス変数を定義するとこれになるっぽい

staticなメンバ変数が普通に使えれば満足なんだけど、、
クラス変数とクラスインスタンス変数って、、

実はクラス変数は全てのサブクラスと共有される仕様らしい(子クラスに値を書き換えられる?)。
一方でクラスインスタンス変数は継承とは無関係。

可読性を考えたらできるだけクラス変数の方を使いたいように思うが、
確かに全てのサブクラスで共有っていうのも嫌だなあ。

クラス直下のローカル変数宣言とか処理とか

Pythonに慣れている人なら、以下のように思うだろう。

クラス変数は先頭に@@つけるならば、
ローカル変数をクラス直下に宣言したら何が起こるのか?
Pythonはクラス直下に宣言した変数がクラス変数になるし。

クラス直下のローカル変数宣言や処理はクラスのロード時に走るだけで、
特に変数はクラスに紐付かずに破棄される。

クラスのロード時の処理を普通に書けるのは便利かも。
Pythonもできたっけ??

定数

  • アルファベット大文字 ([A-Z]) で始まる識別子は定数
  • 定数は名前空間上(module, class上)に定義し、そこに保持される
    • メソッド内では定義できない
    • 外部からは::でアクセス
    • 内部からはインスタンスメソッド、クラスメソッドの双方から直接アクセスできる

定数、変数、メソッド全部のせ

class Hoge
  # クラスロード時のローカル変数。ロード後破棄される。
  local_var = "local var" 
  # クラスロード時に一度だけ実行される
  p local_var 

  # 定数。名前空間上に保持される。
  # 外部からはHoge::CONSTANT_VARのようにアクセス
  # 内部からは直接アクセス可能
  CONSTANT_VAR = "constant var" 

  # クラス変数
  @@class_var = "class var"

  # クラスインスタンス変数
  @class_instance_var = "class instance var"

  # クラスメソッド
  def self.class_method
    p "start class method"
    p @@class_var
    p @class_instance_var
    p CONSTANT_VAR
    p "end class method"
  end

  def initialize
    #インスタンス変数
    @instance_var = "instance var"
  end

  # インスタンスメソッド
  def instance_method
    p "start instance method"
    p @instance_var
    p CONSTANT_VAR
    p "end instance method"
  end
end

Hoge.class_method
Hoge.new.instance_method
p Hoge::CONSTANT_VAR

  # => "local var"
  # => "start class method"
  # => "class var"
  # => "class instance var"
  # => "constant var"
  # => "end class method"
  # => "start instance method"
  # => "instance var"
  # => "constant var"
  # => "end instance method"
  # => "constant var"

アクセサ

自動プロパティ。

class Hoge
  attr_accessor :rw # getter, setter
  attr_reader :r # only getter
  attr_writer :w # only setter

  def initialize(a, b, c)
    @rw = a
    @r = b
    @w = c
  end
end

アクセス修飾子

メソッドはデフォルトでpublic。
privateキーワード以降が全部privateになる(wow!)

以下は fuga, hoo がprivateメソッドになる。

class Hoge
  def hoge
  end

  private

  def fuga
  end

  def foo
  end
end

可読性、、保守性、、どうしてこうなった、、

(コメントでご指摘いただきました!
クラスがシンプルならばこれが便利と考える方も多そうです。)

Ruby 2.1 からは普通に以下のようにメソッド毎に書けるらしい。

class Hoge
  # 言わずもがなpublic
  def hoge
  end

  # これだけprivate
  private def fuga
  end

  # これはpublic
  def foo
  end
end

絶対これを徹底した方が良いと思うのだけれども、
なぜかこうしていないコードをよく見る気がする。

(コメントを頂いたので以下追記)

これについては考え方が分かれそうです。
確かにクラスがシンプルであれば、まとめてprivateにしてしまう旧来のやり方も便利かもしれません。
自分は今まで当たり前のようにメソッド毎にprivateを付与してきたので、ちょっと慣れないですが、
チームや製品の文化に合わせるのが良さそうですね。

Rubyのprivateは実は曲者

privateメソッドは子クラスから呼び出せる(後述)。
Rubyのprivateは レシーバから呼び出せない という仕様らしい(ただしself.からは可能)。
なるほど、確かにそれなら一見privateっぽい挙動に見えるわけだ。

その他

細かい癖のある仕様が多くてまとめきれないので、ざっくり

  • privateの時点で子クラスから呼べるというのに、protectedがあるらしい
  • クラスメソッドをprivateにするにはprivate_class_methodなるものでシンボル指定しないといけないらしい

interface, abstract

ない。Pythonと同じ。

オーバライド

  • コンストラクタ、アクセサも含めインスタンスメソッドは全て継承されるし、オーバライドできる
    • ただ同名で再定義するだけ
    • superで親メソッドをコールできる
  • 親のprivateメソッドも使える
  • クラスメソッドも継承される
  • クラス変数は共有されるので注意が必要
  • クラスインスタンス変数は全く継承されない
  • 定数は継承される、オーバーライドもできる
class Person
  def initialize(name)
    @name = name
  end

  def greet
    say "こんにちは。"
  end

  private

  def say(msg)
    p "#{@name}: #{msg}"
  end
end

class CheerfulPerson < Person
  def greet
    super
    say "今日もいい天気ですね。"
  end
end

class SilentPerson < Person
  def greet
    say "...。"
  end
end

Person.new("Alice").greet
CheerfulPerson.new("Bob").greet
SilentPerson.new("Chris").greet

  # => "Alice: こんにちは。"
  # => "Bob: こんにちは。"
  # => "Bob: 今日もいい天気ですね。"
  # => "Chris: ...。"

特異メソッド

インスタンスに対して定義したメソッド。

class Hoge
end

hoge = Hoge.new
def hoge.special
  p "hello"
end

hoge.special
  # => "hello"

インスタンスに紐づくメソッドのように見えるが、
やっぱりメソッドは特定のクラスに紐づかないといけないということで、
この時には内部で専用のクラスを自動生成しているらしい。
そのクラスのことを特異クラスと呼ぶ。

後述するmixinをextendする際に特異メソッドを意識する。

モジュール

ブロックと並んで最初最も難しい概念の1つと思う。

名前空間としてのモジュール

これは簡単。
入れ子になったクラスも名前空間を形成する。

module Hoge
  class Fuga
    module Foo
      class Poo
        def poo
          p "poo"
        end
      end
    end
  end
end

Hoge::Fuga::Foo::Poo.new.poo
  # => "poo"

staticクラスとしてのモジュール

モジュールは実はstaticクラス、つまりインスタンス化できないクラスのような性質がある。

  • module内でクラスメソッドを定義することで、外部から参照させられる
  • 定数は名前空間上に公開される
module A
  CONST_VAR = "constant var"

  def self.hoge
    "hello"
  end
end

p A::CONST_VAR
  # => "constant var"
p A.hoge
  # => "hello"

一応module_functionというmoduleのインスタンスメソッドを公開する方法もあるが、
mixinをマスターした上級者向けの話と思う。

mixinとしてのモジュール

moduleがインスタンス化できないクラスならば、
module内のインスタンスメソッドは何に使えるのか?

その答えが、mixin。

Pythonは多重継承でmixin可能だが、Rubyは多重継承できない。
そのためRubyではモジュールを使ってmixinを実現する。

モジュールをmixinする方法は、下記の3種類。

  • include
    • 自身の親クラスにモジュールを挿入する
    • モジュールを継承するイメージ
  • prepend
    • includeの逆で、モジュールに自身を継承させるイメージ
    • 自クラスの実体がモジュールにスイッチし、その親クラス側に自クラスがくる感じ
  • extend
    • モジュールのインスタンスメソッドをselfの特異メソッドとして追加する
    • クラスに対してextendした場合はクラスメソッドとして追加される
    • インスタンスに対してextendした場合はインスタンスメソッドとして追加される

なんとモジュールがモジュールをmixinすることもある。

イルカとサメという異なる継承関係のクラスに、「泳ぐ」という共通処理をmixinする例。
(個人的にどこかで読んだイルカとサメのmixinの例がわかりやすくて好きです)

class Mammal
end

class Fish
end

module Swimmer
  def swim
    p "swim!"
  end
end

class Dolphin < Mammal
  include Swimmer

  def swim
    super
    p "cute!"
  end
end

class Shark < Fish
  include Swimmer

  def swim
    super
    p "scary!"
  end
end

Dolphin.new.swim
  # => "swim!"
  # => "cute!"
Shark.new.swim
  # => "swim!"
  # => "scary!"

includeを使って親クラスとしてSwimmerモジュールを挿入しているので、
オーバーライドして動作を拡張して利用できる。
もちろんオーバーライドせずそのまま利用することもできる。

(@scivolaさんにextendの活用例を教えて頂いたので、以下追記)

モジュールはインスタンス化できないが、
以下のようにextendObjectクラスのインスタンスに使うことで、それっぽいことができる。

module ModuleA
  def hoge
    p "hoge"
  end
end

obj = Object.new
obj.extend ModuleA
obj.hoge

モジュールでsuper!?

保守性、可読性の観点で疑問は残るが、mixinはやろうと思えば色々な使い方ができてしまう。
コードを読めるようになるために慣れる必要がありそう。

module内でインスタンス変数を使ってしまう例

module Fuga
  def introduce
    p "My name is #{@name}"
  end
end

class Hoge
  include Fuga

  def initialize(name)
    @name = name
  end
end

Hoge.new("Alice").introduce
  # => "My name is Alice"

mixin先に特定のインスタンス変数があることを前提としてしまっている。
個人的には良くないと思う。

以下はmodule内のsuperのヤバイ例

class Poo
  def hoge
    p "poo"
  end
end

module Foo
  def hoge
    super
    p "foo"
  end
end

module Fuga
  include Foo

  def hoge
    super
    p "fuga"
  end
end

class Hoge < Poo
  include Fuga

  def hoge
    super
    p "hoge"
  end
end

Hoge.new.hoge
  # => "poo"
  # => "foo"
  # => "fuga"
  # => "hoge"

最終的に継承関係がHoge -> Fuga -> Foo -> Pooのようになる。
includeしたモジュールのsuperは、元々の親クラスを呼び出しにくわけだが、
モジュールがモジュールをincludeしていることもありえて、こうなるとかなり厄介。

その他細かいポイント

%記法

以下の記事が参考になりました

インターポレーション

name = "Alice"
p "Hello #{name}, how are you?"
  # => "Hello Alice, how are you?"

シンボルと文字列

:で始めるとシンボルになり、次の特徴を持つ。

  • シンボルは、任意の文字列と1対1対応する
  • 同じシンボルは必ず同じオブジェクト
  • 中身は整数

文字列というよりはEnumに近い。
その性質上ハッシュのキーによく用いられる。

文字列とシンボルの変換

:hoge.to_s
  # => "hoge"

"hoge".to_sym
  # => :hoge

ハッシュ

普通のハッシュ。

h = {"key1" => "hoge", "key2" => "fuga"}
f["key3"] = "foo"

シンボルがキーの場合は特別な記法がある。

h = {key1: "hoge", key2: "fuga"}
h[:key3] = "foo"
h[:key1]
  # => "hoge"
h["key1"]
  # => nil

nil関連

リストで存在しないインデックスを指定した時や、
ハッシュで存在しないキーを指定した時は、
例外は発生せず、nilを返す。

対話型の起動

irbコマンド

終わりに

思ったよりも大変だったが、面白い言語だと思った。

慣れてしまえばPythonと同じくらい便利に使えそう。

mapとかselectといった集合処理系が強そうなのがとても良い。
Pythonも見習ってほしい(リスト内包表記使いにくい)。

一方でRubyyieldの仕様は気になった。
Pythonのようにジェネレータを返せる方が遅延評価とか便利なんじゃないだろうか。

141
113
19

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
141
113