読者です 読者をやめる 読者になる 読者になる

Goでインタプリタ実装本を読んで3 evaluator

Evaluationまで読み、実際にインタプリタとして実行出来るようになった。

evaluator

ソースコードをlexerによって字句解析し、parserによってASTを構築した後、evaluatorによって演算を行う。 この章を読む前は、とても面倒くさい実装をしなければいけないのではないか・・みたいな不安があり、最後まで読み進められるか不安だったけど、意外と簡単に実装出来て驚いた。

本書では、tree-working interpreter と呼ばれる、ASTをコンパイルして実行するのではなく、順に辿りながら計算結果を得る手法が解説されている。これは恐らく他のどのアプローチより演算が遅くなるけれど、ホストマシンのOSの違いを考慮しなくてよいし、ポータブルで実装が簡単。とのこと。

オブジェクトを定義する

evaluatorで重要なのは、言語が内部でどのようなオブジェクト(型)を持っているかを定義すること。本書では “internal object” と呼んでいたけど、例えば本書のMonkey言語だと、この時点では以下の型以外は無い。

const (
    INTEGER_OBJ      = "INTEGER"
    BOOLEAN_OBJ      = "BOOLEAN"
    NULL_OBJ         = "NULL"
    RETURN_VALUE_OBJ = "RETURN_VALUE"
    ERROR_OBJ        = "ERROR"
    FUNCTION_OBJ     = "FUNCTION"
)

本書のevaluatorは、ASTを順に読んでいき上記のオブジェクトに変換して演算をするというもの。実際の実装は、AST nodeのタイプを以下のようにswitch caseで読み取り、 Eval() という実行関数を再帰的に呼び出してASTを解決するという感じ。

(以下の例は、読んでもわかるというものでは無いけれど、雰囲気が伝わると嬉しい。)

func Eval(node ast.Node, env *object.Environment) object.Object {
    switch node := node.(type) {

    // Statements
    case *ast.Program:
        return evalProgram(node, env)

    case *ast.ExpressionStatement:
        return Eval(node.Expression, env)

    case *ast.PrefixExpression:
        right := Eval(node.Right, env)
        if isError(right) {
            return right
        }
        return evalPrefixExpression(node.Operator, right)

    case *ast.InfixExpression:
        left := Eval(node.Left, env)
        if isError(left) {
            return left
        }
        right := Eval(node.Right, env)
        if isError(right) {
            return right
        }
        return evalInfixExpression(node.Operator, left, right)

    case *ast.BlockStatement:
        return evalBlockStatement(node, env)

    case *ast.IfExpression:
        return evalIfExpression(node, env)

    case *ast.LetStatement:
        val := Eval(node.Value, env)
        if isError(val) {
            return val
        }
        env.Set(node.Name.Value, val)

    case *ast.Identifier:
        return evalIdentifier(node, env)

    case *ast.ReturnStatement:
        val := Eval(node.ReturnValue, env)
        if isError(val) {
            return val
        }
        return &object.ReturnValue{Value: val}

    // Expressions
    case *ast.IntegerLiteral:
        return &object.Integer{Value: node.Value}

    case *ast.Boolean:
        return nativeBoolToBooleanObject(node.Value)

    case *ast.FunctionLiteral:
        params := node.Parameters
        body := node.Body
        return &object.Function{Parameters: params, Body: body, Env: env}

    case *ast.CallExpression:
        function := Eval(node.Function, env)
        if isError(function) {
            return function
        }
        args := evalExpressions(node.Arguments, env)
        if len(args) == 1 && isError(args[0]) {
            return args[0]
        }
        return applyFunction(function, args)
    }

    return nil
}

Eval() の第二引数である env は、変数をバインドしておくためのmap。関数スコープごとに作られ、親スコープのenvも保持している。 こうすることで、クロージャが表現出来るという寸法。

GCの話

本書のMonkey言語は、GCを実装しないので以下のようなケースに問題になる。

let counter = fn(x) { 
    if (x > 100) { 
        return true; 
    } else { 
        let foobar = 9999; 
        counter(x + 1); 
    }
};

counter(0);

