パステル色な日々

気ままに綴るブログ

キーパーソンは誰だ

引き続き目標の言語化。今後の行動の方向性を決めるものなのですごく大切だ。

開発シーンでは開発チームと問題が遠くなる場合もあるが今日は近い場合、そして一緒に解決する場合の話。

チームのオーダーをくれた人

チームが作ったものに対して承認をする人。この人と話をすると合格の方向性が見えてくる。抽象化された目標でも明後日の方向に向かって作られたものは承認されないのだ。この人とは合格の方向性を握ろう。

事業の偉い人

一緒に仕事をする人の上長。ミッションを与えている。進んでほしい理想像を知っている。方向性を決める参考にしよう。なぜそのミッションになったのかもっと聞いてみよう。さらにその先○年後のビジョンなども聞いてみよう。

一緒に仕事をする人

実際の課題を体系的に感じ取っている人。一番課題に近い人。直近の戦略を決めるときの参考になる。どんな問題やどんな人が存在しているのか聞いてみよう。対策をとっているのならどんな人にどんな対策をとっているのか聞いてみよう。技術でサポートできるのはどんなことだろうか。目標達成のためのストーリーが描けるかも すると自ずと優先順位が導き出せそうだ

ここまで話をすると作戦が決まった。ポイントは当事者だけで話をすること。小さくやっていく。

誰と会話するべきなのかはきっちり手持ちの情報を照らし合わせて決めていこう。狙いを持って話を聞きに行くことが肝心。

話のときには着目ポイントに沿ってメモしよう。すると議論に一貫性が生まれる。

大きくうなずき、話に割って入らない。深掘り、向きを変え、欲しかった答えを聞き出そう。

話は色んなエピソードを分解して再構成したり、抽象化と具体化を意識するだけで見え方が変わる。抽象度合いは話す相手によりレイヤーが違う事業責任者に具体策を聞いても組織構造の意味がない。

曲者

ちゃぶ台を返しそうな人。鶴の一声でいろいろ変えられるひと。ストーリーが決まったらこの人に合意を取ろう。

チームメイト

この時点で同意を一度取ろう。このあと具体策がでてきてもストーリーに沿っていれば疑問は生まれない。納得感を作り出そう。

目標の言語化の間にいろんなキーパーソンが存在する。ひたすら話をしてまとめて戦略を練って話を聞くことの繰り返し。こうして戦略は練られていく。

チームの一歩先を行く人

チームを率いるリーダーになりました。 日記のような形で日々の学びを書き出したくなったので頻度上げて書いていこうと思います。

なので自分に対する話し言葉で書き残します。

チームメイトの一歩先を行き、水先案内人になろう

リーダーに求められることは大きく2つあった。

  • チームに期待されている成果を上げること
  • チームメイトの先を行き水先案内人になること

特に下について、だいたい1Qを見渡すことから始めることに。大変。 チームメイトが頑張ってくれたのに結果を出せなければリーダーの責任と言われる。全くそのとおり。 戦略をしっかり練って水先案内をしないといけない。なのでリーダーの仕事は考えることっぽい。 このあたりで意識が引き締まった。

大きなプロセスイメージを描く

早速わからなくなったのが次にどうすればいいか。 先を行くにあたりどういう動きをすればいいんだろう。

  1. 目標言語化
  2. 目標共有
  3. タスク設定
  4. アサイ

こうらしい。水先案内人をするってことは抽象度が高い目標をチームメイトに見せる前にある程度具体化の作業が必要。 なぜなら新しい開発をするに当たり、問題提起した人がいるわけで、それに関わるキーパーソンからのヒアリングはすることになるから先んじてやっておけばチームメイトが迷いなくスタートを切れる。 抽象度が高いと不安になるし、それに集団で立ち向かって機能的に動けるほどまだチームは成熟してない。ならばリーダーが率先して動いたほうが良い。

業務のつながりを意識したコミュニケーションを取る

