Tallman

技術とか読書とかいろいろ

tokyo.ex#13 elixir本体ソースコードもくもくリード会に参加してEnum.tally書いてみた

「tokyo.ex #13 elixir本体ソースコードもくもくリード会」に参加してきました。

beam-lang.connpass.com

Elixirのソースコードを事前にcloneしておいてMakefileなんかを見ながらエリクサーのコンパイルの流れを解説していただきました。

speakerdeck.com

speakerdeck.com

Makefile is 何?から解説してくださっている発表もありElixir以前の段階で勉強になりました。

ふんわり理解をつぶやいたら登壇者の@hayabusa333氏に補足してもらえました。ありがとうございます!

 もくもく会

実際のもくもく会の内容は「Elixirのバージョンを書き換えよう」と「標準モジュールの追加」の2つがありました。

Elixirのバージョンを書き換えよう

ElixirのversionはrootディレクトリのVERSIONというファイルに記載されているのでこれを書き換えてmakeすればok

実際にmakeしてみると

~~~~~~
Recompile: src/elixir_aliases
Recompile: src/elixir
Generated elixir.app
==> bootstrap (compile)
Compiled lib/elixir/lib/kernel.ex
Compiled lib/elixir/lib/macro/env.ex
~~~~~~

スライドどおりelixir.appが作成されてbootsrapという関数が実行されているのがわかりますね。

標準モジュールの追加

標準モジュールの追加ですが、自分はモジュールの追加ではなく組み込みモジュールのEnumに関数を一つ追加しました。Rubyで新しく実装されたtallyです。単にtallyが好きで書きたかっただけ

ソースコードはこんな感じです。

  def tally(enumerable, func \\ fn x -> x end) do
    group_by(enumerable, func)
    |> map(fn {key, val} -> {key, count(val)} end)
    |> Map.new
  end

ちょっと例外までは考えきれてないんですが現状のElixirの知見で書いてみました。*1

せっかくなのでテストもElixir本体に書いてみました。

  test "tally/2" do
    assert Enum.tally(~w{a a a b c c}) == %{"a" => 3, "b" => 1, "c" => 2}
    assert Enum.tally(~w{aa aA bb cc}, fn x -> String.downcase(x) end) == %{"aa" => 2, "bb" => 1, "cc" => 1}
  end

Elixir本体のテストはコンパイルしたら直接ファイル指定して実行できます。*2

 > bin/elixir lib/elixir/test/elixir/enum_test.exs

Excluding tags: [windows: true]

......................................................................................................................................................................................................................................................................................................................................................

Finished in 2.0 seconds (1.9s on load, 0.05s on tests)
180 doctests, 162 tests, 0 failures

Randomized with seed 313260

けっこう便利なメソッドだしドキュメントちゃんと書いてElixirにPR出してみようかなとか思ってます。 Elixirがこういうメソッドどう考えてるのかはわかりませんが。

Elixirコミュニティについて感じたこと

福岡を中心に西日本で盛り上がってるみたいですね。Rubyも福岡は元気なイメージだし福岡熱い。
Rubyの勉強会にいくとほとんどの方がwebエンジニアという印象ですが、Elixirでは組み込みやゲーム領域のエンジニアの方もいて新鮮でした。ゲームとか組み込みとかとの親和性もElixirの魅力ですね。

感想

とても楽しい勉強会でした。主催者の方、参加者の方、会場を提供していただいた株式会社ドリコム様ありがとうございました!

*1:もっといい書き方あるで!という方ぜひ教えてください:pray:

*2:https://github.com/elixir-lang/elixir#contributing

Elixirについて社内LTで発表したよ

最近Elixirに興味をもってプログラミングElixirという本で勉強してます
社内LTでそのことについて発表してみました https://speakerdeck.com/qwyng/elixirtopatanmatuti

再代入の説明に@cedretaber氏のこの記事 qiita.com を参考にしたところがあります。

speakerdeck.com

以下Elixirについて思ったこととか

再帰の考え方を学べる

再帰の考え方が学べる言語かと思います。パターンマッチでを使うと引数で処理を分けたりができるのでifを使うことがあまりなさそう

JSON解析が強そう

