Rubyが辛くなってきた俺はHaskellに浮気する

はじめに

社内で、Web API呼び出しを束ねるWeb API作成ブームだから、Haskellでブームに乗ってみようかなと。Ruby好きなんだけれど、人数が増えてくるとコードのフォーマットがバラバラになるのが辛くて、型チェックが羨ましくなったのが選択理由。

関数プログラミング実践入門

確実にこの本に感化されてるw こちらは、いざHaskell使って何か作ろうとしたときに、つまずくであろう箇所を丁寧に解説してくれている。Haskellをサンプルに、オブジェクト試行の言語と比較しながら解説されているので、自分にはよく刺さった。

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

すごいHaskellたのしく学ぼう!

次に購入した本、Haskellという言語自体を学ぶのに適している。 堅い言語とは逆に、軽快な口調で解説しているので、とっつきやすい。ファンクターの説明辺りで理解が追いつかなくなったので、精進せねば。

環境構築

Haskell Platform

何はともあれ、https://www.haskell.org/platform/

for Vimmer

fast-tag

ctagsはHaskell未対応なので、fast-tagsにtagsファイル作成してもらう

ghc-mod

依存解消でエラーになったので、下記ページを参考に

Build failed · Issue #425 · kazu-yamamoto/ghc-mod · GitHub

$ ghc-pkg unregister --force monad-control
$ cabal install --constraint "monad-control < 1" ghc-mod

Framework

https://www.haskell.org/haskellwiki/Web/Frameworks https://www.techempower.com/benchmarks/

Yesod

Yesodが人気なようなので、Yesodでベースに作成しようと思う。

Rubyの子クラスで定数を再定義したのに親の定数が参照されちゃう

ちゃう

class Parent
  CONST = 'parent'

  def initialize
    p CONST
  end
end

class Child1 < Parent
  CONST = 'child'
end

Child1.new # "parent"

"child"を期待してたら、"parent"が返ってきた!?

定数の場合、今のスコープになかったら、外のスコープから探索するので initializeメソッドの外に出たら、Parent::CONSTがいるので、その値を返しているみたい。

じゃあ、子クラス毎にinitializeメソッドを定義すれば

class Parent
  CONST = 'parent'
end

class Child1 < Parent
  CONST = 'child'

  def initialize
    p CONST
  end
end

Child1.new # "child"

やりました、期待通り"child"が返ってきました!

ないな

要は 子クラス::CONST を参照してくれれば良いのでしょ

class Parent
  CONST = 'parent'

  def initialize
    p self.class::CONST
  end
end

class Child1 < Parent
  CONST = 'child'
end

Child1.new # "child"

いけそう

class Parent
  CONST = 'parent'

  def initialize
    p self.class::CONST
  end
end

class Child2 < Parent; end


Child2.new # "parent"

さらに、継承しても

class Parent
  CONST = 'parent'

  def initialize
    p self.class::CONST
  end
end

class Child2 < Parent; end

class Grandchild < Child2
  CONST = 'grandchild'
end

Grandchild.new # "grandchild"

期待通り!

git用のpre-commit gemが便利すぎる

まずは

$ gem install pre-commit
# In your git repo
$ pre-commit install

これだけで、うっかり残念なコミットをする事故が防げます。

Git フック使ってますか?

http://git-scm.com/book/ja/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF

コミットする前にバリデーションかけて、通らなかったらコミット中止する。コミットメッセージにブランチ名を自動挿入する。など、ルーティンワークをフックに仕込んでおけば、誰も読まないガイドラインページを書く手間が省けますし、強制する事ができて幸せです。

じゃあ、何をフックに設定するかと言うと

コードレビューしていると、気になるけどいちいち指摘するのも面倒くさい事が良いんじゃないかと。

  • 行末の空白文字くらい消せよ!
  • タブインデント止めて!
  • binding.pry消し忘れてんぞ! などなど

そこで、pre-commit gemですよ

https://github.com/jish/pre-commit

デフォルトで便利なバリデーションをたくさん持っているので、いちいち自分で作成する必要が無いところがありがたい。 現在のチェック項目を確認したい場合は、pre-commit listで確認できます。

$ pre-commit list
Available providers: default(0) git(10) git_old(11) yaml(20) env(30)
Available checks   : before_all ci closure coffeelint common console_log csslint debugger gemfile_path go jshint jslint json local merge_conflict migration nb_space php pry rails rspec_focus rubocop ruby ruby_symbol_hashrockets scss_lint tabs whitespace yaml
Default   checks   : common rails
Enabled   checks   : common rails
Evaluated checks   : tabs nb_space whitespace merge_conflict debugger pry local jshint console_log migration
Default   warnings : 
Enabled   warnings : 
Evaluated warnings : 