大きい組織になれば分業制は当たり前。そうなると仕事のパスが行われるときにたいてい取りこぼしがある。つながりを意識したコミュニケーションを取ることで個々の知識が流動的につながっていることを感じられてチームとして一体感を作れそうだと思った。

メンバーの体験設計でも生きる。チームメイトのバックグラウンドなんて多種多様。ならばちゃんとチームメイトが直前にお世話になっていた人 (コーチやメンター)に話を聞きに行くと目指していくキャリアやどういう人なのかわかる。これはメンバーが気持ちよく仕事をできるようにする材料になる。大いに役立ちそうだ。

まとめ

まだ動き出したばかり、言葉もつまり、ヘルプを出すシーンも多い。悔しいがしょうがない。とりあえず明日の動き方を決めて今日は良く寝よう。

UIから考えるモデリング in Elm

Modelは大切

皆さんは何を考えながらモデリングしてますか?まずはElm Architectureを見てみましょう。

elm_architecture
Elm Architecture

ViewはModelに依存しUpdateもModelに依存しています。したがってModelの影響の大きさは言わずもがなです。

ちょっとしたModelの修正も影響範囲が多くて溜息をつくことは少なくありません。影響は少ないほうがいいですよね。

今回はモデリングについて考えてみましょう。

UIから考える

今回はこの動画にある方法を使ってモデリングをやってみようと思います。

youtu.be

リチャードさんはまずはUIから考えようと言っています。インライン編集可能なブログ記事のページを考えてみましょう。

blog_article_page
ブログ記事のページ

TitleとStoryという本文で構成されたブログ記事のページです。記事の作成者はインライン編集ができます。

ペンのアイコンをクリックすると右のページようなインライン編集状態になります。Saveボタンをクリックすると左のページのTitleが編集したものに置換されます。

みなさんもこのようなページをモデリングしてみてください。それでは次に私がモデリングしてみます。

type Article
    = Article { title : Title, story : Story } Property


type Property
    = ReadOnly
    | WriteTitle Title


type alias Title =
    String


type alias Story =
    String


-- MODEL

type alias Model =
    { article : Article
    }

Titleのインライン編集に着目してモデリングしてみました。皆さんのモデリングと見比べてどうでしたか?

それでは次のモデリングと見比べてみましょう。

type Property
    = ReadOnly
    | WriteTitle Title


type alias Title = String


type alias Story = String

-- MODEL

type alias Model =
    { title : Title -- READ/WRITE
    , story : Story -- READ/WRITE
    , property : Property -- READ/WRITE
    }

前者と後者は何が違うのでしょうか?後者はレコードの要素をいつでも操作することができます。

そのためPropertyがReadOnlyであってもtitleとstoryは変更できます。もちろんそのようにしないロジックを構築すれば防ぐことができます。

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ToggleEditTitle ->
            ( { model | property = WriteTitle model.title }, Cmd.none )

        SetTitle title ->
            case model.property of
                WriteTitle _ ->
                    ( { model | property = WriteTitle title }, Cmd.none )

                ReadOnly ->
                    ( model, Cmd.none )

        SaveTitle ->
            case model.property of
                WriteTitle title ->
                    ( { model | title = title, property = ReadOnly }, Cmd.none )

                ReadOnly ->
                    ( model, Cmd.none )

それでは前者の場合はどうでしょう。

module Article exposing (Article, empty, mapTitle, toggleTitle)


type Article
    = Article { title : Title, story : Story } Property


type Property
    = ReadOnly
    | WriteTitle Title


type alias Title =
    String


type alias Story =
    String


empty : Article
empty =
    Article { title = "", story = "" } ReadOnly


toggleTitle : Article -> Article
toggleTitle (Article edits property) =
    case property of
        ReadOnly ->
            WriteTitle edits.title
                |> Article edits

        WriteTitle title ->
            ReadOnly
                |> Article { edits | title = title }


mapTitle : (Title -> Title) -> Article -> Article
mapTitle modifier ((Article edits property) as article) =
    case property of
        ReadOnly ->
            article

        WriteTitle title ->
            WriteTitle (modifier title)
                |> Article edits