プログラミングElixir読むとわかるのですがパターンマッチが根幹をなしてる言語なのでJSONの解析がCoolにできます。

関数型楽しい

Rubyだとオブジェクトが主役でメソッドがオブジェクトのAPIぐらいの考え方だったんですけど、Elixirでは関数が主役でオブジェクトじゃなくて「値」って感じがします。 脳に新たな刺激が入ってくる感じで楽しいです。
破壊的変更がないって読みやすくなりますね。

プログラミングElixirが神本

これが一番言いたかったことなんですけど

プログラミングElixir

プログラミングElixir

この本すごいです。他の言語からElixirを学ぶためにこの本を読むと「これこれこの難易度感で語って欲しいねん〜」が頻発します。 また、練習問題が散りばめられており、いい感じに手を動かしながら読み進めることができます。(練習問題は答えがのってないので間違ってるかもしれない)

github.com

GitHubAPIからisuueとってきてきれいにターミナルに表示するというライブラリ作成ハンズオンまであります。 私的にメタプログラミングRubyレベルでいい本かと思ってます。

おまけ

ruby-jpでこの本の内容について質問して「訳とか文脈の問題ですかね〜」とかのたまったら訳者である笹田耕一さんに「訳大丈夫ですか」と聞かれて震えました。 (翻訳ありがとうございます!、翻訳のおかげで自分もこの本の内容を少しづつ読み進めることができます:bow:)

超絶怒涛のゆるふわコードリーディング GraphQL gem篇

GraphQL gemをふんわり読んだ

ふんわり読んで見ました、とりあえずよく使われてそうなDSLをおったメモ
versionはv1.9.12 が最新リリースである時期のmasterブランチ読みました。
ゆるふわの極みです

とりあえず最初に公式のGetting Startedで出てくるやつ

module Types
  class PostType < Types::BaseObject
    field :id, ID, null: false
    field :title, String, null: false
    field :truncated_preview, String, null: false
    field :comments, [Types::CommentType], null: true,
      description: "This post's comments, or null if this post has comments disabled."
  end
end

field is 何?

GraphQL::Schema::ObjectがextendしてるGraphQL::Schema::Member::HasFieldsのメソッド。

def field(*args, **kwargs, &block)
  field_defn = field_class.from_options(*args, owner: self, **kwargs, &block)
  add_field(field_defn)
  nil
end

field_class is 何?

def field_class(new_field_class = nil)
  if new_field_class
    @field_class = new_field_class
  elsif @field_class
    @field_class
  elsif self.is_a?(Class)
    superclass.respond_to?(:field_class) ? superclass.field_class : GraphQL::Schema::Field
  else
    ancestor = ancestors[1..-1].find { |a| a.respond_to?(:field_class) && a.field_class }
    ancestor ? ancestor.field_class : GraphQL::Schema::Field
  end
end

引数はわたしてないので一個目の分岐は飛ばして、@field_classも(多分素のままだと)設定されてないのでGraphQL::Schema::Fieldか何がしかのクラスが返されているっぽい GraphQL::Schema::Field.from_optionsはオプションを色々整えた後newしてインスタンス返してくれるメソッド。

def self.from_options(name = nil, type = nil, desc = nil, resolver: nil, mutation: nil, subscription: nil,**kwargs, &block)

引数の数すごい

最終的に下のコードはGraphQL::Schema::Fieldかサブクラスかなにかのインスタンス作ってると言えそう。

  field_defn = field_class.from_options(*args, owner: self, **kwargs, &block)

add_field is 何

つぎはこの行

  add_field(field_defn)
def add_field(field_defn)
  if CONFLICT_FIELD_NAMES.include?(field_defn.original_name) && field_defn.original_name == field_defn.resolver_method
    warn "#{self.graphql_name}'s `field :#{field_defn.original_name}` conflicts with a built-in method, use `resolver_method:` to pick a different resolver method for this field (for example, `resolver_method: :resolve_#{field_defn.original_name}` and `def resolve_#{field_defn.original_name}`)"
  end
  own_fields[field_defn.name] = field_defn
  nil
end

:context, :object, :method, :class 書くと警告されるっぽい。 中身はハッシュで名前をキーにしてfield_class.from_optionsでつくったインスタンスをそのまま突っ込んでる。