counter() の引数 x が 101 になるまで再帰的に counter() を呼び出している。注目すべきは、 let foobar = 9999; の部分で、これは未使用ではあるがintをアロケートしている(Monkey言語は全ての型が内部的にはオブジェクトであり、都度instanciateしている)。要するにMonkey言語のruntime(evaluator)はGCも無ければfreeも無いのでメモリがリークする。ただ、今回はGoでevaluatorを書いているので、GoのGCがそのまま使える。ホスト言語がGCを持っている場合はGCを実装しなくても実質的にはこの辺りを解決してくれる。本書では、本来であればGoのGCを使わずに何か別の方法を実装すべきであるという警告がしてある。これは言うは易く行うは難し。本書の範疇を超えてしまう。とも。僕はGCについても知見が無い状態なので、こういった話も興味深い内容だった。

感想

本書の主なトピックはこれで読み切ったことになる(lexer, parser, evaluator)。 まだ続きはあって、文字列、配列やハッシュなどの型を追加したり、組み込み関数の実装をしたりする模様。まだ読んでいないけど、これらも基本的には lexer を実装し、それをparseしてASTを構築、実行という流れになるはずなので、なんとなくイメージは湧いている。

理解が間違っているかもしれないけど、例えばここでASTをLLVM IRやJVMで扱える形式に変換するコンパイラを書けば、SwiftあるいはKotlinみたいな感じになるのかな?と思った。LLVMにはGoバインディングパッケージがあるっぽいのでこれを使うとGoで書けるのかもしれない。以下が参考になる。

LLVM IRにコンパイルすることで、Monkey言語で課題になっていた

などの恩恵を受けられそうだなという感じで、LLVMに少し興味が沸いてきた。 この辺のことは一切何も知らなかったので、こういった想像が出来るようになったのは良かった。

ただ他にもやりたいことがたくさんあるのでいつ触るかは未定、、

Goでインタプリタ実装本を読んで2 parser

Parserまで読み終えたのでまとめておく。

Parser

この工程では、Lexerによってソースコードを字句解析した後、それらをよりアクセスしやすい形式に変換する。具体的にはトークンからASTを構築する。

例えば、以下のソースコード

let add = fn(x, y) { return x + y; };

Lexerによって以下の様に字句解析し

{Type:LET Literal:let}
{Type:IDENT Literal:add}
{Type:= Literal:=}
{Type:FUNCTION Literal:fn}
{Type:( Literal:(}
{Type:IDENT Literal:x}
{Type:, Literal:,}
{Type:IDENT Literal:y}
{Type:) Literal:)}
{Type:{ Literal:{}
{Type:RETURN Literal:return}
{Type:IDENT Literal:x}
{Type:+ Literal:+}
{Type:IDENT Literal:y}
{Type:; Literal:;}
{Type:} Literal:}}
{Type:; Literal:;}

Parserによって以下の様なASTを構築する。

{
   "Statements":[
      {
         "Token":{
            "Type":"LET",
            "Literal":"let"
         },
         "Name":{
            "Token":{
               "Type":"IDENT",
               "Literal":"add"
            },
            "Value":"add"
         },
         "Value":{
            "Token":{
               "Type":"FUNCTION",
               "Literal":"fn"
            },
            "Parameters":[
               {
                  "Token":{
                     "Type":"IDENT",
                     "Literal":"x"
                  },
                  "Value":"x"
               },
               {
                  "Token":{
                     "Type":"IDENT",
                     "Literal":"y"
                  },
                  "Value":"y"
               }
            ],
            "Body":{
               "Token":{
                  "Type":"{",
                  "Literal":"{"
               },
               "Statements":[
                  {
                     "Token":{
                        "Type":"RETURN",
                        "Literal":"return"
                     },
                     "ReturnValue":{
                        "Token":{
                           "Type":"+",
                           "Literal":"+"
                        },
                        "Left":{
                           "Token":{
                              "Type":"IDENT",
                              "Literal":"x"
                           },
                           "Value":"x"
                        },
                        "Operator":"+",
                        "Right":{
                           "Token":{
                              "Type":"IDENT",
                              "Literal":"y"
                           },
                           "Value":"y"
                        }
                     }
                  }
               ]
            }
         }
      }
   ]
}