module Page.Article exposing (..)

import Article exposing (Article)


-- MODEL

type alias Model =
    { article : Article
    }

-- UPDATE

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        ToggleEditTitle ->
            ( { model | article = Article.toggleTitle model.article }, Cmd.none )

        SetTitle title ->
            ( { model | article = Article.mapTitle (always title) model.article }, Cmd.none )

        SaveTitle ->
            ( { model | article = Article.toggleTitle model.article }, Cmd.none )

いかがでしょうか。モジュールを2つに分けてみました。

これならArticleはReadOnlyのときにtitleを変更できません。なぜならそのような関数がArticleモジュールから公開されてないからです。

また前述のパターンと異なり、WriteTitleコンストラクタも公開していません。このためArticleモジュール内部にArticleの実態は隠蔽されています。

この方法をOpaque Typeと呼びます。テクニックの一つですが動画では必須とまで言われています。

これで、Articleモジュールを編集してもUpdateやViewに影響が出ないように作ることができました。ネストが浅くなり多少見やすくなってないでしょうか。

また、title編集時の状態を WriteTitle Title と定義しているのもポイントです。

type alias Model =
    { title : Title
    , story : Story
    , editableTitle : Maybe Title
    , editableStory : Maybe Story
    }

少し定義を変えてみました。それではこの状態はとり得るのでしょうか。

type alias Model =
    { title : Title
    , story : Story
    , editableTitle : Just Title
    , editableStory : Just Story
    }

インラインで編集可能なのは一箇所だけとします。するとeditableTitleとeditableStoryの両方がこのような状態を取ることはありえません。

このようなアプリケーションにありえない状態を作り出すことが可能なモデリングを避けることでバグの少ないアプリケーションを構築できます。不可能な状態はモデリングで防ぐことができます。

youtu.be

サマリ

  • UIから考えることでモデリングができる
  • Oppaque Typeで変更に強いモデルを作ることができる
  • 不可能な状態 (Impossible state) を排除することができる

所感

またもや動画からモデリングの方法の一部を紹介しました。Elmでアプリケーション開発をしているとモデルを変更する機会は多々訪れます。

そのたびにModel, Update, Viewのすべてを変更するのはとても大変です。今回の方法を用いてなるだけ変更の影響がもれないように気をつけてみてください。

しかし、モデリングのポイントはこれだけではありません。UI StateとDomainをどのようにモデリングするのかという話題もあります。

フロントエンドでDDDをしようものならもっと複雑になることでしょう。一概にこれが正解というわけではないので皆さんの課題に沿った最善の方法を模索していきましょう。