んでこのハッシュは下記の2つのメソッドで使ってるっぽい

def fields
  # Local overrides take precedence over inherited fields
  all_fields = {}
  ancestors.reverse_each do |ancestor|
    if ancestor.respond_to?(:own_fields)
      all_fields.merge!(ancestor.own_fields)
    end
  end
  all_fields
end

def get_field(field_name)
  if (f = own_fields[field_name])
    f
  else
    for ancestor in ancestors
      if ancestor.respond_to?(:own_fields) && f = ancestor.own_fields[field_name]
        return f
      end
    end
    nil
  end
end

多分実行してるときに使ってるコード

public_sendしてますね。引数も渡せそう。
オブジェクトがもともとメソッドもってればなんでもフィールドにできる感じなのかな?

  def resolve_field_method(obj, ruby_kwargs, ctx)
    if obj.object.is_a?(Hash)
      inner_object = obj.object
      if inner_object.key?(@method_sym)
        inner_object[@method_sym]
      else
        inner_object[@method_str]
      end
    elsif obj.object.respond_to?(@method_sym)
      if ruby_kwargs.any?
        obj.object.public_send(@method_sym, **ruby_kwargs)
      else
        obj.object.public_send(@method_sym)
      end
    else
      raise <<-ERR
    Failed to implement #{@owner.graphql_name}.#{@name}, tried:

    - `#{obj.class}##{@resolver_method}`, which did not exist
    - `#{obj.object.class}##{@method_sym}`, which did not exist
    - Looking up hash key `#{@method_sym.inspect}` or `#{@method_str.inspect}` on `#{obj.object}`, but it wasn't a Hash

    To implement this field, define one of the methods above (and check for typos)
    ERR
    end
  end

前半の分岐がよくわからんけど @method_symはこんな感じでinitializeされるときに入れてる

method_name = method || hash_key || @underscored_name
@method_sym = method_name.to_sym

まとめ

ちょっと読んだ後公式のドキュメントみて使い方知らないとわけわからんことに気づいたのでここで終わりです。
OSSに貢献してる人これ無償で読んで理解して書いてるってすごすぎませんか??????

エンジニアとして働いて

エンジニアとして働き初めて半年がたった。まだまだわからないこと、初めて知ることばかりであり、周りの方の助けを借りながらなんとかやっている。
なんとなく書きたくなったのでエンジニアとして働き初めて感じたこととかをまとめようと思う。

エンジニアとして働くことは幸せなこと

エンジニアは幸せな仕事だと思う。自分がやっていることと仕事を簡単に結びつけやすい職業だ。仕事を人生にしやすいと感じる。
自分が趣味やら素振りやらで培った技術がすぐに仕事に活かせるし、そうでなくても原理原則をしれば仕事のツールとして絶対に効いてくる。
自分は現在教育分野での開発をしているのだけど、教育とITの相性もいいし教育とエンジニアの相性もとてもいいと思う。学んで試してを繰り返している人がエンジニアは多いように思うし、教育も自走できる人間を育てるのが一番の目指すところだと思う。自分ができなかったことができるようになっていくのも、知らなかったことを知るのも楽しい。
僕は大学までノホホンとしていて自分から学ぶことなんてゲーム以外でしなかったが、プログラミングを学び始めてからは知ることも試すことも全て自分から学びに行っている実感がある。誰に言われるでもなく自分が知りたいことを自分から学びに行くことがこんなに楽しいことだとは知らなかった。ましてやそれが実際に収入に結びつくなんて最高だ。
自分が頑張った分だけ良くなっていくという希望はこの仕事を通して初めて感じた。 転職した理由は身近で仕事を楽しそうに話す人がエンジニアだったという理由だったが間違いではなかったと思う。

いわゆる技術と同等に大事だと思うこと

ITエンジニアとしての技術力それ自体ももちろん大切だけど、半年働いてみてこれも重要だなぁと思うこともできた。

チームの雰囲気を良くしていく力

チームの効率を上げたりだとかも重要かもしれないけど、それ以上に雰囲気を和ますというか適度にチャットしたりおしゃべりできるような空気を作る力というのはものすごく大きい思う。心理的安全性というやつだろうか。いつも笑ってるぐらいがちょうどいいと思う。

