tnakata's blog

Webエンジニア(2017/01/01~)。プログラミングやWebについて語る。学生時代は航空宇宙工学を専攻してた。

md2keyを使った

社内LTでスライド作るのにmd2keyを使った。便利。

github.com

k0kubun.hatenablog.com

太字や取り消し線はまだ使えないらしい

Rich texts · Issue #11 · k0kubun/md2key · GitHub

とのこと。

調べてみた

実装できないかなと思って、applescriptで太字や取り消し線を実現できないか調べてみた。keynote見る限り、文字のスタイルにボールドはあるから案外すぐ見つかるんじゃないかと思った。

結果としては見つけられなかった。リッチテキストのプロパティがcolor, font, sizeしかなかったのでそもそもできないんじゃないか(styleみたいなのがあると期待してたがなかった)。ちなみに色やフォントはうまく指定できた。

rich text n, pl rich text : This provides the base rich text class for all iWork applications.
elements
contains characters, paragraphs, words.
properties
color (color) : The color of the font. Expressed as an RGB value consisting of a list of three color values from 0 to 65535. ex: Blue = {0, 0, 65535}.
font (text) : The name of the font. Can be the PostScript name, such as: “TimesNewRomanPS-ItalicMT”, or display name: “Times New Roman Italic”. TIP: Use the Font Book application get the information about a typeface.
size (integer) : The size of the font.

applescriptをあんまり深追いしたくないのでこの辺でやめる。

Rustのスタックとヒープについて書いてみたがまだよくわからない

変数を宣言して値を代入するということは、メモリ上のアドレスに値を設定するということらしいが、今までこの辺りのことを全く意識せずにプログラミングしてきたのでイメージがわかない。普段Rubyを書いているのでなおさら。

ふと静的言語見てみようと思ってRustのドキュメントを読んでたらちょうどメモリ管理の話が出てきたので、自分の理解をまとめてみる。

スタックとヒープ

スタック

  • Rustではintなどを使用したときにメモリが確保される場所
  • 処理が高速
  • サイズに制限がある
  • ブロックを抜けるとメモリが解放される

ヒープ

  • Rustでは、Box 型を使うことでヒープ上にメモリが確保される
  • 処理が遅い
  • 事実上サイズに制限がない

具体例

  • fn main() {}は省略する。

let x = 5;
address variable value
01 x 5

let x = 5;
let y = 10;

|address|variable|value| |:–|:–|:–| |01|x|5| |02|y|10| ``


let x = Box::new(5);
let y = 42;
address variable value
01 x ->(last)-1
02 y 42
* * *
(last)-1 5

感想

  • ざっくりだが変数を定義して値を代入したときに、メモリ上のアドレスを意識できた
  • とはいえ、だから何?、って思いがある。これがどう役に立つのかがまだわかってないからだし、理解がふわふわしている
  • 「メモリを意識してプログラミングする」というのがわからない状態で、問題が大きすぎる気がする。問題を自分が理解できるくらいまで分解する必要がある気がする

dotfiles を github で管理した

dotfilesとはドットから始まる設定ファイルのこと。.bashrcや.gitconfigなど。

自宅macと仕事macの2台使ってるんだけど、linuxコマンドやgitコマンドのaliasを自宅用と仕事用で同じにしたかった。いちいち設定ファイルを人力でコピペして同期させるのも面倒なのでコマンドで簡単に実現できる方法を探した。

調べてみると、githubでファイル管理 + シンボリックリンクを作成するシェルスクリプトを用意、している人が多かったので真似した。

dotfilesを管理しよう - Qiita

dotfilesをGitHubで管理 - Qiita

dotfilesをgithubで管理し始めた - すぎゃーんメモ

githubで公開している人も多かったので参考にした。

GitHub - yuroyoro/dotfiles: dotfiles

GitHub - sugyan/dotfiles: my dotfiles configuration

自分用のを作った。自分のはaliasくらいしか設定してないので、他の人の設定をどんどんパクっていきたい。

github.com

Working With Unix Processes を読んだ

Working With Unix Processes (English Edition)

Working With Unix Processes (English Edition)

この前読んだ本と同じ作者が書いている。

Working with TCP Sockets を読んだ - tnakata's blog

