NAKKA-Kの技術ブログ

技術に関する知見や考え方などを投稿します。

「ゼルダの伝説 ブレスオブザワイルド 」のUXについて考察しました

Nintendo Switch版「ゼルダの伝説 ブレスオブザワイルド 」(通称BoW)のUXについて考察してみました。

このゲームには素晴らしい仕組みや面白さなど多くの要素がありますが、今回話す内容はその中でもトップクラスに素晴らしい要素について書きたいと思います。

導入

BoWのもっともUXが高いと思う点が3つあります。

  • ほぼロードがない
  • スートーリーを意識しなくても良い
  • システム上の都合をユーザーに意識させていない

これらの要素は全て、ユーザーに対して開発者の問題やシステムの押し付けをしていないという共通点があります。

ほぼロードがない、とは

BoWの世界はオープンワールドになっており、エリア間の移動にロードがありマップが切り替わるという仕組みではなく、エリア同士がフラットで移動の際に遮るものはありません。

他のオープンワールドゲームにありがちな問題として、マップを高速で移動すると読み込みに時間がかかったりカクついたりすることがよくあります。 しかし、BoWでは殆どマップ読み込みのカクつきに出会ったことはありません。 (唯一例外として桃白白という特殊を実践した時、ごくごく稀にマップ読み込みにコンマ何秒か要する場合があります。)

スートーリーを意識しなくても良いこと、とは

オープンワールドではないゲームでは大体の場合、ストーリーを進めるごとにマップが解放されて先に進めるようになります。 そのためストーリーとしては自然に進行します。

オープンワールドのゲームでは自由にマップを歩けるため、ストーリー上先のマップにも行くことができます。 ですがストーリーの流れは限定されているため、ストーリーを進めるのが面倒になるということが多発しやすいです。

対してBoWではオープンワールドで自由にマップを歩ける上に、ストーリーすら自由に進められます。 そしてストーリーの進め方によってセリフやイベントが大きく変化します。 ストーリーの変化すら楽しんでプレイできます。

ちなみに私はプレイ開始直後、ラスボスに直行し頑張ってクリアしました。控えめに言って楽しいです。

システム上の都合をユーザーに意識させていない、とは

BoWはオープンワールドでマップ上で倒された敵やアイテムもそのままになります。 そのためシステム上ではその変化を保存するために多くの容量を使用します。 永遠に保存し続ければいつか容量が足りなくなってしまう場合もあります。
その上、ステージ上のアイテムが枯渇してユーザーが楽しめなくなるという場合も発生してしまいます。

それを解消するには多くの場合メタな表現(ゲームの都合で押し通すなど)をする必要があるわけですが、BoWでは「赤い月の夜」というゲーム上自然な内容によってそのシステムの容量を解放しています。

おそらく多くの一般人は裏でシステム容量の解放が行われていることにまったく気がつかないでしょう。 システム上の都合をユーザーに一切意識させることなく、ゲームシステム的にも自然に処理しています。 逆にその処理がユーザーへの面白さ提供にもなっているというのが非常に素晴らしい点です。

まとめ

開発やシステムの都合をユーザーに押し付けることほどユーザーの興を削ぐものはありません。 BoWはこれらの徹底が非常に高い水準で維持されています。 自分たちの作るゲームに大いなる愛を持って開発しているという点が伺えます。

ゲームに限らず、Webサービスなどの開発においても自分たちのサービスに対する愛を持って開発しましょう。

プログラマーの学習方法は1つを極めるべきなのか、広く学ぶべきなのか?

プログラマーの学習方法はどうするのが一番良いのでしょうか?
ある人は1つに決めてひたすら習熟し、その後に別の技術に手を出すべきだ。と言います。
はたまたある人は1つだけを学んでいても意味がないから、幅広く学ぶべきだ。と言います。

どっちだよ!!って思いますよね。

この記事を書こうと思った最初の理由はこのツイートでした。