他業種に対する敬意

売上を出しているのはコードだけではない。営業の人や方針をきめた経営者やユーザーの声を最初に聞いてくれる人、日々プロダクトとユーザーについて真摯に向き合っている人等大勢の人が総合して売上をだしている。
自分も前職のときは自分の組織が使っているシステムを必死に使っていた。他業種の大変さとかを理解するのは人間がわかりあうのは難しいので難しいが、少なくとも敬意をもって考えを聞く姿勢は常に持っているのが大事だと感じた。みんなそれぞれの考えを持って働いているのだ。

人に頼ること

わからないことをきちんとわからないということも大切だし、人間が一人でできることには限界があることを認めることも重要だと思う。
自分が余り知らない機能のコードがあったとして、自分でじっくりそのコードを読むこともいいが、その機能に詳しそうな人に伺うという行為をちゃんとすることも大事だと思う。得意でない事を得意な人に聞いている人というのは素晴らしい人だと思う。

これから

どうなるか知らんけど楽しく働いて行きたい

「試して理解 Linuxのしくみ」を読んだ

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

[試して理解]Linuxのしくみ ~実験と図解で学ぶOSとハードウェアの基礎知識

読んだ理由

せっかく自宅の環境をUbuntuにしたので

sasa5740.hatenablog.com

Linuxの基本的な知識知りたいなぁと思ったのと、A Tour of Goとかruby/rubyの並行テストとか見て並行処理の意味が全くわからんちんだったので、プロセスを詳しく知るために購入した。

全体的な感想

試して理解ということでサンプルコードがたくさん載っていた。最初はGitHubに全部上がってるの知らずにポチポチC言語写経しながら読んでいた。

実際にハードウェアにアクセスするのがカーネルで、普段僕が書いてるアプリケーションはあくまでプロセスとしてシステムコールのラッパー関数を呼び出しているだけ、という事からして初めて知ったので、全編に渡って新しい発見の連続だった。
あと著者の方の環境がメモリ32Gで人権を感じた。

要約感想的なもの

 3章

  • fork()関数もこの本で初めて知った。webサーバーがなんで同時に複数のリクエストをさばけるのか少しわかりかけてきた。

techracho.bpsinc.jp

(この記事の意味がようやく少しわかった。)

 4章 プロセススケジューラー

  • 論理CPU一個が同時に二つプロセス動かすことはない。プロセスはコンテキストスイッチでそのプロセス関係なく処理が一旦止まることがありうる。

  • 単位時間あたりの処理量と結果的な処理にかかる時間はトレードオフ

     5章 メモリ管理

  • メモリアドレスを直接扱うのはプロセスにはまだ早すぎるので仮想アドレスを使う。

  • ページテーブルはルーティングテーブルみたいなもの。コンピュータというのはどこも二分探索にするための工夫が多い気がする。

  • デマンドページング 仮想メモリ確保 != 物理メモリ確保

  • copy on write fork()時にいきなり新しい物理メモリを確保するのではない。 書き込むときに子プロセス用のメモリを確保して書き込む。

  • ヒュージページ 葉を増やすと枝も増える。仮想メモリと物理メモリの関係も増える。単位を大きくして防ぐ。 ubuntu 19.04ではトランスペアレントヒュージページのデフォルトはmadvise()だった。

     6章 記憶階層

  • メモリとキャッシュメモリは違うもの

  • キャッシュメモリに局所性(実際に使うところ)をいかにおさめるか

  • メモリにキャッシュしたものをダーティにして実際に変更してダーティを解除

  • ページテーブルもCPUにあるTLBで

  • Linuxはファイルを可能な限りページキャッシュしようとするって大規模サービス実践技術入門にもあった気がする。実際有無を比べると早さが違う

  • write backは自分の環境でも五秒に一回だった。想像の何百倍も間隔長かった。ダーティのまま中断は普通に起こりそう。 

     7章 ファイルシステム

  • ファイルシステムがないとお前がカーネルになるしかない

  • ファイルシステムでもcopy on write 書き換えるときはコピーする

     8章 ストレージデバイス

  • ファイルは連続あるいは近く

  • 連続する領域へのアクセスは一回で