common と rails がデフォルトで有効になっているcheck項目で、実際には"Evaluated checks"のが実行されます。

  • tabs: インデントにタブが使われていないか
  • nb_space: ノーブレークスペースが使われていないか
  • whitespace: git diff --check --cachedを実行する(Git自体に便利コマンドがある!)
  • merge_conflict: コンフリクトが解消されていないファイルを上げようをしていないか
  • debugger: debuggerを消し忘れていないか
  • pry: binding.pryを消し忘れていないか
  • local: ユーザが定義したバリデーションが通るか
  • jshint: JSファイルがjshintを通るか
  • console_log: console.logを消し忘れていないか
  • migration: Railsのdb, schemaファイルの整合性は取れているか(片方だけコミットしようとしていないか)

これだけでも、十分有用です

check追加したい

git configに追加する方法と、YAMLファイルで管理する方法があります。YAML形式で書き出しておいて、このファイルもプロジェクトに含めてしまえば、他のチームメンバーはpre-commit installをするだけで済むので、YAMLがいいんじゃないかと。

checks, warningsの違いはチェックに引っかかった時に、コミットを許容するか否かです。 cheksだと、そのままコミットさせない。warningsだと、警告を出すだけにとどめる。

$ pre-commit <enable|disable> <git|yaml> <checks|warnings> check1 [check2...]
# rubocopのチェックを追加するが、警告を出すだけ
$ pre-commit enable yaml warnings rubocop

独自バリデーションも適応したい

config/pre-commit.rb というファイルを置いておけば、引数としてコミット対象のファイル名を渡してくれます。exitで0よりも大きな数字を返せば、バリデーションに通らなかったことを通知できます。

# config/pre-commit.rb
valid = ARGV.all? do |file|
  # validate
end

exit valid ? 0 : 1

Guidelines as a Code

人力チェックリストを作るくらいなら、機械にテストさせた方がよっぽど正確だよね。って話でした。

新人への洗礼

新人の子が離席時にPCロックを忘れている!

チャンスですね

ls

ど定番

alias ls=sl

echo

牛さんのAAに癒されます

alias echo=cowsay
 _______ 
< hello >
 ------- 
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||

xcowsayは、最近知りました。 より癒されます。

alias echo=xcowsay

cat

猫がマウスカーソルを追いかけてきます。 無駄な引数があると、エラーになるので引数は捨て去りますw

function cat() {
  oneko
}

EDITOR

nanoで十分です

export EDITOR=nano
alias vim=nano
alias gvim=nano
alias emacs=nano
alias xemacs=nano
alias sbul=nano
alias atom=nano

32bit Win7上でVagrant使って開発環境構築 box構築編

今日こそは

boxの構築をするぞ!ってことで
最終目標は、Ubuntu-13.04 desktop上に開発環境整える

2013/08/16 18:40 apt.shを修正

本日の流れ

  1. veeweeのtemplateから作成したいVMのtemplate選ぶ→definition作成
  2. definitionを微修正
  3. definitionを基にubuntuをインストール
  4. Vagrant用boxファイル作成
  5. vagrant up

VMのtemplate選ぶ

veewee落としてきたディレクトリへ移動する
ここは、適宜読み替えて

> cd work\veewee

template一覧表示

> bundle exec veewee vbox templates

ubuntu-13.04-desktop-i386が無いorz
しょうがないので、server版をインストールして、後でGUI対応する。

> bundle exec veewee vbox define 'ubuntu-13.04-desktop-i386' 'ubuntu-13.04-server-amd64'

これで、definitions\ubuntu-13.04-desktop-i386以下にtemplateがコピーされるわけだが
改行コードがWin仕様だと、Ubuntuが正しく解釈できないので
全てのファイルの改行コードを\nに変換しておく。

忘れると、invalid operationと言われ泣きを見るはめになる。。。

definitionを微修正

definitions\ubuntu-13.04-desktop-i386\definition.rbを編集
32bitな民は、amd64ではなくi386のイメージを指定する。

