Yggdroot/indentLineのせいでmarkdownの表示がおかしくなった

markdown編集中に"*hogehoge"が"hogehoge"のように変換されて表示されてしまう。この表示の仕方はVimのmarkdownモードの機能の1つで、set conceallevel=2が設定されているとこう表示する。

問題はset conceallevel=2などした覚えが無いということで、調べるとどうやら"Yggdroot/indentLine"が内部でset conceallevel=2をしているようだ。勝手なことをするなよな。

Ruby初心者(つまり昨日の僕)に伝えたいRubyの7つのハマり所

書くのが楽しいプログラミング言語であるRubyですが、それでも「落とし穴」がいくつも存在します。

その中から私が最近ハマった落とし穴を7つ紹介します。

シンボルと文字列は異なる

Rubyには「文字列(String)」("a" とか 'Hello world!')の他に、文字列に似た「シンボル(Symbol)」(:aとか:hello_worldとか)があります。*1。両者は異なる型なので、例えば 'a' != :a となります。

そのため、キーがシンボルであるハッシュ(Hash)から、文字列を使って値を取り出そうとするとnilが返されます。

h = { :ja => '日本語', :en => '英語', :fr => 'フランス語' }

p h['ja'] # => nil
p h[:ja] # => "日本語"

ちなみに、シンボルはハッシュのキーとしてよく使われるため、=> を使わない短縮記法があります。

# これは・・・
h = { :ja => '日本語', :en => '英語', :fr => 'フランス語' }

# こうも書ける
h = { ja: '日本語', en: '英語', fr: 'フランス語' }

シンボルと文字列は異なる

Rubyには文字列(String)の他に、文字列に似たシンボル(Symbol)があります。*2。両者は異なる型なので、例えば 'a' != :a となります。

ハッシュのキーにはシンボルがよく使われますが、例えばRailsparams(HTTPのクエリパラメータ)のキーは文字列です。 したがって、文字列を使ってparamsから値を取り出そうとするとnilが返されます。

class XYZController
  def search
    params[:lang] # => nil
  end
end

なお、Railsはハッシュのキーをシンボルに変換するHash#symbolize_keys等の一連のメソッドを追加します。

また、JSON.parseにはJSONのキーをシンボルに変換するsymbolize_namesパラメータがあります。

シンボルと文字列は異なる

Rubyには文字列(String)の他に、文字列に似たシンボル(Symbol)があります。*3。両者は異なる型なので、例えば 'a' != :a となります。

そのため、シンボルを文字列と連結したり、文字列のメソッドを呼びだそうとするとエラーになります。

a = 'hello'
b = :world

a + b # => TypeError: no implicit conversion of Symbol into String

b.tr 'o', 'x' # => NoMethodError: undefined method `tr' for :world:Symbol

このような時は.to_s.to_symで、文字列とシンボルを相互に変換します。

また、文字列連結の代わりに式展開を使うこともできます。 式展開はシンボルだけでなく数値や他の型つかえますし、文字列連結を使うよりも可読性が高くなります。 そのため、Rubyでは文字列連結を使う機会はあまり多くありません。

# これよりも
a + ' ' + b

# こうするべき
"#{a} #{b}"

シンボルと文字列は異なる

Rubyには文字列(String)の他に、文字列に似たシンボル(Symbol)が・・・すみません、これで最後にします。

method_missingの引数はシンボルです。実際のところ、シンボルに文字列操作を施す機会はメタプログラミング以外はあまりありません。

なので、method_missingでこんなコードを書くとエラーになります。

def method_missing(name, *args)
  # エラーになるコード
  if name.end_with?('!')
     # ...
  end
  super(name, *args)
end

privateメソッドをクラス外から呼び出すとmethod_missingが呼ばれる

黒魔術だなんだと言われつつ使わざるをえないmethod_missing について。実はmethod_missingが呼ばれるのはメソッドが無い場合だけではないとことです。

呼ぼうとしたメソッドprotectedprivateであった場合もmethod_missingは呼ばれます。

class Foo
  def method_missing(name)
    p name
  end

  private

  def greeting
    p 'hello'
  end
end

foo = Foo.new
foo.greeting # => :greeting

だから、method_missingは黒魔術なのであって、method_missingなんて使うのは止めましょう。

Module#define_method が作るメソッドはpublicになるとは限らない

黒魔術でもうひとつ。メソッドを動的に定義するdefine_methodは、privateの後ではprivateなメソッドを定義します。

class FooController
  def index
    # 普通のメソッド
  end

  private # あとから foo_params を追加!

  def foo_params
    #...
  end

  %w(name email age).each do |name|
    define_method "show_#{name}".to_sym do
      # ...
    end
  end