終わりに

サンプルコードや実際に手を動かすところが多かったのも良かったけど、解説において過程を飛ばさないで一つ一つ全部図解してくれていたことがありがたかった。
どうやって並行処理の整合性保ってるのかはあんまりわかってない。 子プロセスで書き込みしたらcopy on writeで別の物理アドレスになってしまうのでは???
次は「なるほどUNIXプロセス Rubyで学ぶUnixの基礎」を買ったのでまた知識の上塗りをしていきたい。

rails consoleに簡単にオプションを足せるようになるgemを作った

作ったgemはこれです

github.com

使い方

config/application.rbに下のようにオプションをセットします。
Rails::Applicationの上に書くようにしてください。

Bundler.require(*Rails.groups)

D4C::Console.add_option 'hello' do
  puts "hello"
end

module YourRailsApplication
  class Application < Rails::Application

これでrails consoleに渡せるオプションを追加できました。実際にオプションが呼ばれると渡したブロックがcallされます。

rails c --hello
Running via Spring preloader in process 7922
hoge
Loading development environment (Rails 5.2.3)
irb(main):001:0> 

なんで作ったか

rails consoleを叩くときに、「デフォルトで実行されたくないけど実行したいときはさっと実行したい」処理があったからです。
職場でact_as_tenantというgemを使っています。dbのレコードに制限をかけるgemです。
act_as_tenantの都合上、rails consoleからdbのレコードを叩くには

ActsAsTenant.without_tenant do
end

として作業しなくてはなりませんでした。
とはいえRailtieconsoleメソッドでせっかくかけたセキュリティをデフォルトで外してしまうことはしたくありません。
--sandboxみたいにオプションで選択できると良いと考え、このgemを作りました。 1

実装の話

そもそもrails console --sandboxをした時の流れ

bin/rails console自体の具体的な流れはRailsガイドにとっても詳しく書いてあるので読みましょう。

railsguides.jp

では--sandboxはどうやって処理されているのでしょうか。 (注 Railsのコードを載せていますが色々省いています。)

Rails::Command::ConsoleCommandクラスは下のようになっています。

      class_option :sandbox, aliases: "-s", type: :boolean, default: false,
        desc: "Rollback database modifications on exit."

      def perform
        Rails::Console.start(Rails.application, options)
      end

受け取れるコマンドラインのオプションをclass_optionで追加しています。
Rails::Command::ConsoleCommandはThorというコマンドラインツールの作成を助けるgemを継承しています。class_optionはそのgemのメソッドです。

Rails::Applicationクラス内では--sandboxが入力されたかを保存できるようにしてあります。

    attr_accessor :assets, :sandbox

Rails::Consoleクラスでは、コマンドラインから渡されたオプションの有無をアプリケーションのインスタンスに渡しています。

    def initialize(app, options = {})
      @app     = app
      @options = options

      app.sandbox = sandbox?
    end

    def sandbox?
      options[:sandbox]
    end

これによりif app.sandboxで条件分岐できるようになります。

実際にD4Cで行った拡張

module Rails
  Application.prepend D4C::PrependOptionAccessor
  Console.prepend D4C::PrependOptionSetter
  Command::ConsoleCommand.prepend D4C::PrependClassOption
end

D4CのmoduleはD4C::Console.add_optionに渡されたオプションをRailsのクラスが扱えるようにするものです。 それぞれprependしてmoduleの中でsuperすることで処理を足しています。
D4C::Consoleの中身はオプションを構造体にして保存するクラスのインスタンスです。

Gemの名前

ジョジョの奇妙な冒険のスタンド名です。
最初はbaby_faceって名前でした、コンソールなので。
ディ・モールトベネなgemの名前だと思いながら作成して、いざ公開しようと思ったら同じ名前のgemが既にあって泣きました。皆さんはこうならないようにちゃんとrubygemsで名前を検索してから作りましょう。
最終的にたやすくオプションを足せるってところからD4Cにしました。
最後にこれが一番書きたかったんですけど、D4Cってまさに「Ruby」だと思うんですよね。