そしてどんどん私に教えてください。 (違

Elmのモジュールを作るときはどういうとき

モジュールを作るとき

youtu.be

いきなりですがこの動画を見てください。この動画はElmの作者のEvanさんによる発表です。

まず見て驚くのはElmの場合、一つのファイルに対してプログラムを育てていくことではないでしょうか。通常システムを作るときには責務を切り分けて責務ごとにモジュールを作成することが結果的にファイル分割につながっていました。

Elmでも同様にElm ArchitectureによりModel, Update, Subscribe, Viewにシステムが分割されています。しかし単一の責務ごとにモジュールを作成することなく一つのファイルにアプリケーションを構築していきます。

この発表いわく通常それは400から1000行くらいのコードまで成長するそうです。そのあたりからモジュール作成の兆しが訪れることが述べられています。

Modelの構築の仕方に習っていくと新しいCustom Typeを作る機会が訪れます。まずはそのCustom Typeを作成して、そのCustom Typeにまつわる関数を作ります。

こうしてできたCustom Typeとそれにまつわる関数を別モジュールに切り出します。これがElmにおけるモジュール作成のタイミングと作成方法です。

このことはドキュメントにまとまっています。

Modules · An Introduction to Elm

モジュールの変更による影響範囲を閉じ込める

youtu.be

またいきなりですがこの動画を見てください。この動画はEvanさんの同僚NoRedInkで働くRichardさんの発表です。

動画で述べられている通りModelに依存しているシステムは多いのでModelの変更は影響が多岐にわたることが多いです。これをModel内の変更に留めるにはどうするといいでしょうか?

積極的にOpaque Typeを使うことを主張しています。Opaque Typeについては下記の記事が参考になります。

Advanced Types in Elm - Opaque Types – Charlie Koster – Medium

Elm PackagesのAPI Documentにも記述があります。

Design Guidelines

適切なインターフェース (関数) を作成し、exposingで公開するものを絞り込むことで変更をモジュールに閉じ込めることができます。

module Post exposing (Post, fromDoc)

type Post
  = Post doc
  
  
fromDoc : doc -> Post
fromDoc =
  Post

やりすぎないこと

Evanさんはやりすぎないことを注意としています。Custom Typeによるモジュール化を進めると最初のうちから再利用を意識して同じロジックをまとめたがります。

そんなときには一呼吸おいて問題が起きてから対処するようにしましょう。例えばページによって同じCustom Typeでもデータ構造が異なることがあります。

module PageX exposing (..)

type Doc
  = Doc Title Content

type alias Title = String

type alias Content = String
module PageY exposing (..)

type Doc
  = Doc
    { title : String
    , content : String
    , description : String
    }

早い段階から共通化を見越して過剰に手を加えるのではなく実際に重複するロジックが出てくるまで待ってみましょう。

共通したデータ構造があるのならコードを改善してシェアしましょう。このようにElmでは作っていく最中に発見してリファクタリングを積極的に繰り返すことを勧めているように感じます。

それは強力な型と言語設計によりリファクタリングをしやすい環境が整っているからでしょう。

サマリ

  • Elmでは必要な時が来るまでモノリシックなモジュールでアプリケーションを育てていく
  • カスタムタイプが増えてヘルパー関数ができたときがモジュールを作るサイン
  • モジュールはカスタムタイプに対する責任を持つ
  • Opaque Typeを積極的に使い影響範囲をなるべくモジュール内に限定する
  • 過度なモジュール化はすべきではないリファクタリングすることを念頭に置く

所感

今回はElmのモジュールについて記事にしました。Elmではこのような思想がありますが、一つのファイルで1000行もコードを書いていてはチーム開発をしているとコンフリクトが多発しそうですね。

また、モジュールの作り方にこのような指針がないとチームで開発ではこの関数はどのモジュールに属するのがいいのか迷ってしまうシーンがあります。

JavaScriptとは異なるモジュールの作成プロセスですが、Elmではこのようなプロセスをおすすめしていることを紹介しました。

Elmはどうやって超小さいアセットを実現したのだろう

Elmはとても小さいアセットを作ることができる

We had a community member working on a 49,315 line application try the new version and he got 114kb after compilation, minification, and gzip. That is in the ballpark of the 100kb produced by the 2000 lines in the Vue implementation!

blog/small-assets-without-the-headache

リンク先の画像が示すように、Elmはリリースされた0.19で十分に小さいJavaScriptファイルを生成することができるようになりました。RealWorldアプリケーションでの検証結果ですが、ひと目で主要なフロントエンドのライブラリ利用時よりも小さくなっている事がわかります。

0.19リリース時に公開されたこのブログ記事を見て僕は「すごいなー」と感心しました。一体どうやってこの小さなファイルを生成することができるようになったのでしょう。

デッドコードを削除する

僕はしばしば課題に直面すると同じような課題に対してアプローチしているライブラリがないか探す事があります。JavaScriptにはNPMという素晴らしいシステムがあるので、簡単にプロジェクトにライブラリを導入できます。

しかし、ライブラリのコードをすべて利用することは殆どないのです。ライブラリが公開しているAPIはどれも魅力的なものですが課題を突破するためにはいくつかのAPIで事足りる事が多いからです。

デッドコードはこうしたときに生まれます。利用してないコードがあってもライブラリをインストールした以上、ビルド時には無視できない存在となっています。もちろんライブラリだけではなくプロジェクト内のコードだって同じように考えることができるでしょう。

巨大なプロジェクトでは利用されてないコードが生まれることを見落としていることもあるかもしれません。webpackに代表されるようにツリーシェイキングによってこのようなデッドコードは削ぎ落とされることになりますが、完璧に削ぎ落とせてないそうです。

どうやらwebpackはモジュール単位でルーチンを走らせており、モジュールの粒度が大きくなればなるほど利用してないコードが紛れ込む可能性が高くなります。直接APIを呼んでない場合でも別のルーチンから呼ばれている場合もあるためライブラリが増えれば増えるほどデッドコードは増加する傾向にあります。

対策としてlodashに代表されるように関数ごとにモジュールを分ける方法があるものの、JavaScriptではどのモジュールでも実行時に関数の変更ができるので今呼ばれてないからと言って簡単に削除することはできないのが実情です。

しかし、Elmの関数は実行時に再定義と削除ができないため、メイン関数から到達可能であれば残して到達できなければ削除することができます。これがElmがアセットを小さく保つことができた1つ目の理由です。

また、Elmの場合はNPMとは異なり、package.elm-lang.orgでライブラリを公開しており、ここで公開されているライブラリはすべてElmで書かれているので安心して同じように削除できるのです。

レコードフィールドをリネームする

Elmにおけるレコードとは次のようなものです。

student =
  { name = "Haruka"
  , age = 17
  }

フィールドとはnameageを指していいます。これを参照する場合はstudent.nameとなりますが、0.19ではコンパイルの前後でs.nとリネームされるのです。

驚くべきことにこれで5% ~ 10%縮小するそうです。やはりこれもライブラリを含めたエコシステム全体で機能できます。

サマリ

  • Elmは0.19で主要なライブラリのどれよりも小さいJSファイルを生成する事ができる
    • デッドコードを削除する
    • レコードフィールドをリネームする

所感

非常に小さなアセットの生成が可能になった背景には言語の特性とその特性沿ったエコシステムがありました。一方でElm 0.19には多くの破壊的変更が含まれています。

compiler/0.19.md at master · elm/compiler · GitHub

コンパイラの最適化にはこのような変更を盛り込むしかなかったのでしょうか。Elmは純粋な関数型プログラミングの土壌を用意してくれていますが、JavaScriptに変換する際に問題を作り出してしまうこともあったのではないでしょうか。

変更の裏にはこうした課題を突破できないのでやむを得ず適用した変更したものもあるかもしれません。世界観を一貫して守りきる思想の強い言語という雰囲気を感じ取ることができました。それにしても破壊的変更が多くて大変です。

Elmのアプリケーションコードを読んでみよう

ElmアプリケーションはElmアーキテクチャーに沿って構築していきます。構築したアプリケーションコードはElmのコアコード上でアプリケーションとして振る舞います。

Elmのコアコードがどんなことをしているのか気になったので、今回はElmアプリケーションが起動するまでにどういうことをやっているのかを追っていきたいと思います。

起動呼び出し

Elmアプリケーションを起動するには以下の呼び出しを最初に行います。

var app = Elm.Main.fullscreen()

他にもいろいろ起動するために呼び出せる関数はありますが、とりあえずこちらから考えます。見て分かる通り Elm.Main オブジェクトを操作しているので、まずはこれがどういうオブジェクトなのか知ることから始めていきましょう。

Elm.Main

var Elm = {};
Elm['Main'] = Elm['Main'] || {};
if (typeof _user$project$Main$main !== 'undefined') {
    _user$project$Main$main(Elm['Main'], 'Main', undefined);
}

非常にシンプルなコードですね。_user$project$Main$main をみると、どうやらコンパイラモジュール名$関数名 という名前付けをしているようです。

Elm['Main'] にどういう変更が加えられるのか見ていきましょう。

makeProgram

function makeProgram(flagChecker)
{
    return F2(function(debugWrap, impl)
    {
        return function(flagDecoder)
        {
            return function(object, moduleName, debugMetadata) // == _user$project$Main$main
            {
                var checker = flagChecker(flagDecoder, moduleName);
                if (typeof debugMetadata === 'undefined')
                {
                    normalSetup(impl, object, moduleName, checker);
                }
                else
                {
                    debugSetup(A2(debugWrap, debugMetadata, impl), object, moduleName, checker);
                }
            };
        };
    });
}

_user$project$Main$mainobject, moduleName, debugMetadata を引数に取る無名関数みたいです。flagCheckercheckNoFlagscheckYesFlags が用意されています。

F2 は関数を受け取ってカリー化して返します。逆に A2 はカリー化された関数に引数を渡して評価する関数です。 debugWrap_elm_lang$virtual_dom$VirtualDom_Debug$wrap です。

implHtml.program に渡すおなじみのレコードです。見てわかるように Elm['Main']debugMetadata の有無で呼び出す関数が異なります。

今回は undefined でした。Html.program はこの関数の実行結果で実装が定義されます。

normalSetup

function normalSetup(impl, object, moduleName, flagChecker)
{
    object['embed'] = function embed(node, flags)
    {
        while (node.lastChild)
        {
            node.removeChild(node.lastChild);
        }

        return _elm_lang$core$Native_Platform.initialize(
            flagChecker(impl.init, flags, node),
            impl.update,
            impl.subscriptions,
            normalRenderer(node, impl.view)
        );
    };

    object['fullscreen'] = function fullscreen(flags)
    {
        return _elm_lang$core$Native_Platform.initialize(
            flagChecker(impl.init, flags, document.body),
            impl.update,
            impl.subscriptions,
            normalRenderer(document.body, impl.view)
        );
    };
}

ここで Elm['Main'] に変更が加えられます。これ以上、 Elm['Main'] に変更は加えてなさそうです。

initialize

function initialize(init, update, subscriptions, renderer)
{
    // ambient state
    var managers = {};
    var updateView;

    // init and update state in main process
    var initApp = _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {
        var model = init._0;
        updateView = renderer(enqueue, model);
        var cmds = init._1;
        var subs = subscriptions(model);
        dispatchEffects(managers, cmds, subs);
        callback(_elm_lang$core$Native_Scheduler.succeed(model));
    });

    function onMessage(msg, model)
    {
        return _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {
            var results = A2(update, msg, model);
            model = results._0;
            updateView(model);
            var cmds = results._1;
            var subs = subscriptions(model);
            dispatchEffects(managers, cmds, subs);
            callback(_elm_lang$core$Native_Scheduler.succeed(model));
        });
    }

    var mainProcess = spawnLoop(initApp, onMessage);

    function enqueue(msg)
    {
        _elm_lang$core$Native_Scheduler.rawSend(mainProcess, msg);
    }

    var ports = setupEffects(managers, enqueue);

    return ports ? { ports: ports } : {};
}