うんうん、その通りだと思います!

ですが、1つを極めた場合とあれこれ手を出した場合は何がどう違うのでしょうか?

2つの学習方法の違い

ここでは便宜上、「1つを極めた場合」を「特化型」、「広く学んだ場合」を「汎用型」と呼称することにします。

では早速、具体的にどのようなスキルマップになるのかグラフの例を見てみましょう。 各スキルの合計値はどちらも同じにしてあります。

特化型のスキルマップ
特化型のスキルマップ

汎用型のスキルマップ
汎用型のスキルマップ

これらのグラフを見ると、やはり特化型の方が1つのステータスに全振りできるので圧倒的に即戦力になりそうですね。 汎用型は全体が平均化してしまうので特化型の分野においては絶対に勝てそうにないです。

やはり特化型の一強に見えます。

筆者の学習方法

この流れで言うと「お前バカかよ」って言われそうですが、実のところ筆者はガチガチの汎用型です。

やりたいことがありすぎて気がついたら汎用型になってしまった典型パターンです。 ですが、おそらく自分には汎用型の方が向いていたのだと思います。

特化型より汎用型の方が向いていた話も出てきたところで両方のメリット・デメリットを考えてみましょう。

特化型

メリット

名前の通り特化できるので極めた分野においては無類の強さを発揮します。

  • この問題を得意な言語で解いてください、と言われたら特化分野を武器に戦えます。
  • 仕事でも特化分野の強さを生かしてリードできるかもしれません。
  • 分野が決まっているので仕事が選びやすいです。
  • 一つ極めたことで、似たような他の分野も学びやすくなります。
  • あれもこれもと多くの分野を学習する必要がありません。

デメリット

名前の通り特化しているので他の分野が弱いです。

  • この技術でやってください、と言われてもやったことないものが多いので最初から学ぶ必要があります。
  • 他の分野のことが全然分からなかったりするので、仕事でのコミュニケーションが大変です。
  • 特化分野以外の仕事は選び辛いです。
  • 他の技術をやると考え方の違いが大きすぎて、取り掛かりが大変だったりします。
  • 初心者の3年間は一本特化で行くぞ!と決めてしまうことによって、視野が狭くなって特定分野しかできなくなる場合があります。

汎用型

メリット

特化型とは違い幅広い分野を学んでいるため視野の広さで強さを発揮します。

  • いろんな分野を知っているので話せる技術の話が多いです。
  • 分野にこだわらず仕事を探せます。
  • 仕事をするときに自分の知っている分野が多いため、他分野の人(エンジニア以外も含め)とも簡単にコミュニケーションが取れます。
  • 多くの分野に触れてきたので、新しいことでも弊害なく受け入れることができます。
  • その時に応じた非常に柔軟な行動ができます

デメリット

分野ごとの力量が平均化しているので、自分の代名詞と呼べるこれぞという分野が生まれづらいです。

  • 特定分野のめちゃくちゃニッチな話にはついていけません。(EB3E90が~、なんて言われてもOSの細かい話にゃついていけないよ!となるわけです。)
  • この技術の経験が何年以上の人を募集する。と言われると辛い。
  • 自分が何の分野の人間だったか忘れそうになります。
  • 新しいことでも弊害なく受け入れすぎた結果、やりたいことが増え続けます。
  • 柔軟性が高すぎて、柔軟性の低い人が何故そんなに柔軟性がないのか分からなくなったりします。

汎用型の真の力

汎用型にはまだ見ぬ真の力があります。変身を残しているのです。

汎用型には特化型に絶対負けない数があります。
これらを抽象化することが重要なのです。

浅く広くを上手くやるには、それぞれの技術を抽象化して繋げる必要があります。 上手く抽象化して技術の掛け算を発生させると、広く浅くやってるのに気が付いたら深いところまで理解していたりします。