こちらもサンプルソースRubyで書かれている。Ruby書いててプロセスのことまだ何もわかってない(言語化できてない)という人にはいいかもしれない。

読もうと思った理由

  • プロセスの概念を言語化したいから。

読んでて勉強になった部分

  • プロセスとはUnix上での処理単位。どんなプログラムもプロセスの中で実行される。
    • 例えば、irbを実行するとプロセスが一つ立ち上がる。psコマンドを実行することで立ち上がったプロセスの存在を確認できる。
  • Unixでは全てはFileとして扱われる。
    • test.rbといったファイルやソケットも全てファイル。RubyではIOクラスが該当する。
    • ターミナルでのコマンドで出てくる標準入力、標準出力、標準エラー出力も同様にファイルである。
  • プロセスはforkでコピーできる(子プロセスが作られる)。並列処理に関係するところ。
  • 子プロセスが終了したのに親プロセスがProcess.waitを実行しないと、子プロセスのメモリは解放されないままになる。Zombie Processという。
  • シグナルという概念がある。プロセス間、もしくはキーボードからプロセスへシグナルを送り、プロセスを終わらせることができる。
    • Ctrl+Cによるプロセスの強制終了やProcess.kill
    • ちょっと自信がないが、ソケットを利用したやり取りとは異なる概念。
  • プロセス間の通信で代表的なものはパイプとソケット。どちらもデータの流れはSTREAM(データの始まりと終わりの目印がない)。
  • STREAMの反対はMESSAGE。UNIXソケットで利用される。
  • Daemon Processesとはバックグラウンドで走っているプロセスのこと。
  • 全てのプロセスの親としてinitプロセスがある。
  • execrc(2)で現在のプロセスを他のプロセスに変えることができる。Rubyのexecはexecrc(2)のラッパー。なおプロセスを変えた後に元のプロセスに自動で戻ることはない。
  • Ruby のexecは元のプロセスのfile descriptorsを全て閉じる。メモリリークを防ぐため。
  • PreforkingはCopy on Writeによってメモリを効率的に使用できる。
    • CoWはコピーしたプロセスを使う(たしかacceptした)タイミングでメモリがコピーされるので効率的とのこと。

読んでてよくわからなかったところは以下の通り。

  • Zombie ProcessやDaemon Processが実際にどのようなときに使われるのか
  • 仕事でやってることとどう結びつければよいか

Working with TCP Sockets を読んだ

Working With TCP Sockets (English Edition)

Working With TCP Sockets (English Edition)

一通り読んだけど結構勉強になった。サンプルソースRubyで書かれているから、Rubyを使っていてWebサーバとかソケットとかのことについて基礎から勉強したいって人にはいいと思う。

読もうと思った動機

以下に書いたわからないことを言語化したいから

  • ネットワークプログラミングについて基礎的なことをまるで知らない。
  • Railsを使っているとWebrickとかPumaとかnginxとかいろんなWebサーバが出てくるけど違いがわからない。
  • そもそもWebサーバの仕組みを知らない。

勉強になった部分

  • serverやclientがどのようなlifecycleを送るのかについて
    • server : create, bind, listen, accept, close
    • client : create, bind, connect, close
  • RubyのSocketライブラリはsystem callを呼んでいること
  • Befferがいい感じにデータ量を調整してくれること
  • forkやthreadを利用することで並列接続を実現していること
  • WebrickやPumaやnginxのような様々なサーバが存在するけど、違いは並列接続を実現させる方法だということ(ざっくり)

逆に理解できなかったところ

  • non-blocking IO が理解できた気でいるけど自信ない
  • Preforkingがどうして効率的なのか
  • Eventedは何が何だか、Hybridも

'rails -v'コマンドは何をしているのか

rails -v'コマンドは何をやっているのか

rails -vコマンドがどのような仕組みになっているか気になったので調べてみる。RailsのバージョンはRails 5.0.1

which railsコマンドで実行ファイルの場所を取得する。

$ which rails
~/.rbenv/shims/rails

rbenvを使用している場合には上記が実行ファイルとなるが、上記のファイルはただのラッパーのようである。ここでは詳細は追わないが、以下のリンクが後で理解するときに参考になりそう。

memo.sugyan.com

実際の実行ファイルをrbenv which railsコマンドで取得する。

$ rbenv which rails
~/.rbenv/versions/2.3.1/bin/rails