続けて fullscreen 呼び出しによる起動に至るまでの処理を眺めていきます。この関数はかなり重要そうです。

_elm_lang$core$Native_Scheduler.nativeBinding に渡されている関数はどちらも同じような内容です。おそらく view を更新して effectManagercmdssubs を渡して変更を通知しています。

_elm_lang$core$Native_Scheduler.nativeBinding を始めとする _elm_lang$core$Native_Scheduler モジュールの関数はElmアプリケーションのイベントループに大いに関係しています。

core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub

nativeBinding が返すオブジェクトは後にenqueueされて処理されます。initApp は初期化に必要な処理、onMessage はメッセージを受け取った時に呼び出す処理だと推測できます。

setupEffectsdispatchEffects のための準備ではないでしょうか。もしかするとEffectManager関係の処理かもしれないです。ここで返しているオブジェクトは app に代入されますが利用してないですね。

spawnLoop

function spawnLoop(init, onMessage)
{
    var andThen = _elm_lang$core$Native_Scheduler.andThen;

    function loop(state)
    {
        var handleMsg = _elm_lang$core$Native_Scheduler.receive(function(msg) {
            return onMessage(msg, state);
        });
        return A2(andThen, loop, handleMsg);
    }

    var task = A2(andThen, loop, init);

    return _elm_lang$core$Native_Scheduler.rawSpawn(task);
}

