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
となります。
ハッシュのキーにはシンボルがよく使われますが、例えばRailsのparams
(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
が呼ばれるのはメソッドが無い場合だけではないとことです。
呼ぼうとしたメソッドがprotected
やprivate
であった場合も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-version
やGemfile
で指定していない人はいないと思いますが、その時困るのは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初心者と忘れっぽい僕のための10の必須リンク先
部分集合が含まれているのは仕様です。
1. リファレンスマニュアル
寝るときに枕の下に敷くこと。 なお、リファレンスマニュアルは各バージョンごとにあるので、最新版(今は2.2.0)を見ましょう。
オブジェクト指向スクリプト言語 Ruby リファレンスマニュアル (Ruby 2.2.0)
2. リファレンスマニュアル - 組み込みライブラリと標準ライブラリ
ostructに(;´Д`)ハァハァしましょう。
3. リファレンスマニュアル - 基本コレクション
Enumerable#each_cons かわいいよ、 Enumerable#each_sliceも。
- module Enumerable (Ruby 2.2.0)
- class Hash (Ruby 2.2.0)
- class Array (Ruby 2.2.0)
- class String (Ruby 2.2.0)
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
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. 公開する
# 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して試してみてください!
gulpにもmakeにも不満なWebデベロッパーためのRake(コンパイル・パターンマッチ・ファイル監視・通知)
イントロ
21世紀になって登場したフロントエンド向けビルドツールであるgrunt, gulpなどは、最近批判を受けているようだ。
- 【翻訳】Web世代のデベロッパーのためのmake - MOL
- 最近のビルドツールって何なの? - 檜山正幸のキマイラ飼育記
- 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と同じくRubyでDSLを書くツールの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
をインポートし、CLEAN
とCLOBBER
という特別なリストにファイルを追加するだけだ。
# 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の使い方
- 日本語: http://www2s.biglobe.ne.jp/~idesaku/sss/tech/rake/
- 英語: http://docs.seattlerb.org/rake/doc/rakefile_rdoc.html
FileList
などの機能を使い込みたいと思ったら
Rakeのバグを見つけたり、機能追加を提案したくなったら