~/.rbenv/versions/2.3.1/bin/railsの中身を見ると、

(省略)

gem 'railties', version
load Gem.bin_path('railties', 'rails', version)

#[1] pry(main)> Gem.bin_path('railties', 'rails')
#=> "~/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/railties-5.0.1/exe/rails"

となっており、railtiesというgemのrailsという実行ファイルを実行している。 /exe/railsを見てみる。

#!/usr/bin/env ruby

git_path = File.expand_path('../../../.git', __FILE__)

if File.exist?(git_path)
  railties_path = File.expand_path('../../lib', __FILE__)
  $:.unshift(railties_path)
end
require "rails/cli"

上位のフォルダに.gitがあれば、自分自身のlibが探索されるように探索パス($:)にパスを追加している。 そしてrequire "rails/cli"cli.rbを実行している。/lib/rails/cli.rbが読み込まれるはずだが、念のためにrequire "rails/cli"の上の行にputs $:を差し込んで~/.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/railties-5.0.1/libが含まれているかどうか確認した。ちゃんとあったので多分これが読み込まれているはず。

/lib/rails/cli.rbを見てみる。

require 'rails/app_loader'

# If we are inside a Rails application this method performs an exec and thus
# the rest of this script is not run.
Rails::AppLoader.exec_app

require 'rails/ruby_version_check'
Signal.trap("INT") { puts; exit(1) }

if ARGV.first == 'plugin'
  ARGV.shift
  require 'rails/commands/plugin'
else
  require 'rails/commands/application'
end

Rails::AppLoader.exec_appを詳しく見るため、/lib/rails/app_loader.rbを見る。

require 'pathname'
require 'rails/version'

module Rails
  module AppLoader # :nodoc:
    extend self

    RUBY = Gem.ruby
    EXECUTABLES = ['bin/rails', 'script/rails']

(省略)

    def exec_app
      original_cwd = Dir.pwd

      loop do
        if exe = find_executable
          contents = File.read(exe)

          if contents =~ /(APP|ENGINE)_PATH/
            exec RUBY, exe, *ARGV
            break # non reachable, hack to be able to stub exec in the test suite
          elsif exe.end_with?('bin/rails') && contents.include?('This file was generated by Bundler')
            $stderr.puts(BUNDLER_WARNING)
            Object.const_set(:APP_PATH, File.expand_path('config/application', Dir.pwd))
            require File.expand_path('../boot', APP_PATH)
            require 'rails/commands'
            break
          end
        end

        # If we exhaust the search there is no executable, this could be a
        # call to generate a new application, so restore the original cwd.
        Dir.chdir(original_cwd) and return if Pathname.new(Dir.pwd).root?

        # Otherwise keep moving upwards in search of an executable.
        Dir.chdir('..')
      end
    end

    def find_executable
      EXECUTABLES.find { |exe| File.file?(exe) }
    end
(省略)

if exe = find_executableは現在自分がRails::Rootにいるならtrue、そうでないならfalseを返す。

カレントディレクトリがRails::Root以外

ここでは、一旦homeディレクトリにいるとして続きを見ていく。

cli.rbに戻ると、今回のコマンドの引数は-vなのでrequire 'rails/commands/application'が実行されることがわかる。 /lib/rails/commands/application.rbでは、args = Rails::Generators::ARGVScrubber.new(ARGV).prepare!が実行されている。

require 'rails/generators'
require 'rails/generators/rails/app/app_generator'

module Rails
  module Generators
    class AppGenerator # :nodoc:
      # We want to exit on failure to be kind to other libraries
      # This is only when accessing via CLI
      def self.exit_on_failure?
        true
      end
    end
  end
end

args = Rails::Generators::ARGVScrubber.new(ARGV).prepare!
Rails::Generators::AppGenerator.start args

Rails::Generators::ARGVScrubber.new(ARGV).prepare!を詳しく見るために、/lib/rails/generators/rails/app/app_generator.rbを見る。

    # This class handles preparation of the arguments before the AppGenerator is
    # called. The class provides version or help information if they were
    # requested, and also constructs the railsrc file (used for extra configuration
    # options).
    #
    # This class should be called before the AppGenerator is required and started
    # since it configures and mutates ARGV correctly.
    class ARGVScrubber # :nodoc:
      def initialize(argv = ARGV)
        @argv = argv
      end

      def prepare!
        handle_version_request!(@argv.first)
        handle_invalid_command!(@argv.first, @argv) do
          handle_rails_rc!(@argv.drop(1))
        end
      end