一つの技術を突き詰めてやって行くと何度も成長の壁(学習曲線の横ばい部分)に当たり、この壁に当たっている間は辛く苦しい期間が続きます。 複数の分野を適切に並行して学習していると、成長の壁を別分野から迂回してすんなり通過することが多々あります。 これを上手く使えば成長の壁を意識することなく永遠に成長し続けることができます。

学習曲線グラフ
学習曲線グラフ

この抽象化、迂回が上手くできれば触った分野が多くなるほど情報が集積され、既存分野の底上げがされます。

グラフにするとこうなります。

汎用型の真の力を発揮した後のスキルマップ
汎用型の真の力を発揮した後のスキルマップ

ですが、この道を進むには生半可な気持ちでは難しいです。

汎用型を突き詰める場合にやること

汎用型を突き詰める場合にやること・大事にすることは大きく分けて3つあります。

1つ目に、多くの分野を学ぶということは、それだけやった内容を忘れていく可能性が高いと言うことです。 前提条件として学んだ内容を可能な限り自分の血肉として蓄える必要があります。
すでにやった内容を忘れないようにSNSやブログ、メモにアウトプットをすることは非常に重要です。 そしてやった内容をただアウトプットするのではなく、この段階で他の技術と抽象的に結びつけた自分の見解を加えることで抽象化の能力を高めることにもつながります。

2つ目に、ある分野を特化しないので、意図的に様々な分野を学び続ける必要があります。 もちろん特化型も学び続ける必要がありますが、それ以上に汎用型は学びを止めた時点で価値が下がります。
特化して鍛えた技術は古くなっても一定の需要がありますが、汎用的に鍛えた技術は古くなるとあまり価値が高くありません。

3つ目に、偏見や固定概念を持たずに新たな概念や技術をどんどん取り入れることが大事です。 自分の知らない考えを柔軟に取り入れ、ダメな考えは反面教師にし、自分を高め続けましょう。

柔軟な考えとは時に一般人が考えもしないようなところすら学びに転換できる能力も含まれていると思っています。 以下の記事は独自の発想転換で無理やり学びに繋げたエピソードを記事にしたものです。

nakka-k.hatenablog.com

まとめ

汎用型を突き詰めるには相当の努力と覚悟が必要だと思います。 そして何でも楽しんで学べる人でないと向いていません。

多くの人には特化型をおすすめします。 そして特化型は単純に強いです。

私自身、特化型に憧れもありますのでどちらが良い、悪いということもありません。 自分にあった道を選んでも良いんだということと、道を選ぶ際の基準を知っていただければ嬉しいです。

「HeadFirstデザインパターン」のObserverパターンをGo言語で実装してみた

「HeadFirstデザインパターン」ではJavaを用いてデザインパターンの実装が解説されています。 これらのデザインパターンをGo言語で実験的に設計し直した実装を紹介します。

前回はStrategyパターンを実装しました。

nakka-k.hatenablog.com

Observerパターンとは

簡単な概要だけを説明すると、

親となるインターフェースと子となるインターフェースがあり、子は親のグループに入ったり出たりできます。 そして、親はグループ内の子に対して任意のタイミングでメッセージを送ることができるパターンです。

本に出てくるObserverパターンのイメージ
本に出てくるObserverパターンのイメージ

実装

今回のコードは全てGitHubのリポジトリにありますので、全体のソースコードはそちらからご覧ください。

まずはObserverの親となるインターフェースと子となるインターフェースを定義します。

package main

// 親となるインターフェース
type Subject interface {
    RegisterObserver(o Observer)
    RemoveObserver(o Observer)
    NotifyObservers()
}

// 子となるインターフェース
type Observer interface {
    Update(temperature, humidity, pressure float64)
}

では次にSubjectの実装を見ていきましょう。

package main

// Subjectインターフェースの実装
// グループに入った子を保存するようのObserver配列を持つ必要があります。
type WeatherData struct {
    observers   []Observer
    temperature float64
    humidity    float64
    pressure    float64
}