一見するとこんがらがりそうなんですが、return 文に注目すると taskenqueue しているのがわかります。

core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub

この task_elm_lang$core$Native_Scheduler.nativeBinding の仲間です。これは _elm_lang$core$Native_Scheduler.andThen で処理がラップされています。

core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub

コールバック関数の loophandleMsg という task を設定して再度 andThen でラップしています。_elm_lang$core$Native_Scheduler.receive_elm_lang$core$Native_Scheduler.nativeBinding の仲間です。

core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub

rawSpawn

core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub

processtask をラップしています。 processenqueue します。

work

キューに溜まった process を処理します。取り出して process.root があれば step 関数に渡します。process がなくなると活動を停止します。

core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub

step

enqueue された processstep 関数で処理されます

core/Scheduler.js at 5.1.1 · elm-lang/core · GitHub

enqueue された task をおさらいします。

process(andThen(loop, nativeBinding(task)))

順に処理していきますが、 loop が実行される気配がありません。 loopprocess.stack.callback に積まれたままです。

ちょっと謎ですね。 loop はいつ呼ばれるのでしょうか。

nativeBinding(task) の処理では登録していた関数を呼び出しています。ここで呼び出しているのは初期化処理をする関数です。

 var initApp = _elm_lang$core$Native_Scheduler.nativeBinding(function(callback) {
        var model = init._0;
        updateView = renderer(enqueue, model);
        var cmds = init._1;
        var subs = subscriptions(model);
        dispatchEffects(managers, cmds, subs);
        callback(_elm_lang$core$Native_Scheduler.succeed(model));
    });