parsingにはいくつか種類があるようだけど、本書ではtop-down parsingと呼ばれる手法の中の1つであるPratt parserが紹介されている。

Pratt parserは、1973年にスタンフォード大学のVaughan Pratt氏による Top down operator precedence という論文によって説明されている手法で、JSLintにも使われている手法とのこと。JSLintの作者である Douglas Crockford氏がJavaScriptでそれを詳しく説明しているブログエントリを見つけた。

本書では、ここで書かれているようなことがGoで書かれている。 今回は、これを実際に実装してみたパーサーをまとめたい。

statement

まずASTを構築するためには、ASTをどのようなデータ構造にするかということを決めておかなければいけない。 本書では、ソースコードをいくつかの塊として扱い、その塊をstatementと呼んでいる。

例えば、

let a = 1;

は、 LetStatementと呼ばれ、以下のようなpseudocodeで表現出来る。

type LetStatement {
    Identifier
    ExpressionStatement
}

変数 Identifier の情報と、式 ExpressionStatement の情報で成り立っている。

また、

return 1;

は、 ReturnStatementと呼ばれ、こちらは以下で表現出来る。

type ReturnStatement {
    ExpressionStatement
}

単に式 ExpressionStatement の情報のみ。

そして、それ以外の全てが ExpressionStatement になる。

1;
1 + 2 / 3;
a * b;
fn(x, y) { return x + y; };

最後の fn(x, y) { return x + y; }; は、ExpressionStatementの中にReturnStatementが存在することを示している。

parserは、ソースコード

  • LetStatement
  • ReturnStatement
  • ExpressionStatement

に変換し、それらの親子関係をツリー状のデータ構造で表現、つまりASTを構築することが目的となる。

ソースコードは、上記3つのstatementがそれぞれ再帰的に繋がったような構造になっていて、これを operator(+,-,/,*,fnなど)の優先度を加味してパースするのが Prett parser の特徴である。

expression

式の優先度を加味してパースする、とは、

1 * -2 + 3;

(1 * (-2)) + 3;

と優先度を付けてASTを構築すること。これを1つのExpressionStatementと呼ぶ。

これをデータ構造で表現すると以下のようになる。

{
   "Statements":[
      {
         "Token":{
            "Type":"INT",
            "Literal":"1"
         },
         "Expression":{
            "Token":{
               "Type":"+",
               "Literal":"+"
            },
            "Left":{
               "Token":{
                  "Type":"*",
                  "Literal":"*"
               },
               "Left":{
                  "Token":{
                     "Type":"INT",
                     "Literal":"1"
                  },
                  "Value":1
               },
               "Operator":"*",
               "Right":{
                  "Token":{
                     "Type":"-",
                     "Literal":"-"
                  },
                  "Operator":"-",
                  "Right":{
                     "Token":{
                        "Type":"INT",
                        "Literal":"2"
                     },
                     "Value":2
                  }
               }
            },
            "Operator":"+",
            "Right":{
               "Token":{
                  "Type":"INT",
                  "Literal":"3"
               },
               "Value":3
            }
         }
      }
   ]
}

式内のトークンが nuds(null denotations)なのか、leds(left denotations)なのかを判定し、式を左辺と右辺に分割していく。

  • nuds
    • 左辺を持たない式(e.g, 1,-,!,fnなど)
  • leds
    • 左辺を持つ式(e.g, +,*,/など)

それらを演算子の優先順位を加味してデータ構造を作り上げていく。 上記例は以下のように定義づけられる。

(        (        1 *  (-2) ) + 3    )
         |        |     |   |   |
         |        |     |   |   |
         |        |     |   |   |
leds(+)(leds(*)( nuds, nuds ), nuds  )

ちなみに演算子の優先順位は以下のように定義している。

const (
    _ int = iota
    LOWEST
    EQUALS      // ==
    LESSGREATER // > or <
    SUM         // +
    PRODUCT     // *
    PREFIX      // -X or !X
    CALL        // myFunction(X)
)