(中略)

      private

        def handle_version_request!(argument)
          if ['--version', '-v'].include?(argument)
            require 'rails/version'
            puts "Rails #{Rails::VERSION::STRING}"
            exit(0)
          end
        end

prepare!メソッド内で呼び出しているhandle_version_request!Railsのバージョンを表示していることがわかった。

これでRails::Root以外の場所からrails -vコマンドを打った時の流れがわかった。

カレントディレクトリがRails::Root

次にRails::Rootにいる場合を見ていきます。Rails::AppLoader.exec_appを再び見ていくと、bin/railsをreadしている。

#!/usr/bin/env ruby
APP_PATH = File.expand_path('../../config/application', __FILE__)
require_relative '../config/boot'
require 'rails/commands'

APP_PATHという文字列が含まれているので、Rails::AppLoader.exec_app

          if contents =~ /(APP|ENGINE)_PATH/
            exec RUBY, exe, *ARGV

により、bin/railsRubyスクリプトとして実行される。bin/rails../config/bootrails/commandsを実行する。

# ../config/boot

ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)

require 'bundler/setup' # Set up gems listed in the Gemfile.

詳細までは追いませんが、gemを使用可能にできるようロードパスを更新してるみたい。

Bundler: The best way to manage a Ruby application's gems

# rails/commands

ARGV << '--help' if ARGV.empty?

aliases = {
  "g"  => "generate",
  "d"  => "destroy",
  "c"  => "console",
  "s"  => "server",
  "db" => "dbconsole",
  "r"  => "runner",
  "t"  => "test"
}

command = ARGV.shift
command = aliases[command] || command

require 'rails/commands/commands_tasks'

Rails::CommandsTasks.new(ARGV).run_command!(command)

railsコマンドに続けて入力した引数をここで変数として取り扱っている。rails sといったエイリアスもここで定義されている。そして、rails/commands/commands_tasksをrequireしている。rails/commands/commands_tasksでは、

require 'rails/commands/rake_proxy'

module Rails
  # This is a class which takes in a rails command and initiates the appropriate
  # initiation sequence.
  #
  # Warning: This class mutates ARGV because some commands require manipulating
  # it before they are run.
  class CommandsTasks # :nodoc:
    include Rails::RakeProxy

    attr_reader :argv

(中略)

    def initialize(argv)
      @argv = argv
    end

    def run_command!(command)
      command = parse_command(command)

      if COMMAND_WHITELIST.include?(command)
        send(command)
      else
        run_rake_task(command)
      end
    end

(中略)

    def version
      argv.unshift '--version'
      require_command!("application")
    end

(中略)

    private

(中略)

      def require_command!(command)
        require "rails/commands/#{command}"
      end

(中略)

      def parse_command(command)
        case command
        when '--version', '-v'
          'version'
        when '--help', '-h'
          'help'
        else
          command
        end
      end
  end
end

となっており、Rails::CommandsTasksを定義している。 Rails::CommandsTasks.new(ARGV).run_command!(command)Rails::CommandsTasksインスタンスを作り、run_command!メソッドを実行している。run_command!では、まずparse_commandエイリアスを取り扱い、versionコマンドとしてまとめて扱うようにしている。そして、versionメソッド→require_command!メソッドの順に実行され、最終的にrails/commands/applicationを実行する。

ここから先は、Rails::Root以外から実行した場合と同じになる。

Rubyではなぜmix-inでクラスメソッドを引き継げないのか

includeと継承の違い

includeメソッドによりモジュールをinclude(Mix-in)してそのモジュールのインスタンスメソッドを使えるようになります。クラスメソッドは使えません。

instance method Module#include (Ruby 2.4.0)

一方、継承では親となるクラスのインスタンスメソッド・クラスメソッドの両方を使えるようになります。

module Foo
  def foo_instance_method
    "foo_instance_method"
  end
  def self.foo_class_method
    "foo_class_method"
  end
end

class SuperBoo
  def boo_instance_method
    "boo_instance_method"
  end
  def self.boo_class_method
    "boo_class_method"
  end
