7つのデータベース7つの世界を読んだ
タイトルは半分嘘で、全部じゃなくて途中まで読んだ。具体的には、第2章のPostgreSQL、第3章のRiak、第4章のHBase(Hadoop)の1,2日目、第5章のMongoDB、第8章のRedisの1,2日目。CouchDBとNeo4jはイントロだけ。
PostgreSQL
postgresqlは一応慣れ親しんでる(?)はずだけど、知らない事・使った事の無い機能がいっぱい載ってた。というか、普段はActiveRecord越しでイジるかデータの確認にシェルでselectするかぐらいでしか使ってなかったので、実質的には慣れ親しんで無かったんだと思う。
2日目で言うと、ストアドプロシージャ、トリガー、ビュー、クロス集計は使った事が無い機能だった。
ストアドプロシージャとは
DB側である程度まとまった処理を行いたい時に、プロシージャとして名前をつけて処理を保存しておける機能。「7つのデータベース 7つの世界」では、例としてテーブルA上に特定のレコードaがあるかを確認して、無ければaを作成してからテーブルB上にaと関連づけられたレコードbを追加するというストアドプロシージャのコードが載っていた。レコードの「確認(select)」と「追加(insert)」を1回でまとめて行えるので、パフォーマンスも上がるらしい。
ちょっと自分でもコードを書いてみようと思う。
データベースを作る。
いろいろ試すために、まずは練習用のデータベースを作成する。psql
コマンドでシェルを立ち上げて、以下のコマンドを実行。seven_database
テーブルが作られる。
user=# create database seven_database; // terminalでcreatedbでも良い CREATE DATABASE user=# \c seven_database You are now connected to database "seven_database" as user "(user_name)".
これでOK。次に、contribパッケージとしてcube
等が必要になるので、インストールする。一番楽なのはCREATE EXTENSION (パッケージ名)
でインストールする事らしいので、tablefunc, dict_xsyn, fuzzystrmatch, pg_trgm, cube
について順に実行してインストール。
現在のデータベースにインストールされたパッケージは、pg_extensionビューで確認できる。
seven_database=# select extname from pg_catalog.pg_extension ; extname --------------- plpgsql tablefunc dict_xsyn fuzzystrmatch pg_trgm cube (6 rows)
ちゃんとインストールされている事が分かる。
次に、テーブルとして7つのデータエース 7つの世界に載ってたgenres, movies, actors, movie_actorsテーブルを作る。
このページにおいてあるコードをダウンロードしてpsql -d seven_database < create_movies.sql
で実行するか、シェルから以下のSQLを実行。
CREATE TABLE genres ( name text UNIQUE, position integer ); CREATE TABLE movies ( movie_id SERIAL PRIMARY KEY, title text, genre cube ); CREATE TABLE actors ( actor_id SERIAL PRIMARY KEY, name text ); CREATE TABLE movies_actors ( movie_id integer REFERENCES movies NOT NULL, actor_id integer REFERENCES actors NOT NULL, UNIQUE (movie_id, actor_id) ); CREATE INDEX movies_actors_movie_id ON movies_actors (movie_id); CREATE INDEX movies_actors_actor_id ON movies_actors (actor_id); CREATE INDEX movies_genres_cube ON movies USING gist (genre);
これでOK。
ストアドプロシージャを作ってみる
とりあえず書籍をパクって、moviesテーブルにレコードがあるかを確認して、無ければ作成してからそれに関連付けられたレコードをactorsテーブルにinsertしてみる。
1 CREATE OR REPLACE FUNCTION add_actor_with_movie( actor_name text, movie_title text, movie_genre cube ) 2 RETURNS boolean AS $$ 3 DECLARE 4 did_insert boolean := false; 5 found_count integer; 6 the_movie_id integer; 7 the_actor_id integer; 8 BEGIN 9 SELECT movie_id INTO the_movie_id 10 FROM movies m 11 WHERE m.title=movie_title AND m.genre=movie_genre 12 LIMIT 1; 13 14 IF the_movie_id IS NULL THEN 15 INSERT INTO movies (title, genre) 16 VALUES (movie_title, movie_genre) 17 RETURNING movie_id INTO the_movie_id; 18 19 did_insert := true; 20 END IF; 21 22 -- Note: not an “error”, as in some programming languages 23 RAISE NOTICE 'Venue found %', the_movie_id; 24 25 INSERT INTO actors (name) 26 VALUES (actor_name) 27 RETURNING actor_id INTO the_actor_id; 28 29 INSERT INTO movies_actors(movie_id, actor_id) 30 VALUES (the_movie_id, the_actor_id); 31 32 RETURN did_insert; 33 END; 34 $$ LANGUAGE plpgsql;
とりあえずこんな感じで書いたらちゃんと動いた。32行目でRETURN did_insert
してて、実際にmovieのinsertを行った場合はt
が返ってきてる。
seven_database=# SELECT add_actor_with_movie('Taro', 'Star Wars','(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)'); NOTICE: Venue found 2 add_actor_with_movie ---------------------- t (1 row)
'Star Wars'に違うactorを紐づけて実行すると、ちゃんとactorはinsertされるけど返り値はfになる。
seven_database=# SELECT add_actor_with_movie('Jiro', 'Star Wars','(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)'); NOTICE: Venue found 2 add_actor_with_movie ---------------------- f (1 row)
とりあえず想定通りの挙動はしてる。
ハマりポイント
ストアドプロシージャの引数名がtableのカラム名と一致してるとエラーが出た。下の実行例は、ストアドプロシージャの引数名としてtitle
を使ってたら、movie tableのtitle
カラムと同じって事でエラーが出てる。
seven_database=# SELECT add_actor_with_movie('Saburo', 'Star Wars','(0,7,0,0,0,0,0,0,0,7,0,0,0,0,10,0,0,0)'); ERROR: column reference "title" is ambiguous LINE 2: WHERE m.title=title AND m.genre=movie_genre ^ DETAIL: It could refer to either a PL/pgSQL variable or a table column. QUERY: SELECT movie_id FROM movies m WHERE m.title=title AND m.genre=movie_genre LIMIT 1 CONTEXT: PL/pgSQL function add_actor_with_movie(text,text,cube) line 8 at SQL statement
これをErrorにするのは許せない。。。
あと、途中でいくつか作り替えながら試してみたら、同じadd_actor_with_movie
という名前のストアドプロシージャを複数作ってしまって削除の必要が生じた。どうやら、引数の型が違うと同じ名前でも別のプロシージャとして保存されるっぽい。
このページいわく
drop function プロシージャ名([引数の型])
で削除できたので、drop function add_actor_with_movie(text, text, cube);
で削除した。あと、引数の型は変えずに名前だけ変えようとすると既存のプロシージャを消してくれってエラーが出た。
seven_database=# \i my_code/add_actor_with_movie.sql psql:my_code/add_actor_with_movie.sql:34: ERROR: cannot change name of input parameter "title" HINT: Use DROP FUNCTION add_actor_with_movie(text,text,cube) first.
いったんまとめ
学んだ機能を順に説明してくつもりがストアドプロシージャだけでそこそこ時間たっちゃったので、今日はここまで。ストアドプロシージャは強力だけどデータベースにロジックが激しく依存してしまうので、使いどころは難しそう。覚悟の上で使うなら良いと思う。
SQLアンチパターンを読んだ
3ヶ月くらい積読されてたのを引っ張り出して読んだ。例によって、読み始めるとめちゃめちゃ面白くて何故こんな素晴らしいものを積読してたんだ...って感じだった。
ざっくりとした感想
けっこう色んな人が読んだ感想とか内容を簡単にまとめたものとかを書いてるので、ぶっちゃけ今更な気もするけど簡単にまとめとく。
この本ではSQL(というかRDB)のスキーマ設計とかクエリの投げ方とかで陥りがちなアンチパターンを25個に絞って順に解説していってる(1章で1つのテーマを扱うので、ちょうど25章ある)。25章とか多すぎるんじゃってビビってたけど、むしろ1章1章のボリュームは抑え気味で簡潔にまとまってて読みやすかった。
アンチパターンって言っても頭ごなしに否定する訳じゃなくて、どういう意図があってそのアンチパターンを採用してしまうのかとか、より良い解決策は何なのかとか、そういった事が順に説明されてく構成だった。「何を避けるべきか」だけで無く、「どう進むべきか」の指針も打ち出してくれてて、嬉しかった。
自分にとってはⅠ部の「データベース論理設計のアンチパターン」で扱われてた5~8章が印象的で、特に「データのメタデータへの混入」や「メターデータのデータへの混入」を避けよう、みたいな特定のパターンに依存しない心がけを知れたのが良かった。8章の「メタデータトリブル」で扱われてたケース(データであるはずの「年代」をメタデータであるテーブル名として採用してしまう)はけっこうやってしまいそうな気がしたので、まずいって感覚を掴めたのは良かったと思う。
24章. マジックビーンズについて
24章はモデルをアクティブレコードそのものとして扱うことへの弊害について書かれていた。この辺のこと(特にRailsにおいてActiveRecord::Baseを継承したオブジェクトがmodelとして扱われること)はこれまでに多くの人がいろんな議論をしていて、自分の中でもいろいろ思うところがあったので、ちょっとまとめておく。
まずは用語の定義から。マーティン・ファウラーがPoEAAで取り上げた「アクティブレコードパターン」はモデルオブジェクトのクラスをテーブルと対応付け、1つのオブジェクトをテーブルの1行と、オブジェクトの1つのフィールドをテーブルの1つの列と対応付けるというもの。アクティブレコードは基本的なCRUD操作をサポートする。実は今挙げた点だけだと「Row Data Gatewayパターン」(これもPoEAAで取り上げられてるもの)と全然違いはなくて、ただアクティブレコードは domain logicを含む という点でRow Data Gatewayとは違う。
よく問題だと言われるのが、アクティブレコードをデータアクセスオブジェクト(DAO)としてしか使わず、Controllerにロジックを書いてしまうというもの。正直、これに関してはModelやControllerの役割を理解してれば絶対にしない事だからあまり問題じゃ無いと思う。
また、別の点でよく突っ込まれるのは
- アクティブレコードはモデルをデータベーススキーマに強く依存させてしまう
- アクティブレコードを用いると1つのクラスで扱う仕事が多くなり過ぎる
みたいな点で、これはまあその通りかなと思う。というか、アクティブレコードだけを用いてモデル層を記述しようとすれば、そりゃこういった問題は出て来るだろうという感じ。
PoEAAの中でマーティン・ファウラーも言ってるんだけど、そもそもアクティブレコードはロジックの薄いモデルを扱うときに便利なパターンで、複雑なロジックを記述するには向いていない。だからまあ複雑になってきたらDAOとしての機能は切り出して別のモデルクラスにロジックは書くとか、そういった感じで対処すれば良いしそれが望まれてるんだと思う。
この辺は既に経験豊かな優秀な人たちがたくさん議論してて、Web上にもいっぱい情報が落ちてるのでそういった情報を参考にしながら適宜対処していけば良さそう。
まとめ
ブラウザで動くMarkdown editor
vue.jsの公式サイトのexampleにmarkdown editorっていうのがあって、以下のコードの様にただfilterにmarked指定するだけでちゃんと動いててすごかった。
new Vue({ el: '#editor', data: { input: '# hello' }, filters: { marked: marked } })
htmlもこれだけ
<div id="editor"> <textarea v-model="input"></textarea> <div v-html="input | marked"></div> </div>
marked.jsのmarked関数を適用してるだけなのでmarked.jsがすごいのかもだけど、感動したので手元でも動かしてみた。
環境
例によって、yoのwebappを使う。
bowerでvue.jsとmarked.jsをinstall。ついでに、textareaが縦に伸びていく様にjquery-autosizeもインストール。
$bower install vue marked jquery-autosize --save
markedは勝手にはindex.htmlに追記されないので、自分で追記してやる必要がある。
<!-- manual import --> <script src="bower_components/marked/lib/marked.js"></script>
後は、autosizeを適用するコードをjsに追記。
11 $(document).ready(function(){ 12 $('#autosizable').autosize(); 13 });
htmlもちょっと修正
38 <div id="editor"> 39 <div v-html="input | marked"></div> 40 <textarea v-model="input" id="autosizable" ></textarea> 41 </div> 42 </div>
cssも書いてやる。
95 html, body, #editor { 96 margin: 0; 97 height: 100%; 98 font-family: 'Helvetica Neue', Arial, sans-serif; 99 color: #333; 100 } 101 102 textarea, #editor div { 103 display: inline-block; 104 width: 49%; 105 height: 600px; 106 vertical-align: top; 107 -webkit-box-sizing: border-box; 108 -moz-box-sizing: border-box; 109 box-sizing: border-box; 110 padding: 0 20px; 111 } 112 113 textarea { 114 border: none; 115 border-right: 1px solid #ccc; 116 resize: none; 117 outline: none; 118 background-color: #f6f6f6; 119 font-size: 14px; 120 font-family: 'Monaco', courier, monospace; 121 padding: 20px; 122 } 123 124 code { 125 color: #f66; 126 } 127
ついでに、highlight.jsをインストールしてcodeのsyntax-highlightもしてみた。
$ bower install highlightjs --save
jsにもちょこっと追記
1 marked.setOptions({ 2 highlight: function (code) { 3 return hljs.highlightAuto(code).value; 4 } 5 });
これだけで、以下の画像みたいに表示される。けっこうちゃんとしたmarkdown editorになっててすごい!!
ちなみに、右側がテキストエリア、左側がmarked.jsでレンダリングした結果になっている。vue.js使ってるのでもちろんリアルタイムで表示がされる。
まとめ
余計なお世話かもだけど、はてなブログにもプレビュー同時に見ながら編集できるマークダウンエディタがついてると嬉しいなーとか思った。ただ、画面のサイズどうするのとかいろいろ問題あるし、実際つけると微妙なのかもなーとも思った。試してみないと分からなそう。
ただ、はてなブログのプレビュー見るときにサーバーへリクエスト送ってるっぽくてけっこう遅延があるので、クライアントサイドでのレンダリングは試す価値があると思う。
Masonry試してみた
とある事情でPinterest的UI作ってみたいなーと思ってて、Masonryを試してみた。
環境構築
とりあえず、試すための環境構築をしてみる。久しぶりに、yeomanを使う。
yeoman
久しぶりでversionが古い気がしたので、npm update
してnpmのモジュールを一気にupdateした。この時点で、yo, bower, gruntのversionはそれぞれ
$ yo -v 1.2.0 $ bower -v 1.3.12 $ grunt -V grunt-cli v0.1.13 grunt v0.4.5
みたいな感じになった。
yoで雛形を作る。
まず、webappという名前のgeneratorをinstallする。
npm install generator-webapp
これで、yo webapp
でwebapp generatorを使ってwebアプリケーションの雛形を作ることが出来る。どうでも良いかもだけど、npmコマンドでyo用のgeneratorをインストールするって忘れててちょっと戸惑ってしまった。
次に、yoでgeneratorを走らす。
$ yo webapp _-----_ | | .--------------------------. |--(o)--| | Welcome to Yeoman, | `---------´ | ladies and gentlemen! | ( _´U`_ ) '--------------------------' /___A___\ | ~ | __'.___.'__ ´ ` |° ´ Y ` Out of the box I include HTML5 Boilerplate, jQuery, and a Gruntfile.js to build your app. [?] What more would you like? Bootstrap, Sass, Modernizr [?] Would you like to use libsass? Read up more at https://github.com/andrew/node-sass#node-sass: Yes create Gruntfile.js create package.json create .gitignore create .gitattributes create .bowerrc create bower.json create .jshintrc create .editorconfig create app/styles/main.scss create app/favicon.ico create app/robots.txt create app/index.html create app/scripts/main.js invoke mocha . . .
この時、最初に
[?] What more would you like? (Press <space> to select) ❯⬢ Bootstrap ⬡ Sass ⬡ Modernizr
みたいな画面が出て、Bootstrap, Sass, Modernizrをそれぞれ使うかどうかを選べた。j, kで上下移動でspaceで切り替えが出来て、今回は3つともチェックをつけて実行した。
処理が終わるといろいろファイルが作られてた。
$ ls
Gruntfile.js app bower.json node_modules package.json test
ただ、bower.jsonに記載されてる類のjsファイルはまだインストールされてなかったので、bower install
してやる必要があった。
$ bower install bower modernizr#~2.8.2 cached git://github.com/Modernizr/Modernizr.git#2.8.3 bower modernizr#~2.8.2 validate 2.8.3 against git://github.com/Modernizr/Modernizr.git#~2.8.2 bower bootstrap-sass-official#~3.2.0 cached git://github.com/twbs/bootstrap-sass.git#3.2.0+2 bower bootstrap-sass-official#~3.2.0 validate 3.2.0+2 against git://github.com/twbs/bootstrap-sass.git#~3.2.0 bower jquery#>= 1.9.0 cached git://github.com/jquery/jquery.git#2.1.3 bower jquery#>= 1.9.0 validate 2.1.3 against git://github.com/jquery/jquery.git#>= 1.9.0 bower bootstrap-sass-official#~3.2.0 install bootstrap-sass-official#3.2.0+2 bower modernizr#~2.8.2 install modernizr#2.8.3 bower jquery#>= 1.9.0 install jquery#2.1.3 bootstrap-sass-official#3.2.0+2 bower_components/bootstrap-sass-official └── jquery#2.1.3 modernizr#2.8.3 bower_components/modernizr jquery#2.1.3 bower_components/jquery
これで、bower_componentsというディレクトリが生成され、そこにbootstrap, jquery, modernizrなどのライブラリがインストールされた。
$ cat bower.json { "name": "masonry-practice", "private": true, "dependencies": { "bootstrap-sass-official": "~3.2.0", "modernizr": "~2.8.2" } }% $ ls bower_components bootstrap-sass-official jquery modernizr
良い感じ
一度雛形のWebAppを動かしてみる
とりあえず、今generatorで作ったアプリケーションを動かしてみる。grunt serve
コマンドを叩くとlocalhost:9000をlistenするサーバーが起動され、さらに勝手にブラウザの画面がたちあがる。
$ grunt serve // 注: grunt serverではない。serverコマンドは非推奨らしい。
// サーバーが立ち上がり、ブラウザにhttp://localhost:9000/が表示される。
ここまではうまくいった。
masonryを試す
次に、masonryを試してみる。bowerでmasonryをインストールする。
bower install masonry --save
割といろいろ依存してるみたいで、けっこうbower_componentsの中身が増えた。
$ ls bower_components bootstrap-sass-official eventEmitter get-size jquery matches-selector outlayer doc-ready eventie get-style-property masonry modernizr
あと、app/index.htmlにも勝手にscriptタグが追加されてた。
// app/index.htmlのscript読み込み部分 <!-- build:js(.) scripts/vendor.js --> <!-- bower:js --> ~ いろいろ ~ <script src="bower_components/get-style-property/get-style-property.js"></script> <script src="bower_components/get-size/get-size.js"></script> <script src="bower_components/eventie/eventie.js"></script> <script src="bower_components/doc-ready/doc-ready.js"></script> <script src="bower_components/eventEmitter/EventEmitter.js"></script> <script src="bower_components/matches-selector/matches-selector.js"></script> <script src="bower_components/outlayer/item.js"></script> <script src="bower_components/outlayer/outlayer.js"></script> <script src="bower_components/masonry/masonry.js"></script> <!-- endbower --> <!-- endbuild -->
ちょこちょこ修正
もともとあったdiv要素とか消してmasonryのGetting started見ながらdiv作ったり簡単なjsを書いたりした。
これがjs。ほぼドキュメントのまんま。
1 var container = document.querySelector('#container'); 2 3 imagesLoaded( container, function() { 4 var msnry = new Masonry( container, { 5 columnWidth: 340, 6 itemSelector: '.item' 7 }); 8 });
これがhtmlに追記した部分。
36 <div id="container"> 37 <div class="item"> 38 <img src="http://corp.toei-anim.co.jp/press/watori01.jpg" width="320px"> 39 <p>ワールドトリガー</p> 40 </div> 41 <div class="item"> 42 <img src="http://worldtrigger.info/images/cover.png" width="320px"> 43 <p>ワールドトリガー</p> 44 </div> 45 <div class="item"> 46 <img src="http://www.tv-asahi.co.jp/worldtrigger/intro/img/wt.jpg" width="320px"> 47 <p>ワールドトリガー</p> 48 </div> 49 <div class="item"> 50 <img src="http://livedoor.blogimg.jp/zakuzaku911/imgs/f/3/f39495e6.jpg" width="320px"> 51 <p>ワールドトリガー</p> 52 </div> 53 </div>
これがcss。丸みつけたり線引いたりした。
94 .item { 95 margin: 5px; 96 border: 1px solid #ccc; 97 box-shadow: 0 2px 1px #eee; 98 -webkit-border-radius: 4px; 99 }
で、出来たのがこんな感じ。
うん、なんか良さげな気がする!!!!
ここから発展させるとすると、XMLHTTPRequestで追加で画像取得してどんどん下に伸びていけばさらにそれっぽくなると思う。
まとめ
サクッとそれっぽいviewが作れて楽しかった。Masonry既にめちゃめちゃ有名だけどやっぱり良さげだった。
RustにおけるOwnershipの仕組み
前回に引き続き、Mozillaイチオシの言語であるRustを触ってみる。今日はメモリ管理の仕組みとしての「Ownership」に着目する。
ちなみに、今日書く内容はRust Guideの17章、OwnershipのGuide、PointerのGuideをつまみ食いしながら適当にまとめたものなので、原文読んでもらっても良い。
メモリ管理について
まずは背景として、メモリ管理の必要性について考えてみる。
一般に、計算機上で動くソフトウェアというのはデータをメモリ上に次々に確保して様々な計算等を行っていく訳だけど、メモリというのは有限の大きさしか持たない為にデータの追加ばかりしていく訳にも行かなくて、使わなくなった領域を次々に解放して再利用しなければならなくなる。
こういった事を考えると、メモリ解放の機構を言語に組み込む必要性があると分かる。
メモリに確保されるデータの種類
メモリに確保されるデータには、大別すると次の3種類がある。
- ソフトウェア実行中はずっと残る静的なデータ
- プロシージャ呼び出し時に確保され、プロシージャを抜けると解放されるスタック上のデータ
- ソフトウェア実行時に動的に確保されるデータ(ここで確保される領域をヒープメモリと呼ぶ)
メモリの解放という文脈でこれらのデータを考えると、1の静的なデータはずっと残るものなので解放されないし、2のスタック上のデータは勝手に解放されるので気にしなくて良いのだけど、3の動的に確保されるデータをどう扱うかが問題になってくる。
c言語では、mallocやfreeによってユーザーが責任を持ってメモリの確保と解放を行うことが求められたけど、メモリリークや不正なメモリへのアクセス等が起きやすく大きな問題を抱えていた。
一方、近年の多くの言語では Garbage Collection(GC) の機構を組み込みで持つことでユーザーをメモリ管理の複雑さ・煩雑さから解放したが、 GC 実行時にストップザワールドが起こる等のデメリットもあった。
そこで、Rustは「Ownership」という考え方を導入することで、メモリリークの危険性を抑えながらパフォーマンス上の問題を解決することを目指した。
Ownership
動的にメモリを確保する際、例えばc言語では
{ int *x = malloc(sizeof(int)); // we can now do stuff with our handle x *x = 5; free(x); }
の様な書き方をする。同様の事が、Rustにおいては
{ let x = box 5i; }
という記述で表現できる。ポイントは、この記述できちんとメモリの確保と解放が表現出来ている事だ。
box というキーワードによって 5i という値がヒープ上に動的に確保され、その参照がxへbindされている。このxがownershipを持っていて、xがscopeから外れる(xを含むブロックを抜ける)時にメモリの解放が行われる。
この様に変数にownershipを持たせる事で、メモリの解放が必ず行われる事を保証できる様になる。
ownershipは移動させることもできて、例えばBox型の値(boxキーワードでヒープに確保した値への参照)を引数として関数を呼び出した場合には、ownershipはその関数内の変数numに移る。
fn main() { let x = box 5i; add_one(x); } fn add_one(mut num: Box<int>) { *num += 1; }
ただし気をつけなければならないのは、ownershipを移すと、ownershipを失った変数からは元の値へアクセス出来なくなる事である。上記の例であれば、add_one(x)呼び出しの後にxを使おうとするとエラーが出る。
fn main() { let x = box 5i; add_one(x); println!("{}", x); // error!!!!! } fn add_one(mut num: Box<int>) { *num += 1; }
この問題を避けるために、例えば関数から返り値を使うといった方法が考えられる。
fn main() { let x = box 5i; let y = add_one(x); println!("{}", y); } fn add_one(num: Box<int>) -> int { *num + 1; } //注: これはあくまで例で、あまり良い方法では無い。
ただし、今回の例ではnumの所有権は一時的なものであれば良く、返り値という形ですぐにまたmainのyに戻されている。こういった一時的な所有権というシチュエーションは良くあるものであり、上記の例よりもBorrowingという仕組みを使う方が推奨されている。
Borrowing
Borrowingとは、ownershipは移さずに値への参照を可能にする機構である。&キーワードで値への参照を作ると、それは値をborrowingする為のpointerとなる。例えば、ownershipを移すことで実現していた上記の例は、以下のコードに書き直せる。
fn main() { let x = box 5i; println!("{}", add_one(&*x)); } fn add_one(num: &int) -> int { *num + 1 }
呼び出し方がちょっとややこしくなってるけど、 *x で5iという値を意味していて、さらにその値をborrowingするために&キーワードで参照を作っている。
xのownershipは移動しない為、何度でもxの値を使用できる。
fn main() { let x = box 5i; println!("{}", add_one(&*x)); println!("{}", add_one(&*x)); // 何度でもxを使える!! println!("{}", add_one(&*x)); // 何度でもxを使える!! }
良い感じ。
mutableなborrowing
mutableなborrowingというのも一応出来て、&mutキーワードを使ってmutableな参照を作れば良い。ただし、もともとの値がmutableでないといけない。
fn main() { let mut x = box 5i; add_one(&mut *x); println!("{}", x); // xの値はborrowingされただけで移動してないから使える!! } fn add_one(num: &mut int) { *num += 1 }
mutや&mutや*の様なキーワードが入り乱れる事になるのでちょっとつらい感じになる。
Owner、およびBorrowerの権限について
Rustにおいては、ownerやborowerにはそれぞれ独自の権限がある。ownerは、次の3つの権限を持つ。
owner の権限
- リソースがいつ解放されるかを制御出来る。
- リソースを immutable な形で多くの borrower に貸し与えることが出来る。
- リソースを mutable な形で1つの borrower に貸し与えることが出来る。
ただし、これは次の2つの制約の存在も意味する。
owner の制約
- 既に誰かに貸し与えたリソースを変更したり、mutable な形で別の誰かに貸し与えたりする事は出来ない。
- 既に誰かに mutable な形で貸し与えたリソースは、アクセスすることが出来ない。
基本的に、変更によって変な競合が起きる可能性のある事は禁止されてるっぽい。
また、borrowerには次の権限がある。
- borrowがimmutableなら、リソースの読み取りが出来る。
- borrowがmutableなら、リソースの読み書きが出来る。
- 他の誰かにリソースを貸し与える事が出来る。
ただし、borrowerにも重要な制約があり、
「他の誰かにリソースを貸し与えたら、自分のborrow backの前に貸し与えた分を borrow backしてもらわなければならない」
らしい。この制約が守られないと、例えばownerのスコープが終了してメモリが解放され、不正な値となった領域にborrowerがアクセスする、といった危険な事が起きてしまう。そういった意味で、大事な制約となっている。
所感
Ownershipについて調べてみて、Cなんかと比べて一番便利なのはヒープ上に確保した値をプロシージャで安心して返せることなのかもなーと思った(返り値のownerが明確であれば、メモリの解放を保証出来るから)。
正直、Cでバリバリコーディング出来るとかでは全然無いんだけど、解放が保証されるか分からないポインタを使うとか想像するだけで怖すぎるなーとは思う。
で、C++におけるメモリ管理のお作法がちょっと気になったのでGoogle C++スタイルガイド 日本語訳とかいうのを見てみたら、現代的なC++コーディングではスマートポインタという解放が保証された機構を使うものらしい事を知った。
基本的には所有権の移動が無い scoped_ptr を使って、どうしても共有の必要性が出てきたら(参照カウントを組み込んだ) shared_ptr を使うと。なるほど。
割とRustと似たような事をやるっぽい。
Rustに入門してみる
今日は、Rustに入門してみる。
Rustは、Mozillaの開発したプログラミング言語である。公式サイトでは"high-level, bare-metal programming"を謳っており、高い抽象度を保ちながら低いレイヤーのコントロール(具体的にはユーザーによるメモリ管理等)が可能らしい。
近年出てきた言語の内、高い抽象度とハイパフォーマンスの両立を強みにしていた言語としてはGo言語が真っ先に思い浮かぶ。ただ、GoにおいてはGCを採用していた事を考えると、手動でメモリ管理が可能なRustはさらに低レイヤーを担当する事を目指してるようだ。
MozillaとしてはCやC++に置き換わりたい様だし、そういった立ち位置を目指す言語に興味が湧いたのでちょっと触ってみた。
とりあえず、公式サイトのGuideの手順を順番に試してみる。
1. RustをMacにインストール
公式サイトに従って、curlでschellscriptダウンロードして実行する。
curl -s https://static.rust-lang.org/rustup.sh | sudo sh
rustとcargoのinstallerのダウンロードが始まって、だいたい5~10分程でインストールが完了する。
ちなみに、installされたRustのversionはrustc --version
で確認出来て、14/12/06時点では以下だった。
rustc 0.13.0-nightly (6f4c11be3 2014-12-05 20:23:10 +0000)
まだ1.0前のアルファ版なのでアップデートはあるらしく、定期的にcurl -s https://static.rust-lang.org/rustup.sh | sudo sh
でアップデートしてねって感じらしい。
2. Hello worldしてみる。
とりあえずHello world。main.rsファイルにmain関数を定義。
// main.rs fn main() { println!("Hello, world!"); }
rustcコマンドでコンパイル。そして実行
$ ls main.rs $ rustc main.rs $ ls main main.rs $ ./main Hello, world!
ちゃんとHello worldが出力された。やったぜ!!
main.rsのコードについて
割と見たまんまの解釈で良くて、fnキーワードと波括弧({})で囲んだブロックでmain関数を定義してる。main関数は特別な関数で、生成したバイナリの実行時にはmain関数の中身が実行される(この辺はCとかと一緒)。
println!ってのが実はただの関数じゃなくて、Macroらしい。ビックリマーク(!)ついてればMacroらしいけど、まだ序盤だからって事で理由は教えてくれなかった。そもそもRustにおけるMacroってのが何を指してるのかはまだ分からない。
後、indentはスペース4つって指定されてた。
3. Cargoを使う。
CargoというのはRustのプロジェクトを管理する為のツールで、具体的にはRustのコードのビルド、依存ライブラリのダウンロード、依存ライブラリのビルドの3つの仕事を行うらしい。そこそこの規模のプログラムを書くならCargoを使うべきとの事。
まあ今は練習なので、先ほど書いたmain.rsをCargoプロジェクトにしてみる。まずは、srcディレクトリを作ってそこにmain.rsを移動する。cargoでは、srcディレクトリにコードを置くのが規約らしい。
$ mkdir src $ mv main.rs src $ rm main
次に、configuration fileであるCargo.tomlを生成。TOMLって見た事なかったんだけど、INIの進化版みたいな感じらしい。
// Cargo.toml [package] name = "hello_world" version = "0.0.1" authors = [ "south37 <sout37777@gmail.com>" ] [[bin]] name = "hello_world"
Cargo.tomlにはプロジェクト名、version, author名なんかを書く。そして、buildコマンドでビルド。
$ cargo build Compiling hello_world v0.0.1 (file:///Users/<ユーザー名>/Documents/programs/rust/hello_world) $ ./target/hello_world Hello, world!
これで、targeフォルダ以下に実行ファイル(hello_world)が生成される。良い感じ!!
ちなみに、cargo build実行後にはCargo.lockファイルが生成される。中身は
[root] name = "hello_world" version = "0.0.1"
で、あまり大した内容になってないけど、依存ライブラリ等があればここに書き込まれていくらしい。
lockって拡張子がGemfile.lockを彷彿とさせるんだけど、どちらも元ネタは同じだったりするのだろうか?gemはまさに依存関係管理ツールだし。
4. 変数へのbinding
Rustでは、letキーワードを使って変数への値のbindingを行う。Rustは静的型付け言語であり、型付けを行わなければならない。
let x: int = 5;
型の表記が無いとエラーが出る。
let x = 5;
$ rustc practice.rs practice.rs:2:9: 2:14 error: cannot determine a type for this local variable: cannot determine the type of this integer; add a suffix to specify the type explicitly [E0102] practice.rs:2 let x = 5; ^~~~~ error: aborting due to previous error
ただ、Rustは型推論をサポートしている為、型が自明であれば問題は無い。例えば、上記の例ではiをつけて数値がintegerである事を示してあれば大丈夫。
let x = 5i; // 5の後ろのiがintegerの値であることを示している。
デフォルトでは変数はimmutableであり、書き換えることは出来ない。
let x = 5i;
x = 10i;
$ rustc practice.rs practice.rs:3:5: 3:12 error: re-assignment of immutable variable `x` practice.rs:3 x = 10i; ^~~~~~~
mutableな変数を用意したければ、mutキーワードを使う。
let mut x = 5i; x = 10i;
immutableがデフォルトってのは個人的にはとても好ましい。公式サイトによれば、安全性を考慮した結果らしい。
5. if構文
Rustのifを使った条件分岐は見たまんま。自然に理解できる。
let x = 5i; if x == 5i { println!("x is five!"); } else { println!("x is not five :("); }
ただ、Rustにおいては構文のほとんどが式(expression)であり、値を返すため、if式も下みたいな使い方ができる。
let x = 5i; let y = if x == 5i { 10i } else { 15i };
良い感じ。
Rustにおける構文の内、値を返さない文(statement)は2種類しか無いらしい。一つは変数のbindingで、下記の表記は出来ない。
let x = (let y = 5i); // expected identifier, found keyword `let`
もう一つはexpression statementと呼ばれる構文で、セミコロン(;)をつけることでexpressionがstatementに変換される。(逆に言えば、デフォルトではrustの構文は全てがexpression)。
例えば、if式の中でセミコロン(;)を使うと値が返されない(より正しく言うとunitと呼ばれる特別な値が返される)。その為、以下のセミコロンを用いたif構文の結果を変数にbindingしようとするとエラーが出る。
let x = 5i; let y: int = if x == 5i { 10i; } else { 15i; };
$ rustc practice.rs practice.rs:4:18: 4:51 error: mismatched types: expected `int`, found `()` (expected int, found ()) practice.rs:4 let y: int = if x == 5i { 10i; } else { 15i; };
終わりが見えてこない
まだ構文の本当に基礎っぽいとこしか触れてないけど、終わりが見えてこないので今日はここまで...
本当はRustのメモリ管理に興味があって、ownershipとかborrowingとかみたいな現代的なメモリ管理について学べそうで興味を持ったんだけど、このペースだとたどり着けないのでまた今度にする。
ownershipは、まだあんま分かってないけどスコープを抜けるときに確保したメモリが解放される仕組みっぽい。それだけだとスタックと変わら無いんじゃないかと思うけど、別のスコープにownershipを移す事ができたり(その場合は元のスコープの所有では無くなる)、別のスコープに所有権を貸し与えたり出来るので、常に1つのスコープに所有されつつ、そのスコープが終了すればメモリの解放が行われる事を保証出来るらしい。
まだ自分でコード書いて試してみてないからアレだけど、説明だけ読んだ感じだとすごく良い仕組みのように感じる。
参考
Ownershipとかについては、このページで紹介されてて興味を持った。
こちらは所有権の移動(move)の説明。
おめでたい話
今日は、プライベートなお話を書く。とてもおめでたいお話。
背景: VOYAGE GROUPでのインターンとリレーブログ
実はこのブログは焼き肉ブログというはてなグループに所属していて、グループ内ではメンバー同士で1日交代でブログを書いていってる。メンバーは皆去年の夏に同じインターン(VOYAGE GROUPのTreasure)に参加した仲間で、ブログを書く習慣をつけたりお互いに知見を交換しあったりする為にリレーブログを始めた。何人かは地方にいる為にそんなに頻繁には会えないんだけど、東京に来る機会があった時は皆で焼き肉を食べに行ったりして、何だかんだで1年以上の付き合いになってた。
そんな中、メンバーの一人であるdorivenが結婚する事になり、まさに今日、都合がついた僕, ism1000ch, moguranosenshiの3人で結婚式に出席してきた。
やっぱり色々思うところはあったので、簡単にだけどまとめてみたいと思う。
続きを読む