日々徒然

プログラミングしたりお酒飲んだりする毎日

Rubyしか書けないエンジニアから脱却するためのJavaScript Primerお勉強①

はじめに

エンジニアとして就職してから1年とちょっと経ちました。

その間ほとんどずっとRuby on RailsでのAPI開発しかして来なかったため、そろそろ他の言語も勉強してみようと思いJavaScriptを一から勉強します!

ちなみに現時点でのjs知識は

  • 基本文法は何となく理解している
  • ふわふわな知識でちょこっとちょっと触れるくらい

ご覧の通りちょっとした知識しかないため、まずはJavaScript Primerを全て読んでみたいと思います!

jsprimer.net

(ネットで無料で見れるのありがたすぎる... 製作者の方々、本当にありがとうございます)

第一部

変数と宣言

const は再代入不可、letは再代入可能、varは使わない

基本はconstで、次にletを使えば良さそう

同じスコープ内で変数の再宣言はできないっぽい

let a = 'a';
// undefined

let a = 'aa';
//Uncaught SyntaxError: Identifier 'a' has already been declared

分割代入はこんな感じ

const [b, c] = ['b', 'c'];
b // 'b'
c // 'c'

// 要素数が足りない場合はundefinedが入るっぽい
const [e, f] = ['e'];
e // 'e'
f // undefined

値の評価と表示

変数宣言はundefinedを返す

Rubyだとselfの値が返ってくるのでちょっと違和感

そもそも変数に関数を入れれるのに『変数宣言』って呼んで良いのかは疑問

この辺りRubyと結構違いそうなので気をつける

データ型とリテラル

プリミティブ型とそれ以外のオブジェクト型

RubyでいうところのArrayもオブジェクト型

Ruby的にはクラスがメソッドを持っている感じだけど、jsではオブジェクトがプロパティを持っているという理解で合ってるのかな?

ここに関しては続きの章で解説があるらしいので一旦保留

演算子

基本的にはRubyと同じ感じ

ただこれでなんで2になるのかは分からない...

let num = +'2';

num // 2

Rubyみたいにオブジェクトに対してメソッドを呼ぶみたいな考え方じゃないのかも...

===は比較、==は暗黙的な型変換してからの比較

jsのfalseは

  • false
  • undefined
  • null
  • 0
  • 0n
  • NaN
  • ""

数が多いよ...

||演算子だとfalsyな値全てがfalseとして扱われてしまう

??演算子を使えばnullorundefinedのみfalseとして評価される

演算子系は基本的にRubyと似てるので大丈夫そう

暗黙的な型変換

暗黙的な型変換が行われるのは分かったけど1 + true2になるのは衝撃すぎる

基本的に暗黙的な型変換に頼るような実装はやめましょうみたいな理解でいいのかな?

関数と宣言

仮引数が渡されなかった場合はundefinedになって、多く渡した時は無視される

下の例だとnum2がundefinedになってnum1 * num2の結果がNaNになる