end

class Boo < SuperBoo
  include Foo
end

boo = Boo.new
boo.boo_instance_method #=> "boo_instance_method"
boo.foo_instance_method #=> "foo_instance_method"

Boo.boo_class_method #=> "boo_class_method"
Boo.foo_class_method #=> NoMethodError: undefined method `foo_class_method' for Boo:Class

上記でBooクラスがFooモジュールをincludeしたことでBooクラスの継承チェーンにFooモジュールが組み込まれます。

Boo.ancestors #=> [Boo, Foo, SuperBoo, Object, PP::ObjectMixin, Kernel, BasicObject]

ここだけ見ると、includeは継承の一種かなと思ってしまいます。違いが出るのは特異クラスの継承チェーンを見たときです。

Boo.singleton_class.ancestors #=> [#<Class:Boo>, #<Class:SuperBoo>, #<Class:Object>, #<Class:BasicObject>, Class, Module, Object, PP::ObjectMixin, Kernel, BasicObject]

上記で確認できるように、includeしたモジュールは特異クラスの継承チェーンには出てきません。このため、includeしたモジュールのクラスメソッドを使用できません。つまり、includeと継承は一見似ているようですが全く異なる処理となります。

includeしたモジュールのクラスメソッドも使いたい

上記の通り、includeしたモジュールからはクラスメソッドを引き継げません。このため、ある責務を持ったモジュールのクラスメソッドを複数のクラスへ引き継ぎたいという場合にどうするかという問題が出てきます。この場合はincludedを使った実装をするのが一般的です。

qiita.com

module Foo
  def foo_instance_method
    "foo_instance_method"
  end
  def self.included(base)
    base.extend(ClassMethods)
  end
  module ClassMethods
    def foo_class_method
      "foo_class_method"
    end
  end
end

class SuperBoo
  def boo_instance_method
    "boo_instance_method"
  end
  def self.boo_class_method
    "boo_class_method"
  end
end

class Boo < SuperBoo
  include Foo
end

Boo.foo_class_method #=> "foo_class_method"

上記の実装はRailsでも使用されています。

なぜincludeでクラスメソッドを継承しないのか

そもそもなぜincludedを使用するようなめんどくさいことをしないとincludeしたモジュールのクラスメソッドを引き継げないのでしょうか。

上記で述べたようにincludeでクラスメソッドを引き継ぐ、という要望は普通にあるみたいです。であれば、Rubyの仕様としてincludeしたモジュールのクラスメソッドも引き継ぐというのはありだったんじゃないか、ということを考えました。その方がincludedを使用せずともクラスメソッドが使えるようになるから楽ですよね。

多重継承と同じになってしまうから、というのも考えましたがまあこれは違うかと。複数のモジュールをincludeしても継承しても、継承チェーンの中での順番は確定するからです。

とはいえ、includeでクラスメソッドを継承しないという選択をしているからには何かしらのメリットがあるということです。このメリットが何か私にはわからなかったので調べてみることにしました。

includeが使用されるそもそもの想定とは

Ruby forumの過去のスレッドで同じようなことを疑問に思った人がスレッドを立てていました。

Why the lack of mixing-in support for Class methods? - Ruby Forum

疑問に対するmatzさんの回答もありました。

https://www.ruby-forum.com/topic/68638

引用します。

Mix-in is used for several purposes and some of them can be hindered by inheriting class methods, for example, I don’t want to spill internal methods when including the Math module. I am not against for some kind of inclusion that inherits class methods as well, but it should be separated from the current #include behavior.

以下、matzさんの考えへの私の解釈です。

Mix-inが使用される想定(例えば、後述するMathモジュールみたいな使い方)がいくつかあるんだけど、クラスメソッドを継承したらそれがうまくいかない。 例えば、Mathモジュールを継承するときにクラスメソッドまで継承したら、includeしたクラスでメソッド名(名前空間)がかなり侵されてしまうのを避けたい。Mathモジュールの使い方を見ると、インスタンスメソッドとクラスメソッドにおそらく同名のメソッドを持っています。MathモジュールをincludeすればMathモジュールのクラスメソッドは不要になりますが、この時クラスメソッドも継承していると不要になったクラスメソッドの分だけincludeしたクラスでメソッド名の衝突が起きてしまう恐れがあります。