renderernormalRenderer(document.body, impl.view) の実行結果です。

function normalRenderer(parentNode, view)
{
    return function(tagger, initialModel)
    {
        var eventNode = { tagger: tagger, parent: undefined };
        var initialVirtualNode = view(initialModel);
        var domNode = render(initialVirtualNode, eventNode);
        parentNode.appendChild(domNode);
        return makeStepper(domNode, view, initialVirtualNode, eventNode);
    };
}

normalRenderer では初期DOMの生成を行い、stepper 関数の作成を行います。

function makeStepper(domNode, view, initialVirtualNode, eventNode)
{
    var state = 'NO_REQUEST';
    var currNode = initialVirtualNode;
    var nextModel;

    function updateIfNeeded()
    {
        switch (state)
        {
            case 'NO_REQUEST':
                throw new Error(
                    'Unexpected draw callback.\n' +
                    'Please report this to <https://github.com/elm-lang/virtual-dom/issues>.'
                );

            case 'PENDING_REQUEST':
                rAF(updateIfNeeded);
                state = 'EXTRA_REQUEST';

                var nextNode = view(nextModel);
                var patches = diff(currNode, nextNode);
                domNode = applyPatches(domNode, currNode, patches, eventNode);
                currNode = nextNode;

                return;

            case 'EXTRA_REQUEST':
                state = 'NO_REQUEST';
                return;
        }
    }

    return function stepper(model)
    {
        if (state === 'NO_REQUEST')
        {
            rAF(updateIfNeeded);
        }
        state = 'PENDING_REQUEST';
        nextModel = model;
    };
}

makeStepper は 1フレームごとに実行される関数の登録を行う関数 stepper の作成を行います。virtualDOMの振る舞いを記述しているのもここになります。DOM差分を計算して適用している様子がわかります。

applyPatches では差分適用のほかイベントに対する関数の登録も行っています。renderer によって作成された関数 updateViewonMessage 関数の中で利用されています。

dispatchEffects では特に何もしないため最後にコールバック関数を呼び出します。

modelsucceed でラップしてものをコールバック関数は受け取り新たな process.root としています。同時に processenqueue しています。

succeedstep 関数で処理されます。process.stack.callback に登録した loop 関数がようやく実行されます。