const multiplication = (num1, num2) => {
    return num1 * num2;
end

multiplication(2,3) // => 6
multiplication(2) // => NaN
multiplication(2,3,4)// => 6

Rubyと違ってエラーにならないのか〜

Rubyでいう可変長引数はこんな感じ

function fn(...args) {
    return args;
}

fn(1,2,3,4,5) // => [1, 2, 3, 4, 5]

////////////////

function fn(num1, num2) {
 return num1 * num2;
}

fn(...[2,3]) // => 6

argmentsという関数の中でのみ参照できる特殊な変数がある

function fn() {
    console.log(arguments);
}

fn(1, 2, 3) // => [Arguments] { '0': 1, '1': 2 }

Arrayっぽいけど実際はObjectの形で値が入ってるため、インデックスを使って値は取れるけどArrayのメソッドは使えないみたい

ただそもそもArrayもObject型なのに、Arrayのみで使えるメソッドが定義されてるってどういうこと??

この辺りからちょっと理解が難しい...

jsの関数は第一級関数と言って変数に代入できる

function double (num) {
    return num * 2;
 }

const fn_1 = double; // ()付きで渡してないので関数が変数に代入される
const fn_2 = double(5); // ()付きで渡してるので関数の戻り値が代入される

Arrow Functionの書き方で関数式を短く書ける

const fnA = (x) => { return x; };
const fnB = x => { return x; }; // 仮引数がひとつなら()を省略できる
const fnC = x => x; // 一行のみの場合はブロックとreturnを省略できる

x => x; // 無名関数だとこんな感じで書ける

引数として渡される関数の事をコールバック関数というのは何となく知ってた

オブジェクトのプロパティである関数をメソッドと呼ぶらしい

第一級関数で関数を値として扱えるからオブジェクトの値に関数を定義できるってことかな?

ここが全くRubyと違うのでびっくりした

const obj = {
    method: num => num
};

obj.method(10) // => 10

// こういうい書き方もできる(推奨らしい)
const obj = {
    method(num) {
        return num;
    }
}

obj.hogeでオブジェクトに新しくプロパティを追加できる

感想

やっぱりRubyとは全然違うなぁという感想

色々触ってみないと本質的なところが分からないと思うので、とりあえずJavaScript Primer終えたら違う本買ってみようと思います!

続きは後日

RubyGoldに合格しました!!

はじめに

11月末から勉強していたRubyGold試験に88点で合格しました!

既にたくさんの合格しました系の記事はあるのですが、簡単に自分も書いてみます🙋‍♂️

Ruby

業務でRubyを使った開発は昨年の10月から現在までおよそ一年弱の経験があります。

勉強期間

日数で数えると11/29~12/22

途中の1週間でなぜかモチベが爆下がりして全然勉強できなかったので、結局のところ2~3週間くらいでした。

平日はだいたい1時間くらい、土日は4時間くらいやっていたので合計すると3,40時間ぐらいな感じです。

勉強に使用した教材

メタプログラミングRuby

5章までをざっと流し読み&分からなかった問題があるときにその範囲を再度読み直してました。

公式教科書

www.amazon.co.jp

章末の練習問題、模擬試験は割と本試験に近い感じなので絶対に全て覚えといた方が良さそう。

自分は合計で10週ぐらいして完璧に回答できるようにしときました。

Rex

rex.libertyfish.co.jp

Silverでもお世話になったWeb問題集。

定数探索とeval系の難しい問題が結構載ってる印象。

なのでRexではその辺りを完璧にしておけばOK。

あくまで公式問題集で深く勉強していない範囲を詰めていくイメージ。

自分はめちゃくちゃやりこみました↓

f:id:hirovodka:20211227175807p:plain

過去受験した人たちのありがたいブログ

qiita.com

tamata78.hatenablog.com

大体全部の範囲を勉強できたら、過去の受験者の方達のブログ記事などを漁って、漏れがないか確認してました。

自分もいくつか記事書いてます↓

RubyGold対策① - 日々徒然

RubyGold対策② - 日々徒然

Rubyの特異クラス内のインスタンス変数について - 日々徒然

(完全に理解する)Rubyの定数探索 - 日々徒然

Rubyのeval三兄弟についてのまとめ(instance_eval, class_eval, module_eval) - 日々徒然

感想

RubyGold試験の勉強を通してだいぶRubyのことが理解できたなと思いました。

特にメタプログラミングRubyの内容は、今までなんとなくRubyを書いていた自分からするとどれも知らなかったことばかりで、とても勉強になりました。

よく資格試験は意味がない!みたいに言われたりしますが、業務でRubyを使っていくなら勉強しておいて損は無いと思います(受験料が高いですが...)

ブログでのアウトプットも同時にできたので、自分的には大満足の資格試験でした!!

Rubyのeval三兄弟についてのまとめ(instance_eval, class_eval, module_eval)

はじめに

RubyGoldのお勉強でeval三兄弟について調べたのでまとめた

instance_eval

オブジェクトに対する操作

インスタンスに対してinstance_evalすると、そのインスタンスの特異メソッドを作成してくれる

class Piyo
  def cry
    p "piyo"
  end
end

piyo = Piyo.new
piyo.cry #=> "piyo"

piyo.instance_eval do
  def cry
    p "piyo" * 2
  end
end

piyo.cry #=> "piyopiyo"

selfに対してcryメソッドを定義(再定義)した結果、piyopiyoが出力された

ちなみに文字列とブロックを渡す場合で挙動が変わる

ブロックを渡した場合はそのブロックの外側に対する定義、文字列を渡した場合はselfに対する定義になる

こちらも例

class A
  def const
    p CONST
  end
end

a = A.new
# ブロックの外側のObjectにCONSTが定義される
a.instance_eval do
  CONST = "Object#A"
end

# selfであるインスタンスaにCONSTが定義される
a.instance_eval <<-EOS
  CONST = "eval#A"
EOS

a.const #=> "Object#A"

class << a
  def singleton_const
    p CONST
  end
end

a.singleton_const #=> "eval#A"

class_eval

ブロックor文字列を渡すと、渡した物をそのクラス内で定義してくれる

class A
end

A.class_eval do
  def call
    p "A"
  end
end

A.new.call #=> "A"

こいつもブロックor文字列で動きが変わる

ブロックが渡された場合は定数とクラス変数のスコープがブロックの外側、文字列が渡された時はselfの定義内になる

class A
  def const
    p CONST
    p Object::CONST
  end
end

# ブロックの外側のObjectのコンテキストでCONSTが定義される
A.class_eval do
  CONST = "block#A"
end

# selfであるAのコンテキスト内でCONSTが定義される
A.class_eval <<-EOS
  CONST = "string#A"
EOS

A.new.const
#=> "string#A"
#=> "block#A"

つまりinstance_evalclass_evalブロックが渡された場合はブロックの外側、文字列が渡されたらselfのコンテキスト内に対しての操作になる

module_eval

class_evalと一緒

三兄弟(双子と一人)

え、instance_evalにもクラス渡せるよね?

instance_evalはBasicObjectのメソッドのため、クラスをレシーバーにしても実行できる

わざわざクラスに対してinstance_evalすることも無いとは思うけど気になったので調べてみた

class A
end

# クラスに対してinstance_eval
A.instance_eval do
# selfはAなのでAに対する特異メソッド == Aのクラスメソッドが定義される
  def call
    p "A"
  end

# selfはAなのでAに対するインスタンスメソッドが定義される
  define_method :define_call do
    p "define#call"
  end
end

A.call => "A"
A.new.define_call => "define#call"
class B
end

b = B.new

# Bクラスのインスタンスbに対してinstance_eval
b.instance_eval do
# selfはbなのでbに対する特異メソッドが定義される
  def call
    p "B"
  end

# selfはbなのでdefine_methodが呼び出せない、エラーになる
# define_singleton_methodを呼べば、bの特異メソッドとして定義可能
  define_method :define_call do
    p "define#call"
  end
end

つまりinstance_evalはselfに対するメソッド定義、class_evalはselfのコンテキスト内でメソッド定義してくれている

まとめ

RubyGoldだと文字列とブロックを渡した時の定数参照的な問題がでるっぽい...

だいぶ説明足りてないけど、ひとまずヨシ

(完全に理解する)Rubyの定数探索

はじめに

RubyGoldの試験対策をしていく中で、定数探索についてかなり色々調べたのでまとめてみた

基本的な考え方

定数探索の基本的な考え方はレキシカルスコープ→継承ツリー→トップレベ

これだけ覚えておけば大体なんとかなる

基本的なパターン

class A
  CONST = "A"

  def const
    p CONST
  end
end

A.new.const #=> "A"

これはCONSTを呼び出してる場所のレキシカルスコープ内にCONSTがあるため、そのままAが出力される

外部から呼び出すパターン

class A
  CONST = "A"
end

class B
  def const
    p A::CONST
  end
end

B.new.const #=> "A"

レキシカルスコープ、継承以外から呼び出す場合はクラス::定数名でそのクラスに定義されている定数が呼べる

継承するパターン

class A
  CONST = "A"
end

class B < A
  def const
    p CONST
  end
end

B.new.const #=> "A"

レキシカルスコープに無い && オブジェクトのスーパークラスに無い場合は継承ツリーを辿って定数を探す

この場合はBのインスタンスB.newスーパークラスBにCONSTが存在しないため、継承しているAで探す

レキシカルスコープ VS 継承 part1

class A
  CONST = "A"
end

class B < A
  CONST = "B"

  def const
    p CONST
  end
end

B.new.const #=> "B"

定数はレキシカルスコープ→継承の順番で探索されるため、今回はBが出力される

レキシカルスコープ VS 継承 part2

class A
  CONST = "A"

  def const
    p CONST
  end
end

class B < A
  CONST = "B"
end

B.new.const #=> "A"

意外にハマりそうな仕様

Aを継承しているためBのインスタンスに対して#constメソッドが呼べるが、 その際に参照されるCONSTはBでは無くconstメソッドを定義しているAのレキシカルスコープが優先される

レキシカルスコープ VS トップレベ

CONST = "Object"

class A
  CONST = "A"

  def const
    p CONST
  end
end

A.new.const #=> "A"

レキシカルスコープが優先される

継承 VS トップレベ

CONST = "Object"

class A
  CONST = "A"
end

class B < A
  def const
    p CONST
  end
end

B.new.const #=> "A"

継承が優先される

特異クラスの定数参照

ここから複雑になる

基本的にレキシカルスコープ→継承の順番で探索することを覚えていれば大丈夫なので、特異クラスの継承が分かっていれば定数探索も分かる

レキシカルスコープ part 1

class A
  CONST = "A"

  class << A
    def const
      p CONST
    end
  end
end

A.const #=> "A"

CONSTは特異クラスと同じレキシカルスコープで定義されているため参照可能

特異クラスのレキシカルスコープ part 2 (レキシカルスコープ VS レキシカルスコープ)

class A
  CONST = "class#A"

  class << self
    CONST = "singlton#A"

    def const
      p CONST
    end
  end
end

A.const #=> "singlton#A"

同一レキシカルスコープ内だと特異メソッド側で定義した値が呼ばれる

特異クラスのレキシカルスコープ part 3 (再オープン)

class A
  CONST = "class#A"

  class << self
    CONST = "singlton#A"
  end
end

class << A
  def const
    p CONST
  end
end

A.const #=> "singlton#A"

特異クラスを再オープンした場合も特異クラスのレキシカルスコープ内に定義される

特異クラスから元クラスの定数は参照できないパターン

class A
  CONST = "A"
end

class << A
  def const
    p CONST
  end
end

A.const #=> uninitialized constant #<Class:A>::CONST

これはエラーになる

探索の流れ↓

CONSTはAの特異クラスのレキシカルスコープ内に無いため継承ツリーを辿ってCONSTを探す

なのでAの特異クラスのスーパークラスはAのスーパークラスの特異クラスになるためA.superclass => Objectの特異クラスに探しに行く

以下の例だと定数が参照できる

class A
end

CONST = "class#object" # => ObjectにCONSTを定義

class << Object
  CONST = "singlton#object" # => Objectの特異クラスにCONSTを定義
end

class << A
  def const
    p CONST
  end
end

A.const #=> "singlton#object"

定数探索の順番は

Aの特異クラスのレキシカルスコープ→Aのスーパークラス(Object)の特異クラス

今回はObjectの特異クラス class << ObjectにあるCONSTが呼び出された

特異クラスのスーパークラスはクラスのスーパークラスの特異クラス

この呪文を覚えていれば大丈夫

まとめ

定数探索とメソッド探索で若干違うので、勘違いしないように注意します

Rubyの特異クラス内のインスタンス変数について

はじめに

Ruby Goldのお勉強中によく分からない挙動をしていたので手元で動かしてみた。

特異クラス内のインスタンス変数

まずはこのコード

class A
  @a = 1
  def call_a
    p @a
  end

  class << self
    @a = 2
    def call_a
      p @a
    end
  end
end

Aクラスの中でインスタンス変数@aに1を代入&特異クラス内で@aに2を代入

メソッドを呼んでみる

A.new.call_a => nil

A.call_a => 1

A.new.call_anilになるのは分かる(initializeインスタンス変数に値がセットされてない)けど、A.call_aで2じゃなくて1になるのがよく分からなかった...

けど、手を動かしてみるとなるほどなという結果

class << selfの中でselfを呼んでみる

class A
  @a = 1
  def call_a
    puts @a
  end

  class << self
    @a = 2
    p self # => #<Class:A>
    def call_a
      puts @a
    end
  end
end

#<Class:A>Aクラスの特異クラス。

つまり@aは特異クラス内のインスタンス変数ということなので、特異クラス内に特異クラスを作りレシーバーを特異クラスにしてメソッドを定義すれば取ってこれる(何言ってるか謎)

class A
  @a = 1
  def call_a
    puts @a
  end

  class << self
    @a = 2
    def call_a
      puts @a
    end

    class << self
      def call_a_from_singleton_class
        puts @a
      end
    end
  end
end

A.singleton_class.call_a_from_singleton_class # => 2

singleton_classは自身の特異クラスを返すので、それをレシーバーにしてもう一つネストの深い特異クラスのスコープの中でメソッドを定義する。

まとめ

そもそも特異クラスのスコープの中にインスタンス変数を定義するのかどうか分からない。

自分は見たことがない。

まぁまたちょっとRubyに詳しくなったのでヨシ✋

RubyGold対策②

前回の記事

続きやってく。

refine

変更するクラスを与えるとusing以降から、その定義内容が反映されるみたい。

module内で定義する。

docs.ruby-lang.org

class Piyo
  def cry
    puts "ぴよぴよ"
  end

  def call_cry
    cry
  end
end

module Hoge
  refine Piyo do
    def cry
      puts "ほげぴよぴよ"
    end
  end
end

piyo = Piyo.new
piyo.cry # => ぴよぴよ
using Hoge
piyo.cry => ほげぴよぴよ
piyo.call_cry => ぴよぴよ

クラスの中でusingすると、そのクラス内での呼び出しはrefineが使われるっぽい。

class Piyo
  def cry
    puts "ぴよぴよ"
  end

  def call_cry
    cry
  end
end

module Hoge
  refine Piyo do
    def cry
      puts "ほげぴよぴよ"
    end
  end
end

class HogePiyo
  using Hoge

  def call_cry
    Piyo.new.cry
  end
end

HogePiyo.new.call_cry # => "ほげぴよ"
Piyo.new.cry # => "ぴよぴよ"
using Hoge
HogePiyo.new.call_cry # => "ほげぴよ"
Piyo.new.cry # => "ほげぴよ"

require, load

require一度だけ読み込み、拡張子補完あり

load無条件に読み込み、拡張子補完なし

docs.ruby-lang.org

るりまが言うにはこういうことらしい↓

require はライブラリのロード、load は設定ファイルの読み込みなどに使うのが典型的な用途です。

clone, dup

dupオブジェクトの内容をコピー

cloneは↑に加えてfreeze、特異メソッドなど完全なコピー

浅いコピーなので、深いコピーする時にはMarshalモジュールを使う

docs.ruby-lang.org

特殊変数

$0は実行中のファイル名

$1~$nは正規表現のn番目のカッコにマッチした文字列

分かりやすくまとめてくださってる方がいたので↓

Rubyの特殊変数一覧 · GitHub

lazy

遅延評価してくれるやつ

docs.ruby-lang.org

chunk

結果によってグループ分けしてくれるやつ

docs.ruby-lang.org

Fiber

本当に知らなかった機能No1

Fiber.newにブロックを渡して処理を記述→resumeメソッドで実行する

fiber = Fiber.new do
  p "ふぁいばー"
  Fiber.yield # 処理を停止
  p "ふぁいばー2"
end

fiber.resume # => ふぁいばー

fiber = Fiber.new do
  p "ふぁいばー"
  Fiber.yield "停止"# 引数を渡すと、停止した時に返る
  p "ふぁいばー2"
end

p fiber.resume
# => ふぁいばー
# => 停止

自分レベルのよわよわエンジニアにはイマイチ使いどころがわからん...

感想

知らない機能いっぱい...

RubyGold対策①

はじめに

下半期の目標で『RubyGold取得』を掲げているものの、11月末まで全然勉強できなかった(しなかった)ので、これから本腰入れて勉強開始しようと思います。

Rubyの実行環境

分かりやすくまとまってるサイト

Ruby Gold対策(実行環境) - 気軽に楽しくプログラムと遊ぶ

多重代入

なんとなく分かっていたけど、右辺に*がつくパターンってどんな動きだっけ?ってなった

るりましっかり読もう

docs.ruby-lang.org

irb(main):001:0> x,y = [1,2,3]
irb(main):002:0> x
=> 1
irb(main):003:0> y
=> 2
irb(main):004:0> x,*y = [1,2,3]
irb(main):005:0> x
=> 1
irb(main):006:0> y
=> [2, 3]
irb(main):007:0> x,*y = *[1,2,3]
irb(main):008:0> x
=> 1
irb(main):009:0> y
=> [2, 3]
irb(main):010:0> x,y = *[1,2,3]
irb(main):011:0> x
=> 1
irb(main):012:0> y
=> 2

Rational

初見でした。

IntとRationalの計算はRational,FloatとRationalの計算はFloatになるみたい。

irb(main):029:0> 4/5r
=> (4/5)
irb(main):030:0> 4.0/5r
=> 0.8
irb(main):031:0> 

ブロック

ブロック引数を{}で囲むときには()省略不可、do~endならOK

throw/catch

これも初見。

raise/rescueっぽい感じがする。

irb(main):053:1* def puts_hoge
irb(main):054:1*   puts "catch前"
irb(main):055:1*   throw :exit
irb(main):056:1*   puts "hoge"
irb(main):057:0> end
=> :puts_hoge
irb(main):058:0* catch :exit do
irb(main):059:0* puts_hoge
irb(main):060:-> end
catch前
=> nil # hogeが出力されない

返り値はthrowに渡されたオブジェクトっぽい

Kernel.#throw (Ruby 3.0.0 リファレンスマニュアル)

super

()付きと無しで違いあり。

docs.ruby-lang.org

いままでそこまで意識して使ってなかった...

たしかにassign_attributesをオーバーライドする時とかsuperだけ書いてる...

public, private, protectedの違い

この辺り実は全然気にしてなかった

特にRailsを書いてる時は「このメソッドは外から呼び出す必要ないからprivateで良いか〜」ぐらいの認識。

伊藤さんのこのブログが分かりやすかった↓

JavaやC#の常識が通用しないRubyのprivateメソッド - give IT a try

自分でも動かしてみた

class Hoge
  protected

  def hoge_protected
    puts "protectedです〜"
  end

  private

  def hoge_private
    puts "privateです〜"
  end
end

class Piyo < Hoge
  def call
    hoge_protected # サブクラス内から呼び出せる
    hoge_private # サブクラス内から呼び出せる
    Piyo.new.hoge_protected # サブクラス内でサブクラスのインスタンスから呼び出せる
  end

  def call_with_reciever
    Hoge.new.hoge_protected # サブクラス内で自身のインスタンスから呼び出せる
    Hoge.new.hoge_private # レシーバー付けたらもちろんエラー
  end
end

class Hogepiyo
  def call_hoge
    Hoge.new.hoge_protected # サブクラスじゃないと呼べない
  end

  def call_piyo
    Piyo.new.hoge_protected # サブクラスじゃないと呼べない
  end
end

privateをサブクラスから呼び出せるのは知らなかった...

piyo.callだとPiyoクラスがselfになるから、メソッド探索の時にsuperクラスのhoge_privateを見つけて呼び出してる感じなのかな?

protectedselfが自分自身orサブクラスのインスタンスでも呼び出せる。

どっちもサブクラス内で呼べるけど、レシーバーを付けるときはprotectedみたいな感じで覚えとけばとりあえず混乱しなさそう。

ちなみにオーバーライドできるから要注意

class Hoge
  protected

  def hoge_protected
    puts "protectedです〜"
  end

  private

  def hoge_private
    puts "privateです〜"
  end
end

class Piyo < Hoge
  def call
    hoge_protected # オーバーライドされる
    hoge_private # オーバーライドされる
    Piyo.new.hoge_protected # オーバーライドされる
  end

  def call_with_reciever
    Hoge.new.hoge_protected # オーバーライドされない
  end

  protected

  def hoge_protected
    puts "オーバーライド後のprotectedです〜"
  end

  private

  def hoge_private
    puts "オーバーライド後のprivateです〜"
  end
end

感想

privateprotectedに関しては、まだ理解が浅い感じがする...