なので、「ゴチャゴチャした処理は後ろに書こう」とクラスの後ろでdefine_methodを書いていて、 途中にprivateなメソッドを追加したりすると、思わぬ動作になります。

ちなみに「privateの後ではprivateになる」のはattr_readerなども同様です。

Structはキーワード引数を取らない

最後に、真面目に罠だと思うことを。

たまに便利なStructは、実はコンストラクタにキーワード引数を使えないことです。 間違ってキーワード引数を使うと、それらは1個のハッシュを渡したかのように扱われます。

Dog = Struct.new(:name, :age) # => Struct::Dog

# キーワード引数は使えない
Dog.new(name: 'ポチ', age: 1) # => #<struct Struct::Dog name={:name=>"ポチ", :age=>1}, age=nil>

# ハッシュを渡したかのように扱われる。
Dog.new({name: 'ポチ', age: 1}) # => #<struct Struct::Dog name={:name=>"ポチ", :age=>1}, age=nil>

# なのでStructでは通常の呼び出しをするしかない
Dog.new('ポチ', 1) # => #<struct Struct::Dog name="ポチ", age=1>

正直な所、これは仕様のバグだと思います。後方互換性が失われるので直しにくいと思いますが。

*1:文字列とは別にシンボルがある理由として、シンボルは内部的に整数として扱われるため文字列よりも速度的に有利なことが挙げられることがあります。「性能を目指して実装してない」くせに何言ってやがる、と思わなくもありません。とにかくRubyにはシンボルがあります。

*2:文字列とは別にシンボルがある理由として、シンボルは内部的に整数として扱われるため文字列よりも速度的に有利なことが挙げられることがあります。「性能を目指して実装してない」くせに何言ってやがる、と思わなくもありません。とにかくRubyにはシンボルがあります。

*3:文字列とは別にシンボルがある理由として、シンボルは内部的に整数として扱われるため文字列よりも速度的に有利なことが挙げられることがあります。「性能を目指して実装してない」くせに何言ってやがる、と思わなくもありません。とにかくRubyにはシンボルがあります。

rbenv環境で特定バージョンのコマンドを実行する(特にrubocop)

RBENV_VERSION環境変数を指定すればいい。

#!/bin/bash
RBENV_VERSION=2.2.3 rbenv exec rubocop "$@"

背景

イマドキ、プロジェクトで使うRubyのバージョンを.ruby-versionGemfileで指定していない人はいないと思いますが、その時困るのはgem install hogeして使うRuby製のツールツールは新しいRuby 2.2.xにインストールしたが、プロジェクトでは2.0.xを使っている、といった場合、rbenv は冷徹にエラーを出力します。

rbenv: rubocop: command not found

The `rubocop' command exists in these Ruby versions:
  2.2.2
  2.2.3

そういった場合、最初の例のような内容のラッパースクリプトを書けばよござんす。

(どうでもいいが「書けばよい」を丁寧語でどう言えばいいのだろう)

RubyでHashの多重代入

params = {a: "hello", b: "world"}

def foo(a:, b:)
  puts a + b
end

foo(params) # => helloworld

って出来るんだから、

a:, b: = params
puts a + b # => helloworld

って出来ないのかしらん?Ruby 2.2ではできないみたい

たしかClojureにこんな機能があったはず。Rubyでも議論されてるのかな?Ruby 3.0あたりで入ったらうれしいな。

Ruby初心者と忘れっぽい僕のための10の必須リンク先

部分集合が含まれているのは仕様です。

1. リファレンスマニュアル

寝るときに枕の下に敷くこと。 なお、リファレンスマニュアルは各バージョンごとにあるので、最新版(今は2.2.0)を見ましょう。

オブジェクト指向スクリプト言語 Ruby リファレンスマニュアル (Ruby 2.2.0)

2. リファレンスマニュアル - 組み込みライブラリと標準ライブラリ

ostructに(;´Д`)ハァハァしましょう。

3. リファレンスマニュアル - 基本コレクション

Enumerable#each_cons かわいいよ、 Enumerable#each_sliceも。

4. リファレンスマニュアル - %記法

しれっと、%i なんかが追加されてたりするからタマラナイ。

5. ワンライナー向けオプションまとめ