が、特に何事もなく process は処理されて初期化処理は終了します。

まとめ

Elmアプリケーションの初期化処理でした。今回の重要登場人物は次のとおりです。

  • makeProgram ... デバッグの有無を判定
  • normalSetup ... fullscreen, embed を定義
  • initialize ... 初期化タスク定義と初期化実行
  • spawnLoop ... 初期化タスクの合成
  • rawSpawn ... processでラップ、キューにタスクを登録
  • work ... キューに溜まった process を処理
  • step ... タスク処理
  • makeStepper ... DOMの差分処理

流れもざっくり振り返りましょう。

Elmオブジェクト準備

  1. オブジェクトを作る
  2. normalSetup を呼んで fullscreen を定義する

初期化する

  1. initialize を呼ぶ
  2. spawnLoop で初期化タスクを作成
  3. rawSpawn を呼び、キューに追加
  4. work を呼び初期化タスクを順番に実行
  5. step を呼びタスクを処理、process.root を設定
  6. initApp に登録された初期化処理を実行
  7. 初期viewの追加

「超速! Webページ速度改善ガイド 使いやすさは「速さ」から始まる」を読んだ初学者の感想

今までパフォーマンスといえばSQLチューニングやらWebサーバのチューニングやらしかやってこなかったpastelIncです。 Webパフォーマンスについて必要に駆られなかったこともあり、勉強してなかったんですがSPA開発をしていて必要にかられたので表題の本で勉強してみました。

webperf.guide

なぜこの本だったのか

あほむさん (twitter: @ahomu) と煎茶さん (twitter: @1000ch) の存在が自分の中で大きかったからです。 以前から勉強会や記事、podcastでお見かけすることの多かったお二人です。なかでもパフォーマンスを語るシーンにおいては勉強になることも多く僕にとってのWebパフォーマンスの代名詞でした。そのお二人が書かれた本ということでこの本を手に取った次第です。

パフォーマンス初学者が読んでも良い内容か

間違いなく良いです。個人的には初学者が読んじゃいけない本なんてないと思ってます。興味があれば読む、それだけです。いきなり高度すぎたら少しずつ読み解いていけばよいのです。

超大満足

本書はそんなパフォーマンス初学者の僕にもうってつけな最高の本でした。読みながらただひたすらに「へぇ〜」って感じでした。 もう少し詳しく言うと本書は読み進めていくと「実際に自分でコード書いて確認したい〜」欲が溢れてきます。面白いです。

DevToolsとお友達になれる

Chrome DevToolsは Console タブをメインで使ってましたが、実はいろいろできることを本書で知りました。

パフォーマンスボトルネックの手がかりをつかむための基礎知識が分かる本

本書を読むとパフォーマンス調査をするための基礎知識が手に入ります。パフォーマンス調査は仮説 -> 検証の繰り返しになると思いますが、仮説を立てるときの材料が豊富に用意されています。勘で仮説を立てるシーンが減りそうな気がします。また、仮説を怠り検証ばかり繰り返しなぜかわからないけどうまくいった経験がある方いると思いますが、なぜうまくいったのかを知らなければ同じことの繰り返しになってしまいます。本書は推測するな、計測せよを掲げていきなり検証をするのではなくまずは計測することの大切さを訴えています。本書を読むとなぜ計測が大切なのか身にしみると思います。

非機能要件を勉強する必要あるのか

あると思います。機能要件は開発チームの誰でも割りとヘルプできると思いますが非機能要件となると途端に職人を必要とする現場は多いのではないでしょうか。非機能要件は学ぶことでエンジニアとして一つ尖る事ができると思うのです。 また、パフォーマンスを知りビジネスサイドとも話ができると一口にパフォーマンスを担保してほしいと言われた時に、どこまで出来て何を妥協してほしいのか説明できるようになります。お金を積んで解決する問題であれば訴えることも出来ますし、ビジネスサイドも天秤にかけることができます。結果としてプロダクトは継続して受け入れられやすくなるのではないでしょうか。

まとめ

買いましょう。読みましょう。おすすめです。