// コンストラクタ
func NewWeatherData() *WeatherData {
    wd := &WeatherData{}

    wd.observers = []Observer{}
    return wd
}

func (wd *WeatherData) RegisterObserver(o Observer) {
    wd.observers = append(wd.observers, o)
}

// (Go言語にはスライスの要素削除関数はないので自前で実装する必要があります)
// (実戦で書くときはもう少しちゃんと書きましょう)
func (wd *WeatherData) RemoveObserver(o Observer) {
    observers := make([]Observer, len(wd.observers)-1, len(wd.observers)-1)
    for _, obs := range wd.observers {
        if obs != o {
            observers = append(observers, obs)
        }
    }
    wd.observers = observers
}

// ここでグループに入っている子に対してメッセージを一斉送信します
func (wd *WeatherData) NotifyObservers() {
    for _, obs := range wd.observers {
        obs.Update(wd.temperature, wd.humidity, wd.pressure)
    }
}

func (wd *WeatherData) MeasurementsChanged() {
    wd.NotifyObservers()
}

func (wd *WeatherData) SetMeasurements(temperature, humidity, pressure float64) {
    wd.temperature = temperature
    wd.humidity = humidity
    wd.pressure = pressure
    wd.MeasurementsChanged()
}

func (wd *WeatherData) GetTemperature() float64 {
    return wd.temperature
}

func (wd *WeatherData) GetHumidity() float64 {
    return wd.humidity
}

func (wd *WeatherData) GetPressure() float64 {
    return wd.pressure
}

ここで重要なのが、構造体にObserver配列を持っている点です。 その配列に登録されたObserverたちを保存しておく必要があります。 この配列を管理することがSubjectの大きな仕事になります。

ついでにGo言語のインターフェースの仕様について補足しておきます。 Observer等のインターフェースは値渡しになっているように見えますが、内部にポインタを持っているので構造体とは違って参照渡しになっています。

次はObserverインターフェースの実装を見ていきましょう。

package main

import "fmt"

// Observerインターフェースの実装
// ここではWeatherData(Subjectインターフェース)の参照を持っていますが、実際は絶対に必要というわけではありません。
// しかし子の方から自由にグループ離脱ができて便利なため、親の参照を持つ場合が多いです。
type ForecastDisplay struct {
    currentPressure float64
    lastPressure    float64
    weatherData     *WeatherData
}

// コンストラクタ
// メンバ変数の初期値はコンストラクタ内でしています。
// そして自分自身をSubjectのグループに登録しています。
func NewForecastDisplay(weatherData *WeatherData) *ForecastDisplay {
    fd := &ForecastDisplay{}
    fd.currentPressure = 29.92

    fd.weatherData = weatherData
    weatherData.RegisterObserver(fd)
    return fd
}

func (fd *ForecastDisplay) Update(temperature, humidity, pressure float64) {
    fd.lastPressure = fd.currentPressure
    fd.currentPressure = pressure

    fd.Display()
}

func (fd *ForecastDisplay) Display() {
    fmt.Print("Forecast: ")
    if fd.currentPressure > fd.lastPressure {
        fmt.Println("Improving weather on the way!")
    } else if fd.currentPressure == fd.lastPressure {
        fmt.Println("More of the same")
    } else if fd.currentPressure < fd.lastPressure {
        fmt.Println("Watch out for cooler, rainy weather")
    }
}

ここで気をつけることはほとんどありませんが、自分が登録したグループの親であるweatherDataの参照を保存している点は要注意です。 ここは求められる仕様によって変えていっても大丈夫だとは思いますが、多くの場合は持っていた方が良いと思います。 なぜならコードが単純になりやすいからです。

コンストラクタについて補足すると、Javaと違ってオブジェクトが全て参照渡しではないので明示的にWeatherDataのポインタを渡すようにしています。 そして自分自身をSubjectのグループに追加する処理も走らせています。

まとめ