8,11c8,11
<   :os_type_id => 'Ubuntu_64',
<   :iso_file => "ubuntu-12.10-server-amd64.iso",
<   :iso_src => "http://releases.ubuntu.com/12.10/ubuntu-12.10-server-amd64.iso",
<   :iso_md5 => '4bd3270bde86d7e4e017e3847a4af485',
---
>   :os_type_id => 'Ubuntu',
>   :iso_file => "ubuntu-13.04-server-i386.iso",
>   :iso_src => "http://releases.ubuntu.com/raring/ubuntu-13.04-server-i386.iso",
>   :iso_md5 => '73d595b804149fca9547ed94db8ff44f',

apt.shに追記

1行目にsourceのURLをミラーサーバから取得するよう変更するsed追記
変えなくても取得は可能だが、転送スピードが1.5~2倍近く早くなるので、推奨。

> sed -i -e 's#us.archive.ubuntu.com#mirror.math.ucdavis.edu#g' /etc/apt/sources.list

末尾に、ubuntu-desktopをインストールする行を追記。
このままだと、CLIなのでGUI用のパッケージ入れる。

> apt-get -y install ubuntu-desktop

definitionを基にubuntuをインストール

> bundle exec veewee vbox build 'ubuntu-13.04-desktop-i386' --workdir=C:/Users/takeshi.takizawa/work/veewee
Downloading vbox guest additions iso v 4.2.16 - http://download.virtualbox.org/virtualbox/4.2.16/VBoxGuestAdditions_4.2.16.iso
Checking if isofile VBoxGuestAdditions_4.2.16.iso already exists.
Full path: C:/Users/takeshi.takizawa/work/veewee/iso/VBoxGuestAdditions_4.2.16.iso

The isofile VBoxGuestAdditions_4.2.16.iso already exists.
Building Box ubuntu-13.04-desktop-i386 with Definition ubuntu-13.04-desktop-i386:
- debug : false
- cwd : C:/Users/takeshi.takizawa/work/veewee
- force : false
- nogui : false
- auto : false
- checksum : false
- redirectconsole : false
- postinstall_include : []
- postinstall_exclude : []

We did not find an isofile here : C:/Users/takeshi.takizawa/work/veewee/iso/ubuntu-13.04-server-i386.iso.

The definition provided the following download information:
- Download url: http://releases.ubuntu.com/raring/ubuntu-13.04-server-i386.iso
- Md5 Checksum: 73d595b804149fca9547ed94db8ff44f


Download? (Yes/No)

忘れずに、Yesとタイプする。
時間がかかるので、まったりコーヒーでも飲みながら待つ。

The box ubuntu-13.04-desktop-i386 was built successfully!
You can now login to the box with:
ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -p 7222 -l vagrant 127.0.0.1

成功!

Win7 32bit Core i5-2520M 2.5GHz 4.00GB
で、3時間程度かかった。

Vagrant用boxファイル作成

> bundle exec veewee vbox export 'ubuntu-13.04-desktop-i386'

vagrantに登録

> vagrant box add ubuntu-13.04-desktop-i386 'C:/Users/takeshi.takizawa/work/veewee/ubuntu-13.04-desktop-i386.box'

vagrant up

ここからは、boxファイルに対して色々操作していく。
作業用ディレクトリに移動して

> cd working\directory

Vagrantfile作成

> vagrant init 'ubuntu-13.04-desktop-i386'

いざ、起動!

> vagrant up

sshで入れるかな?

> vagrant ssh
Welcome to Ubuntu 13.04 (GNU/Linux 3.8.0-19-generic i686)

 * Documentation:  https://help.ubuntu.com/
Last login: Thu Aug 15 07:15:17 2013 from 10.0.2.2
vagrant@ubuntu-13:~$

すばらっ

あとは、rails等のCookbookを
http://community.opscode.com/
から、ありがたく頂戴すれば良いはず。

32bit Win7上でVagrant使って開発環境構築 準備編

動機

備えあれば憂いなしでございます。

準備

  1. Install Ruby http://rubyinstaller.org/
  2. Install pik https://github.com/vertiginous/
  3. Install Development Kit http://rubyinstaller.org/downloads/
  4. Install veewee https://github.com/jedi4ever/veewee
  5. Install VirtualBox https://www.virtualbox.org
  6. Install Vagrant http://www.vagrantup.com
  7. Install Git http://git-scm.com/downloads

Install Ruby

Vagrantのpluginは、Rubyで書かれているので
gem installでサクッと入れられるように、Ruby入れます。

Install pik

2013/08/17 14:00
rubyのバージョンを1.9.2 -> 1.9.3に変更