var (
    precedences = map[token.TokenType]int{
        token.EQ:       EQUALS,
        token.NOT_EQ:   EQUALS,
        token.LT:       LESSGREATER,
        token.GT:       LESSGREATER,
        token.PLUS:     SUM,
        token.MINUS:    SUM,
        token.SLASH:    PRODUCT,
        token.ASTERISK: PRODUCT,
        token.LPAREN:   CALL,
    }
)

precedences の下に行くほど優先度が高い。

これを見て僕はparser実装めっちゃめんどいのでは、と思ったけど、Pratt parserではこれをシンプルな再帰関数で解決していてちょっと感動した。そのままのコードを紹介するためには依存する各データ構造まで出さないといけないので割愛。。

ざっくりと言うと、以下のような処理を再帰的に行っている。

  • 1 をパース
    • nudsなのでそのまま左辺へ保存(1, )
  • * をパース(* 1, )
    • ledsなので次の - パース
    • nudsだとわかるのでそのまま 2 をパースして右辺へ -2 を保存(* 1, -2)
    • + をパース
    • * より優先度が低いため、ツリーを戻り ( 1, -2)を左辺へ保存(+ ( 1, -2), )
  • 3 をパース
    • 右辺へ保存(+ (* 1, -2), 3)

ちょっとわかりづらいね・・

感想

これまで、parserと聞くと何やらすごい難解なイメージを持っていたけれど、実際に実装してみるとそれは結構泥臭くて、味わい深いものがあることがわかった。

今回やってみて、parser実装はそのアルゴリズムは面白いけれど、実装は結構退屈な感じだったので、yaccなどのパーサージェネレーターにも興味が湧いてきた。

さて、次はEvaluationを読み進める。ここ結構面倒くさそうな気がしている。

GOでインタプリタ実装本を読んで1 lexer

去年末に購入してからずっと積読状態だった本をようやく読み始めた。 最近、朝活というものにハマっていて、ちょうど良い機会なので少しずつ読み進めている。

ちなみに、僕は英語が得意ではなくて、洋書を読むのにとても時間がかかってしまうのだけれど、とても役立っているのが Kindle for PC。

選択範囲の単語を即座に辞書で引けるので、割りとストレス無く読み進められている。まだ読んでいる途中ではあるけど、得られた知見をまとめておこうと思う。

本書について

Goでinterpreterを実装する過程を説明した本で、内容については @deeeet さんが簡潔にまとまったものを書かれている。

僕は仕事でプログラミングをしているけれど、情報系の大学出身というわけでも、コンピューターサイエンスに明るいというわけでもない。 それはいつしかちょっとした劣等感となっていて、でもそれが僕の技術に対する学びへのモチベーションに転嫁されている気がするのでそれはそれで良い。 そういった文脈があるわけだけれど、ずっとコンパイラインタプリタが一体どのように作られていて、具体的に何をしているのか、という分野にはすごく興味があった。特に、以下の記事を見たときは自分もいつかはやってみたいという気持ちが高まっていた。

本書は、Monkeyという名のインタプリタ言語を実際に実装する過程が書かれており、そういった初心者の欲求を満たすには十分な内容となっているように思う。

本書は主に、

  • Lexing
  • Parsing
  • Evaluation

という構成となっており、今回は途中手を動かしながらLexingを読みきったのでそれをまとめておく。

Lexing

僕はそもそもLexingが何なのかを知らなかったのだけれど、Lexingとは、ソースコードを意味のある最小単位(token)に分割する作業のことで、これをLexing(字句解析)と呼ぶ。 インタプリタソースコードを実行するにあたって、プレーンテキストをよりaccessibleな形式に変換、具体的には、プレーンテキストをAST(Abstruct Syntax Tree)へと変換したい。そのために2段階の変換をするわけだけれど、その1段階目の工程をLexingと呼んでいる。言語によって呼び方は様々なようで、tokenizerと呼んだりscannerと呼んだりする模様。

plain text ---> token ---> AST

そして、実際に字句解析を行う変換器をlexerと呼んでいる。 ちなみに、lexerによって分割されたtokenをASTへと変換するのがparserらしい。今まで聞いたことはあったけどその関係性についてはよく知らなかったのでちょっとクリアになった。

さて、字句解析とは具体的にどのようなものなのか。 以下に簡単な例を挙げてみる。

let x = 5 + 5;

というソースコードがあったとして、それを字句解析して以下のようにする。

[ 
    LET, 
    IDENTIFIER("x"), 
    EQUAL_SIGN, 
    INTEGER(5), 
    PLUS_SIGN, 
    INTEGER(5), 
    SEMICOLON 
]

これつまり、Lexerを作るということは当たり前だけど言語仕様が決まっているということで、もしかするとここが作っていて一番楽しいところなのかもしれないと思った。(逆にEvaluationはつらそうな気がする・・)

Monkey で注目すべきなのは、

  • INTEGERを見て分かる通り型情報がlexing時点で存在する
  • ホワイトスペースは無視する
  • ソースコードのattribute的な情報は無い
    • production readyなlexerを実装する場合はエラーをよりわかりやすくするためにファイル名、行番号、カラム番号などを付与しなければならん
    • これは本書がpractice的な位置づけであるためで、解説を本質的な部分に絞るため敢えて実装していない

こういった言語設計を前提に、ソースコードを解析して意味のある部品に分割していく。

興味深かったのはその実装で、面白いくらいに愚直に書くのだな〜ということ。例えば、部品(token)については以下のように定義し、ソースコードテキストを読み込んでswitch文でガリガリ分割するみたいな。

const (
    ILLEGAL = "ILLEGAL"
    EOF     = "EOF"

    IDENT = "IDENT" // add, foobar, x, y, ...
    INT   = "INT"

    ASSIGN   = "="
    PLUS     = "+"
    MINUS    = "-"
    BANG     = "!"
    ASTERISK = "*"
    ・・・

lexerの本体はこんな感じ。

   ・・・
    switch l.ch {
    case '=':
        if l.peekChar() == '=' {
            ch := l.ch
            l.readChar()
            tok = token.Token{Type: token.EQ, Literal: string(ch) + string(ch)}
        } else {
            tok = newToken(token.ASSIGN, l.ch)
        }
    case '+':
        tok = newToken(token.PLUS, l.ch)
    case '-':
        tok = newToken(token.MINUS, l.ch)
    case '!':
        if l.peekChar() == '=' {
            ch := l.ch
            l.readChar()
            tok = token.Token{Type: token.NOT_EQ, Literal: string(ch) + string(l.ch)}
        } else {
            tok = newToken(token.BANG, l.ch)
        }
    case '/':
    ・・・

雰囲気が伝わると嬉しいんだけど、コンパイラってなんかすごそう(笑)みたいな感じだったけど、考えてみればなるほどこうなるなという感じ。面白い。

本書はこれをテストドリブンで実装していくという特徴があり、それも達成感があって良い。

Lexingの章の最後に実装したlexerを使って簡単なREPLを実装する。入力したソースコードが字句解析されて出力される様を見るとちょっとおおおって感じで楽しい。

次はparserを読む。

phperがgolang書いてて調べたこと

配列のループ

<?php

foreach ($items as $index => $item) {

}
for index, item := range items {

}

まぁ違和感はない。

package

Goji使ってサーバー書いてると、コントローラーやモデルをpackage分割したくなる。例えば以下のようなディレクトリ構造にしたい。

app.go
controllers/
    helpers/
        helper.go
    user_controller.go
    item_controller.go
models/
    user.go
    item.go

この時、packageをPHPでいうところのclassと捉えるとうまくいかない。 例えば、

package user

type User struct {
    Id   string,
    Name string,
}

と定義した場合、user.goファイルは以下に設置しないといけなくて冗長になる。

models/
    user/
        user.go

さらに、import時も以下のように冗長になる。

import(
    "github.com/hatajoe/json-server/models/user"
)

なので、packageはnamespaceと捉えると良いかもしれない。

package models

type User struct {
    Id   string,
    Name string,
}
models/
    user.go

models/item.goは、

package models

type Item struct {
    Id   string
    Name string
}

となる。importは

import (
    "github.com/hatajoe/json-server/models"
)

だけでmodels以下のstructを参照出来るようになる。

尚、同じパッケージ内ならファイルが分割されていようとも相互に参照出来る。全て1つのファイルに宣言されていると思って構わない。 これを利用して、同パッケージ間で共通して利用するルーチンはmodels/common.goみたいに分けて使うと便利。 ちなみに、同パッケージ内で同じ名前の関数やグローバル変数は宣言出来ない。(型が違えば出来ても良いと思うんだけどなー。)

func Register(uuid string) error {

}

// 以下は引数が違うけど名前が同じなので宣言出来ない。
//func Register(uuid string, name string) error {
//}

また、ネストしたディレクトリの場合は、そのファイルが所属するディレクトリの名前をpackage名とするみたい。

app/
    controllers/
        helpers/
            helper.go
package helpers

import(
    "net/http"
)

func GetUUID(r *http.Request) string {
    return r.Header.Get("UUID")
}

importする場合は、

package controllers

import(
    "github.com/hatajoe/json-server/controllers/helpers"
)

気がついたかわからないけれど、自作packageをimportする時に、github.com/hatajoe/json-server/...としてる。 実は、以下のように相対パスでもimport出来る。

package controllers

import(
    "./helpers"
)

しかしこうすると、go test時にパッケージ依存が解決出来ずテストが出来なかった。(参考 http://qiita.com/shiwork/items/a213b1af2da7f7f8ea22#整理)

$GOPATH配下に存在するプロジェクトの場合は相対指定だとダメらしい。 プロジェクトを$GOPATH以下に置くかどうかをチームで共有しておかないと$GOPATH以下にシンボリックリンクを貼るハメになる人が現れる。(逆にシンボリックリンクで解決出来るっちゃ出来る)

エラーハンドリング

1つの関数内でエラー処理をたくさんする場合、

func ComplexFunc() error {
    err := hoge()
    if err != nil { // 型推論してる
        // error
    }
 
    err = fuga()
    if err != nil { // 上で型が決定してるので型推論できない
        // error
    }
 
    v, err := foo() // vの型推論をしてる。が、errをしてるようにも見えちゃう。ややこしい。
    if err != nil {
        // error
    }
 
    log.Println(v)
 
    return nil
}

同じ変数名err型推論は同じスコープ内で一度しか出来ない。んだけど、複数の戻り値を受け取る際、片方の型が決定していなければ型推論出来る。ように見える。これは、以下のようにif文のスコープを利用するとうまく出来る。

func ComplexFunc() error {
    if err := hoge(); err != nil { // errはこのif文ブロック内がスコープとなる
        // error
    }
 
    if err := fuga(); err != nil { // 綺麗に書ける
        // error
    }
 
    v, err := foo() // 逆にここはvをif文スコープ内に入れるわけにはいかないのでこのままとなる
    if err != nil {
        // error
    }
 
    log.Println(v)
 
    return nil
}

if文のスコープ。面白い。 じゃあ複数の値を返す関数を2つ以上使ってる場合はどうするの、ってなるけど、その場合は予めvar err errorを宣言しとくとかなのかな。

抽象化

モデルには大抵プライマリキーでデータを取得する以下のような関数がある。

<?php

class User extends BaseModel
{
    protected $table = "user";
    
    public static function findById($id)
    {
        return DB::where("id", $id)->get()->first();
    }
}

これは、BaseModelに宣言すれば良いじゃない。

<?php

class BaseModel extends Eloquent
{
    protected $table;
    
    public static function findById($id)
    {
        return DB::where("id", $id)->get()->first();
    }
}

こんな感じに書けたと思う。 goだと型の都合上これが出来ない。そもそも継承もない。

僕はシンプルにテーブル構造からモデルクラスを自動生成するツールを書いて対応してる。 長らくPHPとか書いてるとなんとか共通化したくなる部分だと思う。多分方法はいくつかあるんだろうけど、あまり複雑なことはしたくなかった。 簡単に出来る方法があるなら教えて欲しいです。

困ったとき

  • godoc
    • 関数リファレンス頼れる
  • github
    • READMEを読む
    • examplesがあれば読む
    • testを読む

とにかく英語多い。それが辛い・・・!

# React.jsをちょっとだけ調べたので残しとく

うちの奥さんが珍しく子どもと出かけたのでこのチャンスを利用して話題のReact.jsを調べてみた。 前に pocket に突っ込んでいた記事を手がかりに開始。

上記記事によると、今から学ぶには以下が良いそうなので、素直に読む。

まだ1行もコード書いてないけど、把握したものを残します。

Component

React.jsは、コンポーネントと呼ばれる単位でオブジェクトを実装する。

  • 以下のサンプルコードの var CommentBoxコンポーネント
  • コンポーネントは、必ず1つの <div> を戻り値として返す。
  • ネストした <div> ならOK
  • JSの中に普通にHTMLが記述されているように見えるが、これはJSX
  • ようするにこのコードはコンパイルしないと動かない(JSXを使わない書き方も出来るがかなり冗長なので微妙な気がした)
  • 以下のサンプルコードだと、React.render で、 #contentコンポーネントが描画される。
  • コンポーネントはDOMの様に見えるけどこれは仮想的なもので、JSがメモリ上にDOMツリーを持っていて React.render するまで描画はされない。
  • 変化したDOMだけを再描画出来るためハイパフォーマンス。これはReactが勝手にやってくれる。
  • これらのコンポーネントを組み合わせてページを構築していく
  • とても再利用性が高い仕組み。ただし設計をミスると今よりつらいかもしれない。
// http://facebook.github.io/react/docs/tutorial.html
var CommentBox = React.createClass({
  render: function() {
    return (
      <div className="commentBox">
        Hello, world! I am a CommentBox.
      </div>
    );
  }
});
React.render(
  <CommentBox />,
  document.getElementById('content')
);

PropとState

Propとは、Componentに外から渡せるImmutableなデータ。 サーバーから取得したJSONデータだと思っていいと思う。

Stateとはその逆で、Component自身が管理しているMutableなデータ。 フォームへのユーザー入力などが該当すると思う。

これらはどちらも直接変更することは出来ない。変更は、setProp, setState replaceProp, replaceState メソッドを使う。 上記メソッドを呼び出した場合、コンポーネントは再描画される。 つまり、これによりDOMと仮想DOMのデータ整合性を取っているんだろうと思う。

Immutableと言いつつPropには変更を許容するI/Fが定義されている。 これはちょっと納得行ってない。まぁコンポーネント内で、

  • これはCompornent管理だから変えてもOK
  • これは外からもらったものだから変えちゃダメ

という風にわかりやすいのは良いことだと思った。

コンポーネント間のやりとり

こっちのコンポーネントでの操作をトリガーし、あっちのコンポーネントでフックする、という話。 これは、コンポーネント間で親子関係を作っておいて、

  • 子のイベントを親がフックして別の子へ
  • 親でStateを管理し、子のPropとして使う

みたいな感じらしい。わかりやすくて良い。

その他

などなど色々ある。理由は後述するが調査はここまでとした。

調べてみて

調査前の印象として、HTMLとJSの密結合って実際どうなんだ、というのがあった。ただ実際はコンポーネントという再利用性の高い仕組みによってDRYが実現可能で、より見通しの良いコードになるなーという印象に変わった。これは良い。

ただこう来ると、CSSコンポーネントに含めてしまいたくなった。 それについて少し調べてみると、以下の記事が見つかった。

CSSをComponentのPropとして定義出来るようにすればいいじゃん!的な内容だった。将来的には含まれるかもしれない。そうなったら本格的に触ってみようという気持ちになり、調査はここまで。 楽しかった。

おまけ

調査の過程をTwitterでつぶやいてたので貼っとく。

composerで自作ライブラリをインストールしたくて

したくて。

共通の処理ってあるじゃないですか。 Webアプリケーションの場合大抵のロジックはモデルに集約されていると思うんですけど、モデル間で共通のロジックってのはやっぱりあって。

ただ、多くのWebアプリケーションフレームワークって、一応ディレクトリ構造が決まっていて、大体こういった共通の処理を抜き出して置いておくいい感じのところが無いんですよね。 だからなんかルートディレクトリに Library とかディレクトリ作って云々... これがもうめちゃ微妙で、気づいたら Library 以下のソースにプロジェクト依存したコードが紛れてやれやれって感じに仕上がる。 なので、composer使いたくなる。 で、自作のライブラリをcomposerでインストールするのに3時間くらいハマったのでイライラしながらこれを書いてる。

結論から言うと、composerでライブラリをインストールするには以下の手順が必要になる。

後述と書いておきながらいきなりPackagistの話する。 結局は、Packagistに登録しなくてもうまくやる方法はあるらしい。 ただ、今回はちょっと調べきる時間が無かった(こんなものを書くくらいなら調べればいいんだけどもうちょっとどうでも良くなってきてる)。 composerのautoloaderの仕組みを良くわかっていないという点なんだと思うけど、Packagistに登録していない段階ではautoloadされず使えなかった。(ちなみにWAFはLaravel) これに数時間悩み、なんでやねんってなってたけど、Packagistに登録するとすんなり行けた。(vender/composer/autoload...にうまく追加された。ソース読んでないので理由はわからない。) ...まぁ取り敢えず目的は果たせたし、また思い立った時にソース読む。

で、意図せず自作ライブラリを公開してしまったわけだけど、これはこれでなんというかなかなか良い体験だった。というと素晴らしいライブラリが公開されたかのようだけど、内容はどうでも良くて(実際、メソッドは2つしかないしゴミみたいなライブラリで、、)

  • 共通処理を抜き出す試み
  • ドキュメントを正しいのかどうかもわからないけ英語で書いてみるチャレンジ
  • ゴミみたいなライブラリでもcomposerで自作ライブラリがインストールされる様を見るとちょっと嬉しい

こういったものが体験出来た。 ちなみにリポジトリは以下。(ホントは見せるのも恥ずい。。んだけどまぁいいや。。) https://github.com/hatajoe/php-easy-lottery

まぁ取り敢えず、ゴミだろうがなんだろうが、今後も作ったものは表に出して行くという行為をしてみようかなと思った。

如何にしてスクラムを組むに至ったか

プログラマタスクの進捗管理って難しいじゃないですか。 ずーっと難しいなーって感じで特に何もしてこなかったので、何かこう出来ることは無いかと考えていて。 すると、進捗管理の難しさってのは、言ってしまえば以下の2点が問題だという結論に至った。

  • 工数見積が甘い
  • 個々人の能力が把握できていない

つまり、妥当性を欠いた工数上で能力不一致な人員アサインが行われ、結果、遅延する。 しかし、正確無比な工数見積や、個々人の能力把握は不可能だと思われるので、悩ましいなーと。

そこで、取り敢えず、基準値があっての実績値だと思った僕は、工数、能力の両側面における基準値とはなんぞやと考えた。 組織内で個々人の能力を基準にすることは割りと難しいので、工数というものを基準に出来ないかと考えた。工数、すなわち時間は誰にとっても同じはずなので、基準になり得るなーと。

ただ、例えばある機能があるとして、

  • Aさんは8hで見積もって7hで終わらせた
  • Bさんは4hで見積もって6hで終わらせた

みたいなことが往々にしてあって、工数を基準にするにしても人それぞれみたいな感じになってなんだかなーって感じがあった。

ただよく考えてみると、AさんとBさんは見積もり時に倍近くあったものの、実績値としては1hくらいの差であると。これは都合の良い例なんだけど、要するに、見積もり時にAさんが懸念していた点、あるいはBさんが軽視していた点をマージ出来たら、AさんBさんによってその機能の工数は6hという共通の見積もりを弾き出せるはず、つまり、チーム内の基準値作れるよねという理論が成り立つんじゃないかと。 というかこれが見積もりポーカーなんだ、という感じで。 こうすると、タスクをチーム内の基準値として利用でき、そのタスクをどれだけ捌くかで個々人の能力というものもある程度見えてくるかなと。

具体的にはGitHubマイルストーンにイシュー紐付けて、各イシューにタスクの重み(pointと呼んでる)をラベルで付与する。最初は大体1point=3hくらいの換算でやってるけど、実績値によって point:時間 の関係は変動するので多分変わってくだろうな。 ここまで準備すると、AppNeta: Burndown before you burn outを使うことでバーンダウンチャートも作ることが出来る。

最終的には、

という感じに仕上がった。これをスクラムとしてちょっと踏ん張って結果出したい。