Observerパターンの一番の重要点は、情報が一方向にしか流れず、双方向に同期を取る必要がない点です。 Subjectが情報を一元管理し、変更した時点で子に対して通知するだけで後はよしなに、という簡素な関係です。

もしこれが同じ階層のオブジェクト同士でいろんな情報を相互にやりとりしていれば、情報の扱いが非常に面倒なことになってしまいます。

そしてもちろん、SubjectインターフェースとObserverインターフェースに対して実装することでコードを綺麗に保つことも重要です。 HeadFirst本でも書かれていますが、JavaにはObserverパターンを実装するためのパッケージが存在します。 これはインターフェースではなく、継承という形で実装されます。 そうなると拡張性や再利用生が失われかねませんので、やはりインターフェースで実装できるときはインターフェースで実装するようにした方が多くの場合良いでしょう。

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

この本のデザインパターンを順次試していく予定なので、次回もどうぞご覧ください。

「HeadFirstデザインパターン」のStrategyパターンをGo言語で実装してみた

「HeadFirstデザインパターン」ではJavaを用いてデザインパターンの実装が解説されています。 これらのデザインパターンをGo言語で実験的に設計し直した実装を紹介します。

Strategyパターンとは

簡単な概要だけを説明すると、

複数の振る舞いをインターフェース化し、特定のクラスにメンバ変数として持たせます。 それにより特定のクラスの内容を変更することなく、動的に振る舞いを変えることができるパターンです。

想定

複数種類の鴨を実装します。 それぞれの鴨が、飛んだり飛ばなかったり、鳴いたり鳴かなかったりします。

これをもし鴨という親クラスと鴨A・鴨Bと継承して、それぞれに飛ぶ処理や飛ばない処理を実装するのが単純な方法かもしれません。 オブジェクト指向を始めたばかりに多い実装だと思います。

ですがそれでは同じ飛ぶ処理を持つ鴨が出てきて、実装が重複する可能性が高くなります。 また、動的に変更することもできません。

これをStrategyパターンを使って実装するという想定です。 詳細を知りたい場合は書籍を読んでください。

Go言語での実装

今回のコードは全てGitHubのリポジトリにありますので、全体のソースコードはそちらからご覧ください。

飛ぶインターフェースを実装した振る舞い構造体を用意します。

同じく鳴くインターフェースを実装した振る舞い構造体を用意します。

鴨の実装は少し複雑です。

duck構造体は、全ての鴨が持つべき共通処理と振る舞い構造体(*Behavior)を持っています。 この構造体は抽象クラスのように使うつもりなのでprivateで定義しています。 具体的な鴨はDuckインターフェースを実装し、型の統一をします。

具体的な鴨の実装であるMallardDuck構造体に、duck構造体を委譲します。 これによりクラスの継承に似た状況を作り出します。

鴨たちが持つ飛行・鳴き声の振る舞い構造体は実行時に生成し、それぞれの鴨に渡しています。 そして、Duckインターフェースを実装しています。

これが鴨Strategyパターンの動作イメージです。 Duckインターフェースを実装した鴨たちのコンストラクタ関数を呼び出して配列を初期化します。 それをループで回して正常に動くことを確認するコードです。

まとめ

飛んだり鳴いたりする振る舞いを抽出し、鴨の共通構造体に持たせる。 具体的な鴨に共通構造体を委譲し、インターフェースを実装する。

これにより振る舞いは柔軟に変更でき、実装の重複が少なくなります。 そしてインターフェースへプログラミングすることによって自由な取り回しが可能です。

この実装が実戦で使えるかと言われるとわかりませんが、設計とはカスタマイズするためにあるのでいろいろ試してましょう。

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

Head Firstデザインパターン ―頭とからだで覚えるデザインパターンの基本

この本のデザインパターンを順次試していく予定なので、次回もどうぞご覧ください。

nakka-k.hatenablog.com

参考

