ISUCON7の予選で敗退した
Blogにまとめるまでが、ISUCON
と、言うことで久しぶりにこっちに書く。
前から興味はあったけれど、参加者のブログを見る程度だった。
そんな時に、会社の同期に誘ってもらったので、ホイホイ受けてみた。
事前準備
過去問がVagrantで公開されているので、本番と同じ10:00-18:00でISUCON6の問題を解いてみる、ISUCON5の問題をざっくり読んで改善点を挙げる、というゆるふわな対策を行った。GitHub Privateにリポジトリを用意したり、Google DocにTODOリストを作成したりと、ここまでは良い感じだった。
自分の担当はDeploymentだったので、AnsibleのPlaybookをちょこちょこ作っていた。最近は専らDockerfileを書いているので、すっかり使い方を忘れていたけれど、Best Practices — Ansible Documentation 見ながらなんとなく書けるので、Ansible楽で良い。
Host Machine -> Vagrantで立ち上げたisucon5用のVM にansible-playbookするためのinventory fileはこんな感じ。
当日
開始直後
いきなりインスタンスが3台あってちょっと焦る。
とりあえず、Ansible script流す。InventoryにIP Address追加するだけだから、流す分には特に困らなかった。なお、各自のssh keyは ssh-copy-id で送れというスパルタ仕様。
そして、ベンチ走らせてみる、確かデフォルトがPythonで1台にだけ負荷をかけた状態。
Time 2017-10-22 13:27:06 +0900 JST Message ok Score 2282 Best 2282 LoadLevel 1
Goに切り替える。やっぱり1台だけ。
Time 2017-10-22 13:32:00 +0900 JST Message ok Score 4521 Best 4521 LoadLevel 1
2台だと?
Time 2017-10-22 13:35:38 +0900 JST Message ok Score 5030 Best 5030 LoadLevel 1
あんまり増えないね。
ということで、コードリーディングタイム。
14過ぎ辺り
改善できそうな所を出し合う。
alp用のログフォーマットをNginxに適応して、実際のデータを確認。これは、最初にやっても良かったかも。あと、access_log /var/log/nginx/access.log ltsv;
パスの後ろにltsvをつけ忘れるという凡ミスを犯した、ごめん。
DBのチューニングや、index効いてない、N+1問題などベタな所を修正。
iconの画像を、毎回DBから取得しているのが遅いよね。ということで、静的ファイルに書き出してNginxから返そうという方針に。
16時過ぎ辺り
新規、更新のiconはファイルに吐けるようになったので、既存の画像もファイルにしてしまおう。
Time 2017-10-22 16:43:26 +0900 JST Message 負荷走行前のバリデーションに失敗しました。2017-10-22 16:43:25.201090568 +0900 JST m=+1.524851228 [Fatal]画像データが正しくありません (GET /icons/65ea5a70b1fcf8f5f816b01b87f2aca2251315b0.png ) Score 0 Best 7225 LoadLevel 0
謎のバリデーションエラーにより、Nginxから返す作戦を諦めアプリから返すように戻す。
しかし依然として出続けるバリデーションエラー。。。
この時は、変更を1号機にしか適応していなかったので、2号機に1号機のバイナリを持って行って再現確認。結果、再現せず。
ベンチマークツールに変なファイルキャッシュされたのか?と散々悩む。
18時過ぎ
ダメだ、バリデーションエラーが出続ける。もう、諦めてラーメンか?という思いの中、DBのデータから静的ファイルを生成するロジックを書き換えたら、バリデーションに通った!危うく、ISUCONに出たこと自体を無かったことにするとこだった。
Time 2017-10-22 18:21:50 +0900 JST Message ok Score 23513 Best 23513 LoadLevel 1
その後、iconはinitializeの時にNginxのメモリに載せたいよね。という話になるが
で、どう設定すれば良いの?
という残念な状態になり、必死にググるおっさんたち。
20時過ぎ
せめて、アプリを3台にスケールしようということで、2号機とDB用だった3号機にもアプリを展開。
Nginxのログを切って、MySQLのSlow Queryログも切って、できる限りのことをした結果。
Time 2017-10-22 20:50:48 +0900 JST Message ok Score 42731 Best 44732 LoadLevel 1
4万ちょいでフィニッシュでした。
総評
Ansibleでスマートにデプロイしたかったけれど、makeが通らない!等焦ってしまい、書き捨てのShellで凌いだのが負けた気分だったし、そもそもデプロイするところまで事前に用意しておくべきだった。
ETC_BR=${1:-master} APP_BR=${2:-master} cd /etc.git sudo git fetch echo "[INFO] Checkout $ETC_BR" sudo git checkout $ETC_BR sudo git pull sudo cp -r /etc.git/nginx/* /etc/nginx echo "[INFO] Restart Nginx" sudo systemctl restart nginx cd /home/isucon/isubata/webapp/go git fetch echo "[INFO] Checkout $APP_BR" git checkout $APP_BR git pull make echo "[INFO] Restart Go APP" sudo systemctl restart isubata.golang.service
あと、過去何度もNginxが登場しているというのに、Nginxへの理解が低すぎたのは大変よろしくなかった。
とても悔しかったので、来年リベンジしたいな。
冬休みの読書感想文
WEB+DB PRESS vol.90

- 作者: 江口和宏,吉田太一郎,内田優一,青山公士,石本光司,まつもとゆきひろ,おにたま,田籠聡,竹内郁雄,南川毅文,伊藤直也,佐藤太一,?橋侑久,Magnolia.K,佐藤歩,泉水翔吾,西尾泰和,中島聡,はまちや2,竹原,宮崎亮輔,安藤祐介,WEB+DB PRESS編集部
- 出版社/メーカー: 技術評論社
- 発売日: 2015/12/23
- メディア: 大型本
- この商品を含むブログ (1件) を見る
Git実践活用
Git自体の使い方ではなく活用方法を教えてくれるので、特にルールが決まって無いのなら、これに従うと楽そう。 取りあえずググった解決方法試したら、git push -f でmasterブランチの履歴おかしくなりました。 という悲しい事件を未然に防ぐために、場当たり的ではなく体系的に学ぶのがオススメです。
git commit前に静的解析かけるなら、こちらの記事も合わせてどうぞ。
git用のpre-commit gemが便利すぎる - TakiTakeの日記
ドラゴンクエストX 開発ノウハウ大公開
ドラクエでもOracle, Cassandra使ってるのか、という勝手な親近感が湧く。 サラッと書いてあるけれど、KVSへ保存するバイナリサイズが当初の倍以上になったが、余裕を持って設計していたため基本構成はサービス開始当初のままというのはカッコいい。 通信遅延を違和感の無いようにラップする努力は見習いたいところ。
MMOに関しての技術書は、Mobageを支える技術もオススメです。

Mobageを支える技術 ~ソーシャルゲームの舞台裏~ (WEB+DB PRESS plus)
- 作者: DeNA
- 出版社/メーカー: 技術評論社
- 発売日: 2012/06/13
- メディア: 単行本(ソフトカバー)
- 購入: 31人 クリック: 737回
- この商品を含むブログを見る
Release It!

Release It! 本番用ソフトウェア製品の設計とデプロイのために
- 作者: Michael T. Nygard,でびあんぐる
- 出版社/メーカー: オーム社
- 発売日: 2009/02/21
- メディア: 単行本(ソフトカバー)
- 購入: 14人 クリック: 155回
- この商品を含むブログ (50件) を見る
現実は、入門書みたいに単純じゃないんだよ!と嘆いてる人にオススメの一冊。 実例を交えながら対策を説明してくれるので、ものすごく共感できる。
午前9時5分までに全サーバ上のアクティブなセッションは10,000件になっていた。
午前9時10分、サイト上のアクティブなセッションは50,000件を超えた。
午前9時30分、サイト上のアクティブなセッションは250,000件だった。そしてサイトはクラッシュした。
あるある(笑えない)
行儀の良いテストしかしていない。という指摘は、耳が痛い。
ElixirとRabbitMQでJenkinsっぽいサービスつくる その1
Elixirで何か作ってみたい
ということで、いろいろ考えた結果、俺の考えた最強のYet Another Jenkinsをつくってみようとおもいます。 Queueの管理はRabbitMQに任せる予定なので、いわゆるPub/Sub Applicationになる予定。 ブログなので、進捗等を雑にまとめていく。
コンセプト
UIはIFTTTくらいシンプルにする
シンプル イズ ベスト
サービスは実行スクリプトを持たない、Jobのコントロールにのみ専念する
Jenkinsの場合、Jobに直接スクリプトを登録できるのですが、これをやり過ぎると責任範囲が曖昧になってしまう上に、スクリプトをどこで定義しているのかわからなくなってしまうので禁止にする。 代わりに、実行ファイルもしくはPluginの機能を実行する。 最悪、サービスが止まっても、Jobをローカルで実行できることが望ましい。
JobはDocker container上で実行する
Slaveサーバ同士の環境差分や、Localだと動いた/動かない、Jenkinsだと動いた/動かない 問題が厄介なので、差分を無くすために積極的にDockerの恩恵にあずかる。
悩み中
JenkinsのBuild FlowやWork Flow Pluginに代わるJobの定義方法を模索中。 今のプロジェクトでは、これらをヘビーに使っていて、便利さは重々感じているのですが、Jenkinsに依存しちゃうのがいまいちなのと、Jenkins職人じゃないと管理できないのが辛い。
ひとまず、Bitbucketと連携を試みる
なぜGitHubじゃないのかというと、弊社がAtlassianのサービス使っているので、需要はこっちの方が高いからです。もちろん、将来的にはGitHubも対応予定。
Webhookのリクエスト確認
Git pushに対するWebhook
フローとしては
- Git push
- Bitbucket -- webhook --> Publisherアプリ
- Publisherアプリがwebhookの情報を加工してRabbitMQにメッセージとして送る
- Exchangeが適切なQueueにメッセージを追加
- ConsumerアプリがQueueからメッセージを取り出し処理する
Netcatで生データ見るのが、一番手っ取り早い。
$ nc -l 3000 POST / HTTP/1.1 Host: mysidia.takitake.tech:3000 Content-Length: 3404 Accept-Encoding: gzip, deflate Accept: */* X-Event-Key: repo:push User-Agent: Bitbucket-Webhooks/2.0 X-Hook-UUID: 50ac74f5-e8d8-4b6a-82ce-02f433d4816c Content-Type: application/json Via: 1.1 util-102.ash1.bb-inf.net (squid/3.3.8) Cache-Control: max-age=259200 Connection: keep-alive {"repository": {"name": "mysidia", "scm": "git", "links": {"avatar": {"href": "https://bitbucket.org/takitake/mysidia/avatar/16/"}, "html": {"href": "https://bitbucket.org/takitake/mysidia"}, "self": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia"}}, "full_name": "takitake/mysidia", "uuid": "{65abe83f-810a-469f-bfe0-e2d4c409618b}", "type": "repository", "is_private": true, "owner": {"links": {"avatar": {"href": "https://bitbucket.org/account/takitake/avatar/32/"}, "html": {"href": "https://bitbucket.org/takitake/"}, "self": {"href": "https://api.bitbucket.org/2.0/users/takitake"}}, "uuid": "{22c717bd-843a-47f8-8a52-38eef8145ddb}", "display_name": "Takeshi Takizawa", "type": "user", "username": "takitake"}}, "push": {"changes": [{"old": null, "closed": false, "created": true, "truncated": false, "commits": [{"author": {"raw": "TakiTake <TakiTake.create@gmail.com>", "user": {"links": {"avatar": {"href": "https://bitbucket.org/account/takizawatake01/avatar/32/"}, "html": {"href": "https://bitbucket.org/takizawatake01/"}, "self": {"href": "https://api.bitbucket.org/2.0/users/takizawatake01"}}, "uuid": "{608bf02a-5eb8-4822-b16e-699e6fd8fbe3}", "display_name": "Takeshi Takizawa", "type": "user", "username": "takizawatake01"}}, "links": {"self": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia/commit/8fb0eefc6a340d57598ca553a07fade1a10f2c30"}, "html": {"href": "https://bitbucket.org/takitake/mysidia/commits/8fb0eefc6a340d57598ca553a07fade1a10f2c30"}}, "type": "commit", "message": "Initial commit with contributors\n", "hash": "8fb0eefc6a340d57598ca553a07fade1a10f2c30"}], "forced": false, "links": {"commits": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia/commits?include=8fb0eefc6a340d57598ca553a07fade1a10f2c30"}, "html": {"href": "https://bitbucket.org/takitake/mysidia/branch/master"}}, "new": {"name": "master", "links": {"commits": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia/commits/master"}, "html": {"href": "https://bitbucket.org/takitake/mysidia/branch/master"}, "self": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia/refs/branches/master"}}, "type": "branch", "target": {"links": {"self": {"href": "https://api.bitbucket.org/2.0/repositories/takitake/mysidia/commit/8fb0eefc6a340d57598ca553a07fade1a10f2c30"}, "html": {"href": "https://bitbucket.org/takitake/mysidia/commits/8fb0eefc6a340d57598ca553a07fade1a10f2c30"}}, "type": "commit", "message": "Initial commit with contributors\n", "hash": "8fb0eefc6a340d57598ca553a07fade1a10f2c30", "author": {"raw": "TakiTake <TakiTake.create@gmail.com>", "user": {"links": {"avatar": {"href": "https://bitbucket.org/account/takizawatake01/avatar/32/"}, "html": {"href": "https://bitbucket.org/takizawatake01/"}, "self": {"href": "https://api.bitbucket.org/2.0/users/takizawatake01"}}, "uuid": "{608bf02a-5eb8-4822-b16e-699e6fd8fbe3}", "display_name": "Takeshi Takizawa", "type": "user", "username": "takizawatake01"}}, "date": "2015-09-25T12:44:44+00:00", "parents": []}}}]}, "actor": {"links": {"avatar": {"href": "https://bitbucket.org/account/takitake/avatar/32/"}, "html": {"href": "https://bitbucket.org/takitake/"}, "self": {"href": "https://api.bitbucket.org/2.0/users/takitake"}}, "uuid": "{22c717bd-843a-47f8-8a52-38eef8145ddb}", "display_name": "Takeshi Takizawa", "type": "user", "username": "takitake"}}
bitbucket.repo.pushをRabbitMQのrouting keyにして、messageは上記のJSONをmessagepackでエンコードしようかな。
Google様Pub/Subサービスしてた
対抗するわけではないので、積極的に参考にしていく方向で
What is Google Cloud Pub/Sub? - Cloud Pub/Sub — Google Cloud Platform
pre-commitへPull Request送ったときの調査方法
pre-commitって何?という人は、こちらを先に見てください
もしや、バグ?
ignoreできないファイルがある
普段から、pre-commitを愛用していますが、中にはチェックして欲しくないファイルもあります。そんなときは、.pre_commit.ignoreにファイルのパターンを記載するのですが、どうしてもignoreできないケースがありました。
# .pre_commit.ignore foo/*/*.yml
fooディレクトリ以下にある、YAMLファイルは対象外
# foo/bar/baz.yml key: val
実際、余計な改行が入っている
$ pre-commit run pre-commit: Stopping commit because of errors. foo/bar/baz.yml:2: new blank line at EOF.
無視されず、怒られた orz
ちょっとコードを追って見る
rbenvでRubyを管理しているので、.rbenvディレクトリ以下にGemがインストールされています。RubyのVersionは適宜読み替えて下さい。
$ cd ~/.rbenv/versions/2.0.0-p598/lib/ruby/gems/2.0.0/gems/pre-commit-0.22.1/
bin以下に実行ファイルがある
# bin/pre-commit #!/usr/bin/env ruby require 'pre-commit/cli' if !File.exists?(".git") abort "No .git directory found." end PreCommit::Cli.new(*ARGV).execute or exit 1
ARGVで引数を渡している
module PreCommit class Cli def initialize(*args) @args = args end def execute() action_name = @args.shift or 'help' action = "execute_#{action_name}".to_sym if respond_to?(action) then send(action, *@args) else execute_help(action_name, *@args) end end def execute_run(*args) require 'pre-commit/runner' PreCommit::Runner.new.run(*args).tap { |ok| puts "No failed checks." if ok } end end end
action_name 今回は、runなので execute_run が呼ばれる
# lib/pre-commit/runner.rb module PreCommit class Runner include PreCommit::Utils::StagedFiles def run(*args) set_staged_files(*args) run_single(:warnings) run_single(:checks ) or return false true end end end
runメソッドでは、まずはcommit対象のファイルを探してそう。
set_staged_filesメソッドは、このクラスに無いので
include PreCommit::Utils::StagedFiles
で、読み込んでるのかな?
そろそろ、追うの大変になってきたので、binding.pryを仕込む
# lib/pre-commit/runner.rb require 'beybug' require 'pry-beybug' module PreCommit class Runner include PreCommit::Utils::StagedFiles def run(*args) binding.pry set_staged_files(*args) run_single(:warnings) run_single(:checks ) or return false true end end end
beybugとpry-beybugをrequireして、止めたい箇所に binding.pry を挿入
$ pre-commit run Frame number: 0/5 From: /Users/takeshi.takizawa/.rbenv/versions/2.0.0-p598/lib/ruby/gems/2.0.0/gems/pre-commit-0.22.1/lib/pre-commit/runner.rb @ line 28 PreCommit::Runner#run: 26: def run(*args) 27: binding.pry => 28: set_staged_files(*args) 29: run_single(:warnings) 30: run_single(:checks ) or return false 31: true 32: end
step実行で先に進めていく
From: /Users/takeshi.takizawa/.rbenv/versions/2.0.0-p598/lib/ruby/gems/2.0.0/gems/pre-commit-0.22.1/lib/pre-commit/utils/staged_files.rb @ line 48 PreCommit::Utils::StagedFiles#filter_files: 40: def filter_files(files) 41: files.reject do |file| 42: !File.exists?(file) || 43: File.directory?(file) || 44: ( 45: size = File.size(file) 46: size > 1_000_000 || (size > 20 && binary?(file)) 47: ) || => 48: repo_ignores.any? { |ignore| File.fnmatch?(ignore, file) } 49: end 50: end [4] pry(#<PreCommit::Runner>)> repo_ignores.any? { |ignore| File.fnmatch?(ignore, file) } => true [5] pry(#<PreCommit::Runner>)> file => "foo/bar/baz.yml"
ちゃんと、ignore対象になっているみたい。ignoreファイルの書き方はあってそう。
ソースコードに戻ると、listをひとつひとつ実行して、エラーが一つでもあれば show_output でエラー表示しつつ false 返している。
def run_single(name) show_output(name, execute(list_to_run(name))) end def show_output(name, list) if list.any? @stderr.puts send(name, list) return false end true end def execute(list) list.map do |cmd| result = nil seconds = Benchmark.realtime do result = cmd.new(pluginator, config, list).call(staged_files.dup) end puts "#{cmd} #{seconds*1000}ms" if debug result end.compact end
どのチェックに引っかかったか分かるように、デバッグメッセージ埋め込む。
def execute(list) list.map do |cmd| result = nil seconds = Benchmark.realtime do result = cmd.new(pluginator, config, list).call(staged_files.dup) end puts "#{cmd} #{seconds*1000}ms" if debug unless result.nil puts "=" * 30 puts "#{cmd} #{result}" puts "=" * 30 end result end.compact end
$ pre-commit run ============================== PreCommit::Checks::Whitespace foo/bar/baz.yml:2: new blank line at EOF. ============================== pre-commit: Stopping commit because of errors. foo/bar/baz.yml:2: new blank line at EOF. pre-commit: You can bypass this check using `git commit -n`
PreCommit::Checks::WhitespaceでCheckが通らなかった事がわかった。
"class Whitespace"でgrepして定義位置探す。
# lib/plugins/pre_commit/checks/whitespace.rb module PreCommit module Checks class Whitespace < Plugin def call(staged_files) binding.pry errors = `git diff-index --check --cached HEAD -- #{files_string(staged_files)} 2>&1` return if $?.success? # Initial commit: diff against the empty tree object if errors =~ /fatal: bad revision 'HEAD'/ errors = `git diff-index --check --cached 4b825dc642cb6eb9a060e54bf8d69288fbee4904 -- 2>&1` return if $?.success? end errors end end end end
call直後で止めてみる
$ pre-commit run Frame number: 0/11 From: /Users/takeshi.takizawa/.rbenv/versions/2.0.0-p598/lib/ruby/gems/2.0.0/gems/pre-commit-0.22.1/lib/plugins/pre_commit/checks/whitespace.rb @ line 27 PreCommit::Checks::Whitespace#call: 25: def call(staged_files) 26: binding.pry => 27: errors = `git diff-index --check --cached HEAD -- #{files_string(staged_files)} 2>&1` 28: return if $?.success? 29: 30: # Initial commit: diff against the empty tree object 31: if errors =~ /fatal: bad revision 'HEAD'/ 32: errors = `git diff-index --check --cached 4b825dc642cb6eb9a060e54bf8d69288fbee4904 -- 2>&1` 33: return if $?.success? 34: end 35: 36: errors 37: end [1] pry(#<PreCommit::Checks::Whitespace>)> staged_files => [] [2] pry(#<PreCommit::Checks::Whitespace>)> "git diff-index --check --cached HEAD -- #{files_string(staged_files)} 2>&1" => "git diff-index --check --cached HEAD -- 2>&1"
あぁ、本来は対象のファイルを指定して git diff-index --check するのだけれど、staged_filesが空の場合はファイル指定無し、つまり、全ファイルがチェック対象になっちゃうのね。
空の時はチェックしなければ良いじゃない
後始末
$ gem pristine pre-commit Restoring gems to pristine condition... Restored pre-commit-0.22.1
pre-commitを初期状態に戻す。
Scottyで簡易サーバ作成
つくったもの
ScottyでWeb API呼び出しを束ねる(予定)のServerを作成しました。Yesodは、俺にはまだ早かった。
構成
Web Browser -> Web Server (Scotty) -> API Server (Sinatra)
SinatraでAPI Server作成
Rubyの方が慣れているので、API Server側はRubyでサクッとつくる。
$ mkdir api $ cd api $ bundle init $ vim Gemfile $ bundle install
Gemfile
source "https://rubygems.org" gem "sinatra"
index.rb
require 'sinatra' get '*' do 'Hello World!' end
サーバ立ち上げる
$ ruby index.rb
Scottyでクライアント作成
$ mkdir server $ cd server $ cabal sandbox init $ cabal init $ vim server.cabal $ cabal install
server.cabal
-- Initial server.cabal generated by cabal init. For further -- documentation, see http://haskell.org/cabal/users-guide/ name: server version: 0.1.0.0 -- synopsis: -- description: license: MIT license-file: LICENSE author: Takeshi Takizawa maintainer: takitake.create@gmail.com -- copyright: category: Web build-type: Simple -- extra-source-files: cabal-version: >=1.10 executable server main-is: Main.hs -- other-modules: -- other-extensions: build-depends: base >=4.7 && <4.8 ,http-conduit ,scotty -- hs-source-dirs: default-language: Haskell2010
Main.hs
{-# LANGUAGE OverloadedStrings #-} import Web.Scotty import Network.HTTP.Conduit (simpleHttp) main :: IO () main = scotty 3000 $ do get "/:word" $ do res <- simpleHttp "http://localhost:4567/" raw res
サーバ立ち上げる
# ここの手順が自信ないです # 今時は、どうやるんだろ? $ cabal install $ ./dist/dist-sandbox-4f285b0c/build/server/server
動作確認
ブラウザで、 http://localhost:3000/ にアクセス。Hello World!が表示されること。
所感
Ruby | Haskell |
---|---|
Rails | Yesod |
Sinatra | Scotty |
httparty | http-conduit |
かなと。
- cabal installは、毎回buildするもの?gem installに比べもの凄い時間かかる
- ByteString.Lazyをどう表示したら良いか分からなくて、コンパイラに散々怒られて辛い
Rubyが辛くなってきた俺はHaskellに浮気する
はじめに
社内で、Web API呼び出しを束ねるWeb API作成ブームだから、Haskellでブームに乗ってみようかなと。Ruby好きなんだけれど、人数が増えてくるとコードのフォーマットがバラバラになるのが辛くて、型チェックが羨ましくなったのが選択理由。
関数プログラミング実践入門

関数プログラミング実践入門 ──簡潔で、正しいコードを書くために (WEB+DB PRESS plus)
- 作者: 大川徳之
- 出版社/メーカー: 技術評論社
- 発売日: 2014/11/14
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (3件) を見る
確実にこの本に感化されてるw こちらは、いざHaskell使って何か作ろうとしたときに、つまずくであろう箇所を丁寧に解説してくれている。Haskellをサンプルに、オブジェクト試行の言語と比較しながら解説されているので、自分にはよく刺さった。
すごいHaskellたのしく学ぼう!

- 作者: Miran Lipovača,田中英行,村主崇行
- 出版社/メーカー: オーム社
- 発売日: 2012/05/23
- メディア: 単行本(ソフトカバー)
- 購入: 25人 クリック: 580回
- この商品を含むブログ (67件) を見る
次に購入した本、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"
期待通り!