Rubyのバージョン管理してくれるgem
Windows版のrmvもしくはrbenv

同じバージョンだけれど、gemだけ分けたいってのは無理なのかな?こういうの

$ rvm use 1.9.2@veewee --create

この件は、いったん保留して

サクッとgem install

> gem install pik

まだ、終わりじゃない。
PATH通っているところに、pikコマンドをインストールする必要がある。
Cドライブ直下に、binディレクトリ作ってパス通しておく。

> mkdir C:\bin

コントロールパネル -> システム -> システムの詳細設定 -> 詳細設定 -> 環境変数 -> システム環境変数
無ければPathを新規作成して、あればpathを編集
末尾に追記

;C:\bin

pikコマンドをインストールする

> pik_install C:\bin

ようやく完了。
ruby-1.9.3入れとく。

> pik install ruby 1.9.3
> pik use 1.9.3

もし、7zipが無いと怒られたら
エラーの通り、7zipを入れる。
親切なエラーですね、エラーってこうあるべき。

> pik package 7zip install

Install Development Kit

C/C++の拡張をbuildするのに必要。

最初のRubyと一緒に入れても良かったけれど
pikで取ってきたRubyにもインストールするので
ここで、一緒に入れる。

インストール手順
https://github.com/oneclick/rubyinstaller/wiki/Development-Kit

Cドライブ直下に、DevKitフォルダ作る
https://github.com/oneclick/rubyinstaller/downloads/
から、DevKit-4.5.2 self extracting archive ダウンロードする
展開先を、C:\DevKitにする

> cd C:\DevKit
> ruby dk.rb init

気をつけるのは、config.ymlにpikで入れたRubyのpathを追加することくらい
最後の行に、pikで入れたRubyのpathを追記。

# This configuration file contains the absolute path locations of all
# installed Rubies to be enhanced to work with the DevKit. This config
# file is generated by the 'ruby dk.rb init' step and may be modified
# before running the 'ruby dk.rb install' step. To include any installed
# Rubies that were not automagically discovered, simply add a line below
# the triple hyphens with the absolute path to the Ruby root directory.
#
# Example:
#
# ---
# - C:/ruby19trunk
# - C:/ruby192dev
#
--- 
- C:/Ruby192
- C:/Users/takeshi.takizawa/.pik/rubies/Ruby-193-p429

インストール
pikで新しくruby追加したら、そのバージョンにも忘れずにDevKit入れる。

> ruby dk.rb install

defaultのrubyを変更したい場合は、環境変数のPathのrubyの向き先を変える必要がある。
今回の場合、Pathには
C:/Ruby192
が書かれているので
C:/Users/takeshi.takizawa/.pik/rubies/Ruby-193-p429
に変更する。

いちいち面倒だな。。。

Install veewee

Vagrantのbox作成をサポートしてくれるgem

既存のboxで十分な場合は、不要。
既存のVagrant box一覧は、こちら
http://www.vagrantbox.es/

veeweeを使う場合、githubから頂戴してくる

> cd <path_to_workspace>
> git clone https://github.com/jedi4ever/veewee.git
> cd veewee

bundlerでinstall

> gem install bundler
> bundle install

veeweeコマンド叩いて、コマンド一覧が見れれば
インストール成功

> bundle exec veewee

Install VirtualBox

仮想マシンを実行する仮想化ソフトウェア・パッケージ

Install Vagrant

仮想マシンの雛形(box)から、お手軽に仮想マシンのセットアップができるツール。
仮想マシンごと作成、破棄できるので、気に入らなければ気軽に1から作り直せる。

Install Git

バージョン管理さまさま

RubyでDSL作ってみる

肝となる処理

instance_eval が便利。

ブロック渡し以外にも、文字列を渡してインスタンス内のコンテキストで実行可能。

てことで

  1. DSL用のメソッドAを用意
  2. DSLで書かれたファイルを読み込む
  3. メソッドAと同じインスタンスで、ファイルの内容をinstance_eval
  4. -_-b

Example

DSL用のメソッドを持ったクラス

class DSL
  attr_reader :names

  def self.execute
    contents = File.open('DSLFile', 'rb'){ |f| f.read }
    dsl = new
    dsl.instance_eval(contents)

    p dsl.names
  end

  def initialize
    @names = []
  end

  def hello(name)
    @names << name
  end
end

DSLファイル

hello 'world'
hello 'again'
hello 'kitty'

実行してみる

DSL.execute

["world", "again", "kitty"]