Strategy パターン - Wikipedia

「Go言語でつくるインタプリタ」を読んでインタプリタの身近さをを確認した

「Go言語でつくるインタプリタ」は、そこらへんのネットに転がっている超軽量なRubyで作ったLispインタプリタほど小さくなく、重厚な理論に関する論文や書籍のような難しすぎるものでもなく、インタプリタがどのように動作するのか理解するために十分で適量のコードと解説が書かれた本だ。
この本を読み終わった頃には、コンピュータがプログラミング言語を解析して評価するための基礎知識が得られるだろう。

Go言語でつくるインタプリタ

Go言語でつくるインタプリタ

インタプリタ

インタプリタとは、テキストを受け取って、解析し、評価し、結果を出し、これを繰り返す。

インタプリタの実装についてだが、本当に簡単なインタプリタなら30分〜1時間程度で作れるものもある。(BrainfuckLispなどが簡単な部類だ) もちろん上等なものを求めればキリがなくJITインタプリタなるものもあったり、何年かかるか分かったものではない。

この本のインタプリタは1日1時間ずつやれば1ヶ月くらいでできる程度のちょうど良いものだ。 コード自体も難解で困り果てるようなものは存在せず、多少頭をひねるところがあるといった程度だ。

この本の流れとしては、字句解析、構文解析、抽象構文木、内部オブジェクトシステム、評価、データ構造や組み込み関数の拡張、マクロシステムの追加、といった順番でインタプリタが出来上がっていく。 使用言語はGo言語で、全てのコードはTDD(テスト駆動開発)で進められる。

完成するインタプリタで使えるコード例を示す。 ここには変数、関数、高階関数クロージャー、if式、マクロ定義、マクロ呼び出し、などなど様々な要素が含まれているが、これでもまだまだ一部だ

字句解析

字句解析というのは非常に単純明快だ。

1文字ずつ読んでいき、このインタプリタ言語(以下monkey)で決めた字句の識別子(トークン)を設定していくだけだ。 monkeyでは空白に大した意味を持たせないので、字句解析時に空白は全てスキップする。

let five = 5;

例えばこのプログラムを字句解析すると、

token.LET, token.IDENT, token.ASSIGN, token.INT, token.SEMICOLON

という風にトークンを設定していく。 あとはこれらのトークンを増やしていき、言語仕様で許される限りのトークンを用意していけば良い。

構文解析

構文解析とは字句解析で設定したトークンらに構文としての意味を持たせていくものである。 monkeyでは抽象構文木(AST)を用いて構文を表現する。

let文(let ident = 1 + 1)のASTイメージはこんな感じだ。

let文のAST例

let文が存在するだけのプログラムのASTだ。 Programというのが原点になっていて、その下にlet文のノードがあり、letが持つ名前と値それぞれから変数ノードと式ノードが連なっている。

monkeyのシステム内で、ASTのノードはあるインターフェースを満たす構造体で表現されている。 プログラムで表現される文や式には全て構造が決まっている。 その構造をASTのノードという形にひたすら変えていく作業だ。

評価

monkeyコードの全容はすでにASTに収められている。 あとはこれらに意味付けをし、命を吹き込んでいくことでmonkeyは本当の意味で動き出す。

ASTを使って評価をしていくわけだが、この評価の仕方によって性能や移植性、実装難度が大きく変わってくる。 今回はASTのノードを巡ってそのまま評価するtree-walking型インタプリタと呼ばれる方式で評価する。 実際に世間でよく使われている言語ではここで最適化やバイトコードへの変換など様々なことが行われたりする。

ここでは値や式をオブジェクトとして扱っていく。 Evalと呼ばれる巨大な評価器を拡張し、ノードを順番に巡り式を評価し、そのまた中にある式を評価し、値を評価していく。