f:id:sasa5740:20190819222624p:plain


  1. ruby-jpでgemを晒したら「pryrcirbcに書くのも手ですがよりチームでコミットしやすいメリットもあるかも。」というお言葉もらいました。pryrcirbc書くのも手ですね。ありがとうございます。ruby-jpは本当に神。

クラスメソッドをPryのコマンドにできるGemをリリースしました

作ったGemはこれです

github.com

使い方

まず.pryrc内で下のようにクラスをセットします。

PrySingular.make_commands HogeJob, FactoryBot

これでPryの中で設定したクラスの特異メソッドをコマンドとして使うことができます。

pry(main)> perform_now

引数を渡すこともできます(あくまでコマンドなので引数との間はスペースが必要)

pry(main)> create :user

コマンドにしたいメソッドを直接指定することもできます。

PrySingular.make_commands FactoryBot, only: [:build, :attributes_for]

コマンドにしたくないメソッドを指定することもできます。

PrySingular.make_commands FactoryBot, except: :create

コマンドなのでObjectを汚してメソッドを生やすことはありません。

[1] pry(main)> self
=> main
[2] pry(main)> self.respond_to?(:create)
=> false

また、内部ではsingleton_methods(true)を使っているので、classとかrubyのコアな部分まではコマンドを設定しません。

f:id:sasa5740:20190715124804j:plain
超絶怒涛の神issueとPRを立ててくれた@fursichさんありがとうございます!*1

作った理由

FactoryBotのcreateとかActsAsTenantのwithout_tenantとかをいちいちrails cの中で書くのめんどい〜みたいな話が周りで起こり、.pryrcの話がにわかに盛り上がりました。そのとき職場の人がPry::ClassCommandでゴニョゴニョといっていたのを聞いたのが始まりです。
最初はFactoryBotとPryを連携させたいな〜と思って作っていたのですが、書いていくうちに「これクラス設定してもらって全部できるようにしたほうが便利だな」と思って実装してみました。

実装

コアな部分はここです

def import_class_command(klass, options)
  singular_methods = adapt_options_singleton_methods(klass, options)
  set_pry_command do
    singular_methods.each do |klass_method|
      command "#{klass_method}", "#{klass}.#{klass_method}" do
        extend PrySingular::Slop
        klass.class_eval <<-EOS
          #{parse_singular_method_command(Readline::HISTORY.to_a.last)}
        EOS
      end
    end
  end
end

def set_pry_command(&block)
  commands = Pry::CommandSet.new &block
  Pry.config.commands.import(commands)
end
module PrySingular
  module Slop
    def parse_singular_method_command(items)
      method, args = items.split(" ", 2)
      method + ' ' + args.gsub(' ', '')
    end
  end
end

commandはStringオブジェクトをメソッドの名前、メソッドの説明の順に引数に取って、コマンドが呼ばれたらブロックの中身を実行してくれるPryのメソッドです。
仕組みとしてはコマンドが呼ばれたらReadline::HISTORYの最新のものを一度メソッドと引数に分けてから引数の空白を削除し、対象のクラスをレシーバーにclass_evalして直接実行しています。

pry(main)> create :user

と呼ばれたら最終的に

FactoryBot.class_eval <<-EOS
  create :user
EOS

が実行されるといった具合です。 なのでコマンドの実装の中身は全て、クラスにReadlineから受け取った文字列をズルっとわたしているだけです。

Gemを作ってみて

Gem作成は初めてでしたが、RubyGemsのおかげで簡単に公開することができました。
PryのDSLの柔軟性にも助けられましたし、オプションの設定方法もRailsのパクリです。 巨人の肩は高い 。
反省点としてはリリースしてはだめじゃんを繰り返して既にversionが0.1.7なことですね…
まだまだテストもカバレッジ足りないしリファクタもできると思うし、そもそもまだバグもあるかもしれないので、よいGemにできるようやっていきです。
よかったら使ってみてください、バグとかこれがあったら面白いとかあればissueやコメントで教えてくれたら泣いて喜びます。

2019/07/21 追記

インターフェースをmake_commandsにしたり内部のコードいろいろかえました

*1:ブログに乗せることも快く了承いただき本当にありがとうございます