筑波大学に編入して半年経ったのでまとめてみる
呉高専エンジニア勉強会 Advent Calendar 2017の6日目の記事です。
呉高専OB1年目の@euglena1215です。
Advent Calendar 2記事目になります。全埋めの先は長そうです。
筑波大学 情報科学類へ編入して半年とちょっとが過ぎたので編入してからいままでやったことをまとめてみたいと思います。
編入体験記はこちら
筑波大学について
テストについて
テストの難易度は高専のときと大差ないと思ってます。
自分は電気系科目に本当に興味がなくて大変だったのですが、
大学に編入してからは情報系の科目ばかりなので頭に入りやすくていい感じです。
ざっくりと電気系よりも情報系の方が理論が人間寄りなので理解しやすい感がある
— てっぺー (@euglena1215) 2017年6月28日
テストがあった教科全部A+だったのでもしかしたら自分賢いのかもしれない(機械学習は除く
— てっぺー (@euglena1215) 2017年7月19日
高専時代の成績
1年次 17位
2~4年次 9~11位
5年次 21位http://euglena1215.hatenablog.jp/entry/2016/07/31/065327
※ 高専のときと同じくらい勉強すれば大丈夫ということを伝えたい
課題について
TAが課題の採点をするという制度が確立しているためか高専のときよりも課題は多いように感じます。
また、実験は選択したテーマによって重さは色々ですが基本的に高専のときよりも軽いと思います。
毎週手書きで20枚前後のレポートを作成しろとかは聞かないです。
総合大学として
高専は理系しかいない+高校1年から5年間ずっと同じクラスと良くも悪くも偏っていると思っていたので色んな人がいそうな総合大学に行くことにしました。
筑波大学は大学内ですれ違うユニークユーザー数(Daily Daigaku Surechigau Unique User)を計測すればかなり上位にランクインすると思います。
しかしそれを生かせているかといえばそうでもなく、情報系の友達(主に編入生)orサークルの友達で固まっている感があります。
ほんとアクティブな人すげえなぁって思います。
他の学類の授業を受けられる
筑波大学には他の学類の授業を取っても単位として認定される仕組みがあって、認定される単位数は学類によって定められています。
ですが
- 情報科学類はその単位数がとても少ない(単位変換で消滅してしまった)
- 3年次で卒業に必要な単位を取りきろうとすると時間割がほぼ埋まってしまい単位にならない科目を受講する余裕があまりない
などの理由で、他学類の授業はあまり受けてないです。。。
情報科学類について
志望するときに迷った情報科学類(coins)とメディア創成学類(mast)を比較して考えてみます。
coinsはTHE Computer Science という感じで根幹となっている基礎理論を勉強できる科目が多いです。
なので
- ずっと使えるエンジニアとして教養を身に付けたい
- コンピュータサイエンスで研究をバリバリやっていきたい
人は向いているのかなという感じです。
mastはComputer Scienceもやっているものの、サービスやメディアの見せ方や作り方など使っている人や社会にどうすれば影響を与えやすいかといった内容の科目が多いと聞いています。
なので
- サービスを作るのが好き
- 自分の作ったものがどんな影響を与えるのか考えるのが楽しい
- ツールをうまく使って面白いものを作りたい
- 落合陽一大好き
人は向いているのかなという感じです。
学類の雰囲気としてはcoinsは黙々と何かしてる印象でmastはワイワイしている印象です。(mast楽しそう)
mastの方がサービスを作るのが好きな人が集まっているからか手を動かしている人が多いんじゃないかと思います。
個人的には科目内容はcoinsで正解だったと思いつつ学類の雰囲気はmastの方が楽しそうだなぁと思っています。
つくばの立地
つくばエクスプレスの終点の秋葉原までは1時間かからないくらいなので呉-広島くらいの感覚です。
行こうと思えば行けるって感じです。(交通費は呉-広島の倍くらいだけど…)
ただ、秋葉原からIT企業が多い渋谷, 目黒, 恵比寿に行くと30分くらいかかるのでちょっと遠いなと感じることは結構あります。
サークル
なにか非開発系のサークルに入りたいなぁと思っていて、新歓を回っていたいたときに楽しそうだったねっしー(自然教育研究会)に入会しました。
ねっしーとは…
「筑波大学ねっしー・自然教育研究会」は、つくば近辺に住んでいる小中学生を対象に、キャンプや自然観察会などのイベントを行っている筑波大学公認サークルです。
1980年発足当時から、多くの子どもたちに参加いただき、大変好評を得ながら現在にいたっております。
現在も活発に活動しており、2、3ヶ月に1度くらいの割合でイベントを行い、子どもたちに多くのことを学び、そして楽しんでもらっています。
筑波大学ねっしー・自然教育研究会‐公式Webページ‐
最近バタバタしていて参加率があまり高くないのをなんとかしたい…
ねっしーのHPもなんとかしたいのですが、全然手が回らずという感じです。
大学外での活動
イベント参加
逆求人系面談イベント
IT系(他もあるのかも?)は自分のやってきたことをスライドにまとめ、IT企業のエンジニアや人事の人とスライドを交えながら談笑するイベントがあります。
下に書いてあるインターンもこの逆求人イベントからエントリーしました。
イベントの開催場所は東京か関西であることが多いですが、広島からだと交通費で3万円もらえたりするみたいなのでお金はあまり気にしなくていいみたいです。
売り手市場を感じますね…!!!
定番の逆求人系はジースタイラスの逆求人とサポーターズの二つだと思います。
自分はサポーターズをよく利用してました。
スライドとか作るのめんどくさいけどとりあえず様子を見てみたいって人は 問答無用で交通費1万円支給してもらえるギークフェスタおすすめです。
学校で{プログラミング好き && 焼肉食べたい}人をある程度集めると焼肉を食べさせてくれる人を召喚できるようになるのもアツいです。
Rubykaigi
Rubyのコミッターが世界中から集まって今後のRubyについてトークしてくれるカンファレンスが広島であったので参加してきました。
今回はRuby 3x3(Ruby version3で従来の3倍速くさせる)をどうやったら実現できるのかという話と
Rubyの苦手な範囲もカバーしたい(機械学習やIoTとか)話とかが多かった気がします。
セッション中にみんながどうして笑っているのかよく分からなかったり分からなすぎて段々と眠くなってきたり
言語の壁や技術力の壁をひしひしと感じました。
来年はスタッフ側として参加しようと思ってます。
ステッカー収集が捗ります今日の戦利品です #rubykaigi pic.twitter.com/diF2197tcE
— てっぺー (@euglena1215) 2017年9月18日
ISUCON
ISUCONとはIikanjini Speed Up CONtest の略でwebサービスが動いているサーバが与えられ インフラ、データベース、アプリケーションなど色々な箇所に手を加えながらレスポンス速度を向上させるコンテストです。
その予選、本選(学生枠)に参加してきました。
記事にしているのでここでの内容は割愛します。
euglena1215.hatenablog.jp euglena1215.hatenablog.jp
インターン
ドリコム
1週間講義+2週間実務で合計3週間のインターンでした。
会社ブログとして書かせてもらったのでこれを参考にしてもらえればと思います。
ドリコムのインターンシップはいいぞ! - Tech Inside Drecom
エイチーム
3日間コンテスト形式のインターンで3,4人1組のグループを作ってスコアを競い合うといった内容でした。
ゲームとは
webアプリのソースと修正したい箇所がまとめられた資料(Redmine)が渡され、改修するとポイントがもらえるといったwebアプリのCTFっぽいものや
社内ツールのソースと社員からのアンケートを元に必要な機能を考えて実装し、発表する
などがありました。
使ったことのあるGemを手札に殴り合うバトルと化していましたがイベントとして楽しかったです。
株式会社エイチーム 2019卒サマーインターンシップ【受付終了】
Wantedly
実務2週間のインターンでした。
feedの一覧ページに表示される投稿のアルゴリズムを追加
アクセスログをBig Queryで整形して次の施策を考える
などをしていました。
Wantedly Summer Internship 2017 - X YOU | 2017年度サマーインターンの募集が始まりました!
現在も週1で通わせてもらっています。
その他
今年編入した情報系3編生をメインに構成された yakiniku-lovers
今年明石高専からmastに編入してきた@at_sushi_atが元々やっていたグループ Cyder
インターンの感想(愚痴も含む)を赤裸々に話せるサービス ittern(未リリース)
とあるラジオのポータルサイト cho_kure_web
ActiveRecordっぽいO/Rマッパーの再実装 my_active_record
とか何か作ってます。(この辺は書くと長くなりそうなので割愛します)
こんな感じで楽しくやってます。
ISUCON7本選に参加して何もできませんでした
そもそもISUCONとは
ISUCONとはIikanjini Speed Up CONtest の略でwebサービスが動いているサーバが与えられ インフラ、データベース、アプリケーションなど色々な箇所に手を加えながらレスポンス速度を向上させるコンテストです。
このあたりを読むとISUCONがどんなものなのかざっくりと分かると思います。
インターン生向けのISUCON CM - Qiita
個人的に感じる面白さは
- 普段webアプリケーションを作る上ではそこまで気にしてないところまで知る良い機会になる
- パフォーマンスがスコアとして表示されるのでゲームとして楽しい
- web系企業のリードエンジニアと呼ばれる人がたくさん参加しているので凄さを肌で実感できる
あたりだと思っています。
興味をもった呉高専生がいたら自分に声かけてください! なんかします!
Advent Calendarっぽい内容はこれで終わりで以降は普通のISUCON本選エントリです。
ISUCON7本選に参加して何もできませんでした
先週の土曜日に開催されたISUCON7本選に学生枠として参加してきました。
チーム名は「なにもしちょらんのに壊れた」です。
一緒に参加したのは予選と同じく@k5342と@chigichan24の強い編入生達です。
マイメロディのパチモンも一緒に参加しました。
(chigichan24がICPCアジア大会に行ったときに買ってきたやつ)
予選のエントリはこちら euglena1215.hatenablog.jp
メンバーの本選エントリはこちら chigichan24.hatenablog.com blog.ksswre.net
前日
予選ではオンメモリする気満々で突撃して失敗したので 本選ではレギュレーションをしっかり読もうとかN+1をまず潰そうとか当たり前のことをちゃんとやろうと話をしました。
つくばから新宿に9時はつらいと思ったので自分と同じように編入した@planetary3gearの家に泊まらせてもらいました。ありがたい。
当日
コンテストが始まる前はtrelloの予選ボードにあるカードを本選ボードに移し戦いに備えました。
壊さないようにしていきたい #isucon pic.twitter.com/vc8mPoTj5p
— k5342 (@k5342) 2017年11月25日
今回はwebsocketを使った多人数で行えるクッキークリッカー(のようなもの)でした。
レギュレーションには今回のスコアの算出はHTTPリクエストによるものではなく
クッキークリッカーの操作の回数と書いてあってふむふむと思いながら読みました。
やったこと
DBのschema,
SELECT * FROM hoge LIMIT 5
のようなレコードのプレビューをSlackに貼った定番プロファイリング系ツールのmyprofiler, kataribe, rack-lineprofを入れた
myprofilerで見たところ、room_timeのtimeにindexを貼ったくらいであとは特にすることがなかった。
今回はwebsocketがメインだったのでアクセスログはほぼ出力されていなかったのでkataribeは使えなかった。
rack-lineprofではcalc_statusの後半部が圧倒的に遅いということがわかった。m_itemsテーブルのレコードがアプリケーション内で変更がないことが分かったのでグローバル変数化した。
が、擬似production環境での動作は確認したがベンチが通らなかったので放置した。
m_itemsをグローバル変数にしてみた by euglena1215 · Pull Request #5 · k5342/isucon7-final · GitHubcalc_statusで毎回全てを計算し直していたので確実にこれが原因だよねという話になり、状態をグローバル変数で持ち毎回差分のみを計算すればいいように変更しようとした。
が、全てを一度に修正しようとして頭がパンクしてしまい全然終わらなかった。calc_statusを半分諦めた状態で他のところを見ていると、
Concurrent::Channel.ticker(0.5)
となっている箇所を発見した。
レギュレーションでは「クッキークリッカーの操作を行ってから1秒以内に操作が反映されること」と書いてあった気がした(予選でも謎のsleep(1)
があったのでそーいう系かと思った)ので0.8秒に伸ばしベンチをかけてみたがあまり変化は見られなかった。
えいやっと0.5秒 -> 10秒とかに変えてみてもfailせずスコアが下がるだけだったので???となり放置した。
Tickerの時間をいじってみた by euglena1215 · Pull Request #9 · k5342/isucon7-final · GitHub上の変更がうまくいかなかったので逆にループ回数を減らせるのではないかと思い1000 -> 500にしてみたがfailするようになりダメだったので放置した。
tickerの時間にあわせてループ回数を変更してみた by euglena1215 · Pull Request #10 · k5342/isucon7-final · GitHub残り時間も少なくなり何をすればいいのか分からなくなったのでとりあえずベンチをEnqueueする機械と化していた。
結果としては19位となり、下から2番目で本選出場が決まったわりには悪くない?結果でした。
(初期スコアが6000前後だったので1,2つ変更が効いたくらいのスコア?)
ただ個人では スコアには何も貢献できませんでした。
予選もほぼ貢献できなかったのでメンバーには申し訳ねぇという気持ちとありがてぇという気持ちがフルスロットルです。。。
反省
実装力が低い
コンテスト後の講評ではcalc_statusの変更はまぁ終わるよね、という感じで実際ある程度のスコアを出しているチームはcalc_statusの修正はきちんと終えているようでした。
一方自分は一度に全部修正しようとして頭がパンクしてわちゃわちゃしてバグ連発みたいな感じだったので実装力がまだまだ足りてないなぁと思いました。問題を段階的に解決できなかった
calc_statusで問題を一度に解決しようとして分からなくなっていました。
終わってから考えてみると、シミュレーションしている処理と今までのを計算している処理でfor文を分離させるなど段階的に修正を加えることは可能だったことに気づき問題を段階的に解決することが全然習慣になってないなぁと思いました。
また、他のエントリを見ていると処理をループの外に出すなど簡単に高速化を図れる部分もあったみたいで問題への見方が偏っていたんだなと感じました。
問題に対して
nginxやDBの設定周りのチューニングはなかったものの、デカいメソッドが1つあってそいつが重すぎて話にならないといったケースは現実世界によくありそうで良い問題だったなぁと思いました。
(まずオンメモリにするところから始まるといった想定解答はどうなんだろうという気はしますが…)
来年が学生枠で出場できる最後のチャンスっぽいので頑張ろうと思います。
できれば来年も同じメンバーで出たいな。。。
ISUCON7予選に参加しました
ISUCON7予選に参加しました。
その前にきちんと投票しました。
チャリで行ったので雨が目に染みてつらかった。
最終スコアは40088点で学生枠で本選に出場することができました。
の強い編入生2人と一緒に参加した。
アプリケーション改修担当として参加した。
バグ取りが終わらなかったためスコアに反映させることができなかった...悔しい。
一緒に参加した2人が優秀で良かった...
やったこと
セットアップ
show create table hoge;
をslackで共有SELECT * FROM hoge LIMIT 10;
をslackで共有- myprofilerを入れる
myprofiler -user=root
でmysqlに繋げなかったので困った。
- kataribeを入れる
- rack-lineprofを入れる
ほぼコピペのセットアップを終わった時点で1時間くらい経っていた。
アプリケーション改修
まずmyprofilerでスロークエリを探してみたものの、何も表示されなかったのでそんなもんなのかーと思いrack-lineprofで遅い行を探す。
- GET /fetch が遅かったが、レギュレーションでスコア計算に入らないと書いてあったため無視。
- その次にGET /messageが重そうだった&N+1があったのでこの辺を改修することに決めた。
練習での経験*1よりmessageを全部redisに載せてみる作戦をとってみることに
(これが完全に判断ミスだった)- mysqlからredisにデータを流し込むスクリプトを書いた
- GET /messageを書き換えた
- POST /messageを書き換えた
- 他でmessageテーブルを参照しているところを探したら色んなところで使っていた
- 書き換える, また見つける, 書き換えるを永遠に行った
- 一通り書き換えが完了したのでwebサーバ起動させて動作を確認する
- バグが大量に生成したいたので地道に潰していく...で時間切れとなった。
- messageをredisに移植したかった by euglena1215 · Pull Request #2 · k5342/isucon7-qualification · GitHub
他のメンバーはアプリケーションをごにょごにょしている間にiconを304で返せるようにほげほげしてくれていた。
詳しくはこちら
反省点
- 影響範囲を考えて手をつけていなかった
- 変更しながら後悔する羽目になった
- channelごとのmessage数のカウンタをredisで実装とかは影響範囲が小さくある程度の効果が見込めたはず
- N+1をきちんと潰してなかった
- こちらの方が優先度は高いはず
- そもそもredisに慣れていなくて時間がかかった
- 鍛錬します
①N+1を潰す
②影響範囲の小さいところからredisに載せていく
の2つを確実にこなせるように本選まで練習したいと思う。
*1:pixiv isucon, ishocon1, isucon4でオンメモリ化させる判断がつかず点数が上がらなかった
ActiveRecordっぽいO/Rマッパーを作ってみた
ActiveRecordっぽいものを一度実装してみて本物と実装方法の違いを眺めるのが勉強になりそうだと思ったのでとりあえず作ってみました。
ついでにSQLite3のC言語APIをRubyで実行できるようにする拡張ライブラリsqlite3_coreも作りました。
※記事中では読みやすさのために例外処理は省いています。
実装したのは以下のメソッドになります。
set_database(db_path, type)
どの種類(type)のどこに保存されている(db_path)DBを利用するのか設定する。TableClass.new(attribute)
レコードオブジェクトを生成する。TableClass#{column名}
レコードオブジェクトのカラムデータを参照する。TableClass#{column名}=
レコードオブジェクトのカラムデータに書き込む。TableClass.where(condition)
conditionに合致するTableのレコードオブジェクトを全件取得する。TableClass.all
Tableのレコードオブジェクトを全件取得する。TableClass.find(id)
同一のidをもつTableのレコードオブジェクトを1件取得する。TableClass#save
レコードオブジェクトの情報をDBに保存する。TableClass#update(attribute)
レコードオブジェクトの更新をDBに反映させる。TableClass#destroy
レコードオブジェクトをDBから削除する。TableClass.belongs_to(table)
Tableがtableに所属しているリレーションをTableClassに反映させる。TableClass.has_many(tables)
Tableは複数のtableを所持しているリレーションをTableClassに反映させる。
それではひとつずつ見ていきます。
set_database(db_path, type)
どの種類(type)のどこに保存されている(db_path)DBを利用するのか設定する。
def set_database(db_path, type) $db = Object.const_get(type.to_s.capitalize).new(db_path) end
- DBの情報をグローバル変数として持たせることにしました。
type: :sqlite3
のときはSqlite3.new(db_path)
を実行するという単純な仕組みにしました。
irb(main):003:0> set_database('./db_test.sqlite3', :sqlite3) => #<Sqlite3:0x00007f86858fdc08 @db=#<Sqlite3Core:0x00007f86858fdbe0>>
TableClass.new(attribute)
レコードオブジェクトを生成する。
def initialize(args = {}) @table_schema = $db.table_schema(self.class.table_name) define_column_name store_record_to(args) define_accessor_belongs_to define_accessor_has_many end # レコードオブジェクトにnewの引数の値を格納 def store_record_to(info) @table_schema.keys.each do |column| if info.has_key?(column) instance_eval "@#{column} = #{info[column].inspect}" else instance_eval "@#{column} = nil" end end end
instance_eval
でレコードオブジェクトに初期値を与えるようにしました。- 引数
info
でeachを回すのではなくテーブルのカラム名でeachを回しているので存在しない引数を受け取ると無視してくれます。 define_column_name
,define_accessor_belongs_to
,define_accessor_has_many
は後ほど説明します。
irb(main):007:0> User.new => #<User:0x00007f8a2316bdb8 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=nil, @name=nil, @age=nil> irb(main):008:0> User.new(id:1, name: 'name01', age:20) => #<User:0x00007f8a24872ea8 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=1, @name="name01", @age=20> irb(main):021:0> User.new(id: 5, name: 'name05', height: 174, weight: 56) => #<User:0x00007f868582c428 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=5, @name="name05", @age=nil>
TableClass#{column名} / TableClass#{column名}=
レコードオブジェクトのカラムデータを参照/書き込みをする。
def define_column_name @table_schema.keys.each do |column| self.class.class_eval "attr_accessor :#{column}" end end
attr_accessor
で定義しました。
irb(main):009:0> user = User.new => #<User:0x00007f8a23124418 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=nil, @name=nil, @age=nil> irb(main):010:0> user.id = 1 => 1 irb(main):011:0> user.id => 1 irb(main):012:0> user.name = 'name01' => "name01" irb(main):013:0> user.name => "name01" irb(main):014:0> user.age = 20 => 20 irb(main):015:0> user.age => 20 irb(main):016:0> user => #<User:0x00007f8a23124418 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=1, @name="name01", @age=20>
TableClass.where(condition)
conditionに合致するTableのレコードオブジェクトを全件取得する。
def where(condition) objects = [] if condition == :all condition = nil end records = $db.select(table_name, condition) records.each do |record| objects << self.new(record) end objects end
- selectで取得した内容を引数に
TableClass.new
して配列に格納しました。
rb(main):019:0> User.where(age: 20) => [#<User:0x00007f8a23160828 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=1, @name="aaa", @age=20>, #<User:0x00007f8a24888398 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=2, @name="bbb", @age=20>, #<User:0x00007f8a23139390 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=3, @name="ccc", @age=20>] irb(main):020:0> User.where(age: 20, name: ['aaa', 'bbb']) => [#<User:0x00007f8a2310d740 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=1, @name="aaa", @age=20>, #<User:0x00007f8a230f50a0 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=2, @name="bbb", @age=20>]
TableClass.all
Tableのレコードオブジェクトを全件取得する。
def all where(:all) end
TableClass.where(condition)
を使いました。
irb(main):017:0> User.all => [#<User:0x00007f8a2484a048 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=1, @name="aaa", @age=20>, #<User:0x00007f8a24822c00 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=2, @name="bbb", @age=20>, #<User:0x00007f8a2301fea0 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=3, @name="ccc", @age=20>, #<User:0x00007f8a23818ff8 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=4, @name="ddd", @age=25>]
TableClass.find(id)
同一のidをもつTableのレコードオブジェクトを1件取得する。
def find(id) hash = $db.select(table_name, { id: id }) return nil if hash.empty? self.new(hash[0]) end
- そのままです。
irb(main):018:0> User.find(1) => #<User:0x00007f8a230446b0 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=1, @name="aaa", @age=20>
TableClass#save
レコードオブジェクトの情報をDBに保存する。
def save $db.insert(self.class.table_name, self.to_attr) end
- これもそのままです。
irb(main):021:0> User.find(5) => nil irb(main):022:0> user = User.new(id: 5, name: 'name05', age: 100) => #<User:0x00007f8a230bf180 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=5, @name="name05", @age=100> irb(main):023:0> user.save => true irb(main):024:0> User.find(5) => #<User:0x00007f8a238320e8 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=5, @name="name05", @age=100>
TableClass#update(attribute)
レコードオブジェクトの更新をDBに反映させる。
def update(attribute) if $db.update(self.class.table_name, attribute, self.id) @table_schema.keys.each do |column| if attribute.has_key?(column) instance_eval "@#{column} = #{attribute[column].inspect}" end end return true end false end
$db.update
でDBを更新した後instance_eval
でレコードオブジェクト自身も更新しています。
irb(main):028:0> user = User.find(1) => #<User:0x00007f8a2483aad0 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=1, @name="aaa", @age=20> irb(main):029:0> user.update(age: 500000000) => true irb(main):030:0> User.find(1) => #<User:0x00007f8a230f4178 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=1, @name="aaa", @age=500000000>
TableClass#destroy
レコードオブジェクトをDBから削除する。
def destroy $db.delete(self.class.table_name, self.id) end
- そのままです
irb(main):009:0> user = User.find(3) => #<User:0x00007f868598cc00 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=3, @name="ccc", @age=20> irb(main):010:0> user.destroy => true irb(main):011:0> User.find(3) => nil
TableClass.belongs_to(table)
Tableがtableに所属しているリレーションをTableClassに反映させる。
def belongs_to(table) if $db.table_schema(table_name).keys.include?("#{table}_id".to_sym) @@belongs_to_tables << table end end def define_accessor_belongs_to @@belongs_to_tables.each do |belongs_to_table| instance_eval <<~EOS def #{belongs_to_table} if @#{belongs_to_table}_id.nil? nil else Object.const_get('#{belongs_to_table}'.capitalize).find @#{belongs_to_table}_id end end EOS end end
define_accessor_belongs_to
はTableClass.new
内で実行されます。- クラス変数
@@belongs_to_tables
にbelongs_toしたいテーブルをためておいてnew時にinstance_eval
でメソッドを定義してます。
# post.rb class Post < NonActiveRecord belongs_to :user end irb(main):019:0> post = Post.find(1) => #<Post:0x00007f86859363a0 @table_schema={:id=>:integer, :title=>:text, :content=>:text, :user_id=>:integer}, @id=1, @title="title1", @content="content1", @user_id=1> irb(main):020:0> post.user => #<User:0x00007f86858fc0d8 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=1, @name="aaa", @age=20>
TableClass.has_many(tables)
Tableは複数のtableを所持しているリレーションをTableClassに反映させる。
def has_many(tables) if $db.table_schema(tables.to_s).keys.include?("#{self.to_s.downcase}_id".to_sym) @@has_many_tables << tables end end def define_accessor_has_many @@has_many_tables.each do |has_many_table| instance_eval <<~EOS def #{has_many_table} Object.const_get(self.class.singularize('#{has_many_table}').capitalize).where(#{self.class.to_s.downcase}_id: self.id) end EOS end end
- だいたい
belongs_to
と同じで定義されるメソッドの内容が違うだけです。
# user.rb class User < NonActiveRecord has_many :posts end irb(main):015:0> user = User.find(1) => #<User:0x00007f868580eec8 @table_schema={:id=>:integer, :name=>:text, :age=>:integer}, @id=1, @name="aaa", @age=20> irb(main):016:0> user.posts => [#<Post:0x00007f86859c6ce8 @table_schema={:id=>:integer, :title=>:text, :content=>:text, :user_id=>:integer}, @id=1, @title="title1", @content="content1", @user_id=1>, #<Post:0x00007f86859aef58 @table_schema={:id=>:integer, :title=>:text, :content=>:text, :user_id=>:integer}, @id=2, @title="title2", @content="content2", @user_id=1>]
感想
- 今回実装した処理と本家の処理を見比べてどこが違うのかを調べていきたいと思った。
- 本家のwhere句はメソッドチェインすると適切なSQL文が発行されるが、どう考えても謎なのでソースを漁ろうと思う。
【大学編入】Not情報系から情報系に編入が決まって不安な人がやっておくといいこと
Not情報系から情報系に編入が決まった方、おめでとうございます。
高専では情報系の科目が全然なかったけど本当にやっていけるんだろうか…と不安に思っている人はちょこちょこいると思います。
自分もそうだったので編入する前にこんなことをやっておくと(授業レベルでは)全然不安に思うことはないよってのを挙げてみたいと思います。 この分野が足りてないってのがあればコメントをもらえると助かります。
ブログを持つ
初めの記事は編入体験記とかでいいのでとりあえず技術系記事を書く媒体を持っておく。
以下の内容をやってみて「ここ面白いな」とか「ここもうちょっと深掘りしてみたいな」と思った内容を記事にまとめてみるのが良いと思います。
コンピュータシステムの理論と実装をやってみる
コンピュータシステムの理論と実装 ―モダンなコンピュータの作り方
- 作者: Noam Nisan,Shimon Schocken,斎藤康毅
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/03/25
- メディア: 単行本(ソフトカバー)
- この商品を含むブログ (3件) を見る
NAND素子からテトリスのようなアプリケーションが動く環境を作るをコンセプトに書かれている本です。
全てシミュレータ上で行えるのとTDDで作っていけるので独学しやすいと思います。
以下が気になる人におすすめです。
この解答を一通りまとめてみる
webに関する情報科学の基礎知識がまとまっています。
何かのアプリケーションは作ったことあるけど何がどうなって動いてるのかよくわかってないって人におすすめです。
関数型言語に触れてみる
だいたいの大学で関数型言語を扱う授業はあるように思います。(プログラム言語論とか)
あらかじめ触れて概念や構文を理解しておくと授業を楽に理解できるようになると思います。
ちなみに筑波大学の情報科学類はOCamlです。
(おまけ) 好きな言語をもっておく
編入してから確実に一度は好きな言語を聞かれるので好きな言語がない人は色々触って見つけておくといいかも。
その言語のパーフェクトシリーズやオライリーなどを1冊やっていると好きな言語が同じ人に会ったときに盛り上がれて良いと思います。
refineは一体どこで真価を発揮するのか
結論
ナンセンスな標準メソッドの実装にパッチを当てたくなったときにrefineは真価を発揮する。
以下、結論に至るまでに道のりです。
refineとは
Rubyにはrefineというどんなクラスのメソッドでもローカルで再定義できる機能がある。
[1] pry(main)> module StringExtensions [1] pry(main)* refine String do [1] pry(main)* def reverse [1] pry(main)* "esrever" [1] pry(main)* end [1] pry(main)* end [1] pry(main)* end => #<refinement:String@StringExtensions> [2] pry(main)> [3] pry(main)> module StringStuff [3] pry(main)* using StringExtensions [3] pry(main)* "my_string".reverse [3] pry(main)* end => "esrever" [4] pry(main)> "my_string".reverse => "gnirts_ym"
refineを含んでいるStringExtensions
をusingで呼び出した場所からStringStuff
の終わりまで再定義が有効になる。
refineの構文についての細かい説明は省略するので上記のコードからふわっと理解してほしい。
Railsに潜る
refineを知った最初は「めっちゃ便利じゃん!」と思ったけど、よく考えてみると大抵のことはサブクラス作って親クラスのメソッドオーバーライドを行えば解決しそうだし、refineを使わないと解決できない問題が思いつかなかった。
なのでrefineの真価を発揮する場面をRailsのソースコードから調べてみた。
すると1ヶ所だけヒットした。
% find . -type f -name "*.rb" | xargs grep 'refine' -n ./activesupport/lib/active_support/core_ext/enumerable.rb:142: refine Array do
# https://github.com/rails/rails/blob/32431b37704c0aaec06ae1a23e0d6091d6542fd2/activesupport/lib/active_support/core_ext/enumerable.rb 一部抜粋 # Array#sum was added in Ruby 2.4 but it only works with Numeric elements. # # We tried shimming it to attempt the fast native method, rescue TypeError, # and fall back to the compatible implementation, but that's much slower than # just calling the compat method in the first place. if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false) # Using Refinements here in order not to expose our internal method using Module.new { refine Array do alias :orig_sum :sum end } class Array def sum(init = nil, &block) #:nodoc: if init.is_a?(Numeric) || first.is_a?(Numeric) init ||= 0 orig_sum(init, &block) else super end end end end
Ruby 2.4でArray#sum
が追加されたけど要素にNumeric以外を入れるとTypeErrorになっちゃうからその辺イイ感じにしちゃうよー
って感じのコメントが書いてあるような気がする。
読んでいく。
if Array.instance_methods(false).include?(:sum) && !(%w[a].sum rescue false)
Array#sum
が存在するかつ[‘a’].sum
が例外を吐くときTrue
using Module.new { refine Array do alias :orig_sum :sum end }
if文中はArray#sum
がArray#orig_sum
という別名を定義
class Array def sum(init = nil, &block) #:nodoc: if init.is_a?(Numeric) || first.is_a?(Numeric) init ||= 0 orig_sum(init, &block) else super end end end
要素がNumericのときはArray#orig_sum
(再定義前のArray#sum)を使い、それ以外のときはEnumerable#sum
でイイ感じにする
(今回の目的はArray#sumの解読ではないのでこれ以上の追跡は省略する)
再定義前のArray#sum
を使ってnativeに任せられるところは任せている。
さらにif文を抜けるとArray#orig_sum
は使えなくなるため無駄に汚染することもない。
でもrefineなんか使わなくても以下のように直接Arrayクラスでprivateなorig_sumを宣言してしまえばいいじゃないかと思う人がいるかもしれない。
class Array alias :orig_sum :sum private :orig_sum def sum(init = nil, &block) # ... end end
refineを使ったときと使わないときの差を考えてみる。
refineを使用したとき
irb(main):001:0> module ArrayExtension irb(main):002:1> refine Array do irb(main):003:2* alias :sumsum :sum irb(main):004:2> end irb(main):005:1> end => #<refinement:Array@ArrayExtension> irb(main):006:0> module Hoge irb(main):007:1> using ArrayExtension irb(main):008:1> [1,2,3].sumsum irb(main):010:1> end => 6 irb(main):011:0> [1,2,3].sumsum NoMethodError: undefined method 'sumsum' for [1, 2, 3]:Array irb(main):012:0> [1,2,3].send(:sumsum) NoMethodError: undefined method 'sumsum' for [1, 2, 3]:Array
usingで呼び出したmodule中のみでArray#sumsum
が使えるがmodule外ではsendメソッドを使っても呼び出せない。
refineを使用してないとき
irb(main):001:0> class Array irb(main):002:1> alias :sumsum :sum irb(main):003:1> private :sumsum irb(main):004:1> end => Array irb(main):005:0> [1,2,3].sumsum NoMethodError: private method 'sumsum' called for [1, 2, 3]:Array irb(main):006:0> [1,2,3].send(:sumsum) => 6
Array#sumsum
はprivateメソッドなので[1,2,3].sumsum
はNoMethodErrorを吐くが、sendメソッドを使った場合Array#sumsum
が呼び出せてしまう。
今回の目的は標準メソッドにパッチを当てることなのでパッチを当てる前のメソッドが呼び出せてしまうのは意に反している。
だから標準メソッドにパッチを当てたいときはrefineを使った方が良いよねっていうお話でした。
- 作者: Paolo Perrotta,角征典
- 出版社/メーカー: オライリージャパン
- 発売日: 2015/10/10
- メディア: 大型本
- この商品を含むブログ (3件) を見る