本当に素晴らしいのは、この章では特に難しいことなどしていないのに気がついたらmonkeyに命が吹き込まれ動き出していることだ。 今まで字句解析し、構文解析をへて真心込めて作られたASTは評価され、実際に式が計算されたり、let文に値が代入されたり、return文が動作したり、エラーがあれば排出する。

しかもなんとGC(ガーベジコレクション)までも動いている!まさかと思うなら実際に読んでみればわかる。動いているんだ。

データ構造拡張

ここまでのmonkeyには数値、真偽値といった単純なデータ構造しかなかった。 この章では文字列、組み込み関数、配列、ハッシュ、という言語を彩る追加のデータ型や関数を追加する。

データ構造を追加していくために、一番最初からコードを追加していく必要がある。 字句解析からもう一度見ていく必要があるんだ。 だけどそれは知識の定着にも繋がるので、もう一度しっかり理解しながら進めていこう。

まず字句解析に""[]{}などの字句を追加する。 構文解析ではこれらが文字列として、配列としてハッシュとしてオブジェクトシステムに当てはめられる。 評価器では新しいデータ構造用にEvalを拡張する。

組み込み関数やハッシュでは新しい試みが多く見られるが、今までに組み上げてきた部分の多くを使い回せるのでそれほど大きな手間はかからない。 あっという間に追加のデータ構造は完成するだろう。

マクロシステム

この章では今までのデータ構造や関数とは一風変わったマクロと呼ばれる、自分自身を書き換えるための仕組みを実装していく。 自分自身を書き換えるというと、ブラックジャ●クのように自分で自分の手術をするようなイメージだ。(危険もあるという意味でも)

マクロは自分自身を書き換えるといったが実際にどこでどう行われるのかを簡単にまとめよう。 構文解析が終わったあと、評価される前にASTを書き換えてしまうのだ。 書き換えるためにASTが生成されていないといけないし、評価されてしまってはもう手遅れなので評価される前でないとダメだ。

実際に実装するマクロシステムはquote, unquote, macro の3つだ。 それぞれ、評価させない、quoteの中でもここだけ評価させる、macro定義でプログラムの指定位置を置き換える、といった役割を持っている。

これらを実装するには通常のEval実装にマクロシステムを割り込ませ、本来評価するはずだった場所を評価させないように隠蔽したり、はたまた隠蔽した場所を再度Evalの実装とは別の場所で下準備をしたあと何食わぬ顔でEvalを再度実行したりする。 マクロシステムで通常のEval実装に割り込む部分は目から鱗が落ちるような単純だが強力なノードの書き換えを行う。 ASTの書き換えという部分が増えたが、結局やるべきことはだいたい同じなので今までのコードとだいたい同じものを少し追加するだけだ。

まとめ

ここまでで本の範囲で見ることができるmonkeyの全てだ。 冒頭で見せたサンプルコードも問題なく実行できるだろう。

だがmonkeyはまだまだ未熟なインタプリタなので、多くの部分を拡張したり最適化できるだろう。 変数名などに許される名前の設計を変えたり、あらたなデータ構造を追加したり、ユーザーに優しいエラー発生システムの追加、評価方法の変更、組み込み関数や組み込みライブラリの追加、......数えればキリがないほどの拡張ができるし、すぐに取りかかれるさほど難しくない拡張もある。

この本で学べることは簡単すぎず、難しすぎない丁度良いボリュームのインタプリタの作り方を学べる。 もちろんGoコードの勉強にもなるかもしれないが、一番の目玉としては字句解析や構文解析、評価器、マクロシステムなどがどのような仕組みや考え方で実装されているのかを学べることだろう。 この本を学んだことで今までより難しい理論に関する書籍のようなものもつまみ食いしていけるかもしれない。

そしてなにより、インタプリタは楽しいってことだ!!

Go言語でつくるインタプリタ

Go言語でつくるインタプリタ

プログラマーがモチベーションを維持するためにやったこと