{sed,awk,grep,perl,...}が許されるのは(ry maeharin.hatenablog.com

6. Railsが拡張したObjectのAPI

ライスとカレーライスの区別は重要です。 http://api.rubyonrails.org/classes/Object.htmlapi.rubyonrails.org

7. Rake

コンパイルとかバックアップはRakefileにしとくと便利。

なお、一番大事なことはページの最後に書いてある。

8. 必須開発ツール

迷ったらgem install pry rubocop

迷う前にgem install pry rubocop

github.com

github.com

9. 「関数型Ruby」という病

処女地じゃない。雑草が再び生い茂ったというだけだ。 yuroyoro.hatenablog.com

&:symbolは好き。

10. Rubyから外部プログラムを起動

このブログの記事。大したことない記事だと思うのですが、なぜかアクセスが多いので、書いておきます。 doloopwhile.hatenablog.com

Goライブラリもウルトラ簡単に作れる!

RubyGemはめっちゃ簡単に作れる! - 酒と泪とRubyとRailsとRubyライブラリの公開方法が紹介されていました。

Goライブラリも知らないとハードル高そうに見えますが、実はかなり簡単につくれます。 これから積極的にGoライブラリを作ってOSSの世界に貢献していきたいので簡単な作り方をまとめました。

1. ファイルを用意する

今回はテスト的にHello World!と出力するようにします。hello.goを以下のように作成します。

package hello
func Greet() { println("Hello World!")}

2. 公開する

GitHubなどのpublicなリポジトリを作成。

# gitのリモートリポジトリを設定
git remote add origin git@github.com:doloopwhile/hello.git
# add & commit & push
git add . -A && git commit -m 'first commit' && git push

ライブラリはgo get github.com/doloopwhile/helloでダウンロードできるようになります。

今回のサンプルソースは以下のリポジトリにあります。良ければForkして試してみてください!

doloopwhile/hello - Github

gulpにもmakeにも不満なWebデベロッパーためのRake(コンパイル・パターンマッチ・ファイル監視・通知)

イントロ

21世紀になって登場したフロントエンド向けビルドツールであるgrunt, gulpなどは、最近批判を受けているようだ。

たしかに、コンパイラ毎にプラグインをインストールしたり、毎年のように新しいビルドツールを覚えるのは無駄だ。古き良きmakeで十分という意見も理解できる。

でも、僕はmakeを使う気にもなれない。タブでインデントするのはまだ我慢できる。 しかし、$(JC) $(JCFLAGS) $< -o $@ といった記号を多用したソースは読みやすいとは言えない(可読性は大事だ!)。 また、機能がシンプルすぎて、標準的な使用方法から外れた時にはシェルスクリプトを苦労して書くことになる。

そこで、僕が提案したいのが、Ruby/DSLで記述するビルドツールRakeだ。

Rakeの導入

Rakeはmakeによく似た機能のビルドツールだ。

RakeのビルドスクリプトRakefileは、Rubyソースコードそのものだ。 Rubyオブジェクト指向言語として知られているが、 Rakeを使う上で自分でクラスを定義する必要は普通は無いので安心して欲しい。 むしろ、必要に応じて文字列処理機能やコレクションといったRubyのリッチな機能が使えるので、 makeより可読性が高いコードを簡単に書ける。

また、Ruby on Railsはもっとも普及しているWEBフレームワークの一つだ。 RubyはWEBデベロッパーにとって全く未知ということも無いと思う。 もしあなたがRubyに不案内でも、同僚のだれかはRubyを知っているはずだ。

RakeはRubyの処理系に一緒についてくる。 Unix系OSではRuby/Rakeをインストールするのは本当に簡単だ。

$ sudo apt install ruby2.0 # Ubuntuの場合

$ brew install ruby # OS Xの場合

一方、あなたがWindowsを使っているなら、RubyInstaller for Windowsをダウンロードして実行すればいい。

余談:Rubyは難解か?

Rakeと同じくRubyDSLを書くツールの1つにChefがある。Chefは特にRubyに不慣れなエンジニアからは難しいとの悪評が高かった。でも、Chefの「難しさ」はRuby/DSLのせいだけでなく、そもそも「冪等性」という新しい概念のせいでもある。

ところで、おなじRubyツールでもserverspecには「Rubyだから難しい」という評判はあまり聞かない。 これはserverspecがわかりやすいモデルに基づくツールだからだろう。

そして、Rakeのモデルはmakeと同じシンプルでわかりやすいものなので、 Rubyだから難しいのではないか」という心配をする必要はない。

もちろん、広い世間には必要以上に難しいコードや汚いコードを書いてしまう人がいるわけだけど、 それはRakeが難しいかどうかとは別の問題だ。

ファイルのコンパイル(TypeScript, SCSSなどのコンパイル、JSやCSSの結合など)

以下は、複数のJSファイルを結合して、dist.jsを生成するコードだ。

# Rakefile
# パターンにマッチするファイルのリストを生成
Src = FileList['src/*.js']

# dist.jsの生成にはSrcに含まれるファイルが必要と定義
# do〜endの中で具体的な生成方法を定義
file 'dist.js' => Src do |t|
  # t.sourcesがソースファイルのリストなので、それをスペースで連結したコマンドラインを生成
  sh "browserify -o dist.js #{t.sources.join(' ')}"
end

# デフォルトのタスクはdist.jsの生成と定義
task :default => 'dist.js'

どうだい?Makefileよりずっと親しみやすいだろう?

コマンドライン文字列の中で式展開#{...} を使っているが、 これはRubyが元々提供していた機能だ。 一方、FileListはRakeが独自に提供しているもので、 Ruby標準の配列にファイルパス操作機能が追加されている(class Rake::FileList)。

ここで、rakeコマンドでdist.jsを生成するタスクを実行した後、 もう一度rakeを実行すると今度はタスクは実行されない。 Rakeはmakeと同様、生成物とソースのタイムスタンプを比較し、生成物の方が新しければ、 わざわざ生成しなおしたりはしないんだ。

パターンによるファイルのコンパイル

ruleを使うと、規則的な変換が定義できる。例えば、 * 'src/FOO.scss' を 'dist/FOO.scss' に * 'src/BAR.scss' を 'dist/BAR.scss' に

# Rakefile
SRC = FileList['src/*.scss']

# SRC を規則的に変換
DIST = SRC.pathmap('%{^src,dist}X.css')
# Rubyの標準機能を使ってやることもできる
# DST = SRC.map do |path|
#   path.sub! /^src\//, 'dist/'
#   path.sub! /\.scss/, 'css'
# end

# rule を使うと規則的な変換が定義できる
rule /dist\/.*\.css/ => '%{^dist,src}X.scss' do |t|
  sh "scss #{t.source} #{t}"
end

task :default => DIST

ここでは、Rakeが提供する機能を使って、パスの変換を定義している。 * FileList.pathmapメソッド(書式文字列を渡している) * rule=>の左側の正規表現 * rule=>の右側の書式文字列

しかし、ここであえて注意したいのは、.pathmapメソッドなどは使わなくてもいいということ。 たとえば、コメントに書いたようにRubyの標準機能で用足すこともできるし、 はたまた.pathmapで再現できない変な変換を書くこともできる。

これはmakeでは不可能だったことだ。

不要ファイルの削除

C言語などの開発では、最終成果物(実行ファイル)の他に、オブジェクトファイル等の中間ファイルができる。 こういった中間ファイルを一括に削除するタスクがあると良い。

そのためにRakeで必要なのは、rake/cleanをインポートし、CLEANCLOBBERという特別なリストにファイルを追加するだけだ。

# Rakefile
require 'rake/clean'

Src = FileList["*.c"]
Obj = Src.ext('o')

CLEAN.include(Obj)
CLOBBER.include('hello')

file 'hello' => Obj do |t|
  sh "gcc -o #{t.name} #{t.sources.join(' ')}"
end

rule '.o' => '.c' do |t|
  sh "gcc -c #{t.source}"
end

task :default => 'hello'

# rake clean で *.oが削除される
# rake clobber で hello も削除される

コンパイル以外のタスク(rsyncによるファイルアップロード等)

コマンド実行の形なら、どんな仕事をさせることもできる。

例えば、ファイルアップロードならrsyncを実行させればいい。

# Rakefile
task :rsync do |t|
  sh "rsync -rax dist/ appsvr:/var/www/html/"
end

task :default => :rsync

シンプルだ!

ファイル変更の監視・通知

多くの人は、gruntやgulpを使う理由として、ファイルの変更を監視して自動で再コンパイルする機能を挙げるだろう。 残念ながら、Rakeにはファイル監視や通知の機能は無い。

だがファイル監視にfswatch、デスクトップ通知にnotify-sendといった外部コマンドを使えば、 実現するのは簡単だ。

# Rakefile
task :rsync do |t|
  sh "rsync -rax dist appsvr:/var/www/html/"
end

task :watch do |t|
  sh %w{fswatch -r w/ | xargs -I{} sh -c "rake && notify-send 'rsync success' || notify-send 'rsync failure'"}
end

task :default => :rsync

# 以下のコマンドで監視開始
# $ rake watch

ここでは簡単さを優先してrake再帰的に呼びだす方法を書いた(安易な方法とも言う)。

もちろん、移植性その他を考慮すれば、別のコマンドやライブラリを使う選択もあるだろう。

まとめ

Gruntなどの21世紀のビルドツールにも、1970年代に登場したmakeにも、それぞれ不満な点があった。

一方、2000年代に開発されたRakeは、 makeのようにUnixの威力を活用でき、Rubyによって自在に拡張できる。 まさに、Gruntとmakeの長所を兼ね備えたツール

リンク

基本的なRakeの使い方

FileList などの機能を使い込みたいと思ったら

Rakeのバグを見つけたり、機能追加を提案したくなったら