モチベーションとは自分の意志だけで維持するのは非常に難しいです。 どれだけ外部から効率的にモチベーションを得られるかが鍵となっています。 新生活が始まるこの時期からスタートダッシュを維持できるように、私が実践しているモチベーションアップ方法を紹介します。

対象読者

  • 新生活を始める方
  • モチベーションアップしたいクリエイター

普段使うものに拘る

ペンやノート

設計を紙に書いてみたり、学んだことをノートにまとめてみたり、メモとして使ったり、色々なことに使われるペンやノートですが、自分のお気に入りを見つけると自然とやる気が出たりします。 書き物が多い人は100均のペンより1000円くらいの若干高めのペンをお勧めします。 初期投資は少し上がりますが書き心地が全く違う上に芯を入れ替えればいくらでも使えます。

そしてなにより書くのが楽しくなります。

ノートは場合によって相談が必要ですが、ハードカバーでちょっと大きめのメモ帳はかなり格好良くてテンション上がります。

紙の本を良く読む人は金属製のシャレオツな栞なんか買った日には凄くいい感じに本が読めます。 特に本を開いたままにしておきたい人は、開いた状態を維持できる栞がお勧めです。 本を開いたまま作業ができるので非常に捗ります。

タブレット

電子書籍やブログを良く読む人は、安いものでも問題ないので9インチ以上のタブレットを買うことをお勧めします。 スマートフォンと比べて圧倒的に読みやすくなりますし、タブレットなら電車の中でも使用できます。

絵を描いたり、電子媒体でメモを書きたい人はiPadProが最高にマッチします。

無線イヤホン

AirPod最高です。(AirPodじゃなくてもいいです)

無線イヤホンだとコードが絡まらないうえ、離席時ににイヤホンを取り外す必要がないくストレスフリーです。

PC

PCは自分の業種で求められる平均より上のスペックにするのがいいです。

プログラマーなら分野によって違います。 分野共通でデータストレージはSSD512GB以上かHDD2TB以上。 モバイル、webアプリ系ならメモリ16GBあった方がいいです。 iPhoneアプリを作成するにはMacOSが必要になるので考慮しておきましょう。

椅子

机の前に座ってPCを触っている時間が多いクリエイターはすぐに腰が死にます。 背中も死にます。肩も死にます。首も死にます。とりあえず色々死にます。 安い椅子はできるだけ避けるのが良いでしょう。

椅子には多くの種類がありますので、詳しくはいくつか記事を見て自分にあった椅子を探しましょう。 お勧めとしては実際に店に行って座り心地を確かめてみるのが確実です。 値段目安は2万〜10万程度がいいでしょう。 良い椅子は10年以上使えるので高いと思うのは最初だけです。

環境を整えよう

勉強会に参加する

勉強会とはお互いに学び合おうとする人たちが集まるです。 多くの分野における勉強会がありますので、興味があるものを選んで軽い気持ちで参加してみると新たな刺激が受けられると思います。

広い作業場所を確保する

PCを使うクリエイターは机の上が作業場所なので、机は横1m50cm×縦80cm程度あるものが良いです。

部屋の広さは特に必要ないのでお好みでどうぞ。

新しいことに挑戦しやすい環境に身を置く

新しいことに挑戦するとき応援してくれる人のいる環境に身を置きましょう。それが無理なら最悪、新しい挑戦を批判する人がいる環境からは絶対に離れるようにしましょう。

他人に実力を認められたり、応援されたり、褒められたりするなど、外的フィードバックは非常に重要です。

やる気のある人たちと集まる

やる気のある人たち、実力のある人たちと集まると良い傾向のサイクルが回ります。 自分の実力が一番低くなるようなグループに所属すると成長する、というのも似たような意味合いがあります。

まとめ

自らの上達ぶりに対する自己満足のような内的フィードバック、他からの外的フィードバックを適切に組み合わせ、モチベーションを維持する必要があります。 努力ができない、ということが無いように自分のモチベーションを操作するのが実力向上の必須条件かもしれません。