すもぎのめも

いろいろあったことをメモしています

The Timeless way of Event - F# 勉強会 岐阜

connpass.com

F# 勉強会 岐阜を開催し、リアクティブプログラミングについて発表してきました……といっても最後でしか「リアクティブ」という言葉を使わない発表にしました。リアクティブという言葉は言葉だけが先行していると感じていたからです。 4年ぶり 2回目の発表となりました。

見どころ

忙しいかたはこちらだけでも見てやってください。https://github.com/smallgeek/Sample/blob/master/src/ExcelBuilder/Program.fs

リアクティブプログラミングを勉強したことのあるかたは思わずニヤリとしてしまうかもしれない

発表資料

私の作る発表資料は文字が少ないため、公開しても参加していない方には真意を測りかねるかもしれませんので、1スライドずつ、それぞれのメモ書きを併せて記載します。

f:id:smallgeek:20170531220546p:plain The Timeless way of Event というタイトルで発表させていただきます。

f:id:smallgeek:20170531220800p:plain 57577 ですね。

f:id:smallgeek:20170531220842p:plain これからデモするのですが、それにあたって皆さんに知っておいてもらいたいことがあります。 まず、デモには FlashAir という SD カードを使います。有村架純の CM のやつですね。こいつは AP として使用できます。 それからローカルフォルダを使います。フォルダに画像ファイルを格納するのですが、 先ほどの FlashAir から API 経由でダウンロードした画像ファイルの保存先と僕が手動で保存するファイルの保存先の 2つのフォルダになります。 そして Cognitive ServicesComputer Vision API を使用します。画像を認識して文字を抽出してくれるサービスです。

f:id:smallgeek:20170531221028p:plain うわぁ

皆さんこういう仕様書に見覚えがあるのではないでしょうか。 はい、Excel 方眼紙です。 偉い人からいただいた仕様書をもとにプログラミングしていく世界があるようです。 この内容をコピペして、それに沿ってコードを書けば仕様書通りの処理ができる!

f:id:smallgeek:20170531221100p:plain とまぁ、このままだと仕様書マッピングされたコードが生み出されてしまうので、ちょっと見方を変えてみましょう。 イベントの開始から終了までを可視化してみます。 まずダウンロードイベントがあります。FlashAir からファイルがダウンロードされるとイベントが発生します。 次にファイル生成イベントがあります。フォルダ内にファイルが移動されるとファイルが生成されたことになりますので、そのときにもイベントが発生します。 これらのパスから Uri を生成して、Uri をひとつにまとめます。 それらの UriComputer Vision API に渡して、結果を取得します。 取得した結果を判定して、テキストが抽出したかどうかで振り分けを行い、画面に表示する、という流れになっています。

f:id:smallgeek:20170531221205p:plain https://github.com/smallgeek/Sample/tree/master/src/MergePartition

f:id:smallgeek:20170531221318p:plain デモのソフトで核となるコードはこの部分になります。

f:id:smallgeek:20170531221411p:plain 処理とコードの流れを確認していきます。まず、イベントがあります。

f:id:smallgeek:20170531221440p:plain 発生したイベントから通知されたデータを Uri にしています。C#LINQ でいうところの select にあたります。

f:id:smallgeek:20170531221510p:plain ダウンロードファイルの Uri とローカルファイルの Uri のデータソースを 1つにまとめています。

f:id:smallgeek:20170531221549p:plain Computer Vision APIUri を送信して結果を取得しています。ここでは非同期でデータが送受信されています。

f:id:smallgeek:20170531221616p:plain API からの結果を確認し、文字列が検出されたかどうかで結果を振り分けています。 今見てきたように、処理とコードが同じような流れ方をしています。 また、イベントの開始から終了までが、あたかも普通のデータ操作のようにも見えます。

f:id:smallgeek:20170531221659p:plain 以上から、イベントというのは、時を超えて到達するデータの集まりのようなものだ、と考えることができます。

f:id:smallgeek:20170531221728p:plain データの集まりだとみなすなら、普通のリストや配列と同じようにデータ操作ができるはずです。 例えばベルトコンベアで流れてくるパーツに不良がなければラベルを貼る、というような加工ができます。 この処理ではパーツが完成するたびに不良品がチェックされます。 今ぐらいの時期になると親会社で工場勤務が始まる会社もあるそうで闇を感じます。 加工には map や filter みたいな関数が使われます。

f:id:smallgeek:20170531221759p:plain 別の工場ラインで仕上がったパーツを結合する、ということもできます。 これは飛騨と美濃の両方のパーツが必要なので zip ですね。 飛騨と美濃の二つの地方がないと岐阜として成り立たないので、両方のイベントのデータが到達していないときはパーツが結合されません。 イベントとイベントを結合することで、新しいイベントが生まれています。 このようにイベントの結合も、普通のコレクションの zip 操作と同じようにすることができます。

f:id:smallgeek:20170531221844p:plain さて、イベントをデータとして扱うためには、コンテナ、エンジン、モジュールが必要となります。 データには入れ物が必要です。コンテナにはイベントそのものが入っていると考えていいです。 エンジンはイベントの発生を通知したり、イベントを発生させるソフトウェアの機構です。こいつはコンテナの裏で動きます。 モジュールはイベントをカスタマイズするための関数群です。コンテナに何かしらの操作を加えて 偶然にも F# は標準でイベントをデータとして扱うための機能を持っているので、単純なフローなら何も追加しなくても OK です。 さらに OSS を使うことで、データの結合の仕方や値の変更通知の方法など、新しいデータ表現が手に入ります。OSS はいくつかありますが発表の後のほうで紹介します。

f:id:smallgeek:20170531221955p:plain これらを踏まえて、もう一度ソースを見てみましょう。このとき、remoteSource と localSource がソースとなるイベントのコンテナで、detected, undetected が新しいイベントのコンテナです。 例えば detected というのは、文字列が認識されたという Detected イベントとみることも、時を超えて生成された文字列認識結果の集まりとみることもできます。

f:id:smallgeek:20170531222024p:plain イベントは “時を超えて到達するデータの集まり” とみなすことができます。 データの集まりとみなせれば、同じくデータの集まりである配列やリストと同じようにデータを処理できると考えられます。 データの集まりは結合が可能ですので、”時を超えて到達するデータの集まり” を結合することは、イベントを結合することに他なりません。 このように考えていくと、イベントの流れをコードで表現できるようになり、単純なイベントを組み合わせて、より具体的なイベントを構築できるようになる、というメリットがあります。

f:id:smallgeek:20170531222102p:plain 57577

f:id:smallgeek:20170531222156p:plain イベントは時間を超えて到達するデータの集まり、データの流れだと説明しました。 次々にやってくるデータであっても、観測した瞬間は単純にひとつのデータに見えます。 イベントは”時間が経過したら変化するデータ”だといえそうです。 この変化するデータにもコンテナが必要となります。

さて、変化するデータのコンテナといえば……

f:id:smallgeek:20170531222316p:plain はい、みなさんご推察のとおり、Excel です。ただし今度は方眼紙ではありません。やったね。 関連するセルの変更というイベントを受けて変化するセル……画像でいうと C1 セルですね。A1 と B1 の変更を受けて C1 は変化します。 この単純な式のなかには、データが変更しても、C1 の値は常に A1 + B1 だよ、という、依存するデータの変更で変わらないデータの関係が表現できています。 Excel の表現力ってすごいね

f:id:smallgeek:20170531222645p:plain Excel の数式をコードに落とし込んでみます。これは架空の言語ですが、a1 に入っている値が変わったら c1 も変わることを祈って書かれています。 残念ながらどんなに祈りを捧げても期待通りには動きません。Excel の表現力ってすごいね

f:id:smallgeek:20170531222720p:plain F# でも Excel を模した擬似コードらしいものは書けるのですが、これも期待通りには動きません。Excel の表現力ってすごいね

f:id:smallgeek:20170531222745p:plain もう少し足掻いて、型と演算子を追加してみましょう。そこそこ理想形に近いコードができあがりました。このコードは動く!

f:id:smallgeek:20170531222818p:plain https://github.com/smallgeek/Sample/tree/master/src/ExcelBuilder

f:id:smallgeek:20170531222902p:plain セルの変更もイベントといえます。C1 のセルから見れば、A1 や B1 はイベントとも、時間によって変化するデータとも見ることができます。 また、セルとセルとの間には今日も冷たいデータの流れが存在しています。C1 セルはこれらを zip 操作した結果とみることもできます。 そして、C1 セルには数式が格納されていましたが、このセルは値を自動更新して、数式に込められた熱き誓いを守る勇者のようなシステムとみることができます。

f:id:smallgeek:20170531223024p:plain Excel を模したコードは実用的ではないので、今度はもう少し実用的な例を出します。 半加算器という演算装置があります。専攻が情報化の方には説明が不要だと思いますが、僕は情報化でないのでなじみがありません。 この装置は 2進数の同じ桁どうしを演算して、加算結果と桁上がりを出力します。なんだかよくわかりませんが、真理値表のとおりの入出力が行われます。

f:id:smallgeek:20170531223200p:plain 半加算器から全加算器を作ることができます。この装置も 2進数の加算を行い、結果を出力するものです。 これらの装置にも Excel のように、変化する値と、それを受けても変化しない関係が含まれています。 先ほどとは別の方法を使って、これらの関係をコードで表現してみます。

f:id:smallgeek:20170531223216p:plain https://github.com/smallgeek/Sample/tree/master/src/HalfAdder https://github.com/smallgeek/Sample/tree/master/src/FullAdder

f:id:smallgeek:20170531223402p:plain デモのようなソフトウェアを設計するには、時間を超えてデータがやってきても、時間が経過して状態が変化しても、変わることのない関係をコードで表現することが必要です。 Excel だと数式、加算器だと入出力が、変わることのない関係を表現していました。デモのソースコードでは、関数による結合とラムダ式による宣言で表現されていました。

f:id:smallgeek:20170531223445p:plain 変わることのない関係をコードで表現できたなら、あとはイベント処理エンジンがよろしくやってくれます。 エンジンの実行時には 2つのフェーズがあります。 まず初期化フェーズで、エンジンは記述されたコードを元に内部でデータの関係を表すグラフ構造を生成します。 そして実行フェーズでは、記述されたデータの流れと関係が常に守られるように状態を更新します。 割と単純な仕組みで、変わることのないデータの関係を作り上げているわけです。

f:id:smallgeek:20170531223555p:plain ここまでのまとめですが、まず時間とともに変化するデータがありました。 時間とともに変化するデータには、時間が経過しても変化しない関係があることもあります。 その関係をコードで表現し、イベント処理エンジンに任せることでエンジンがデータの関係を維持してくれるようになり、状態管理が簡単になる、というメリットがあります。

f:id:smallgeek:20170531223635p:plain 一方でデメリットも存在します。 エンジンはデータの関係を維持するために、何度かデータを更新することがあります。 特に考えずにデータを結合してしまうと、パフォーマンスが問題になる場合がありますので、一度キャッシュしたデータを使用してデータの関係を維持するようにするといった対策が必要になることもあります。 先ほどの ExcelBuilder はデータ変更の差分を計算していないので、実は効率が悪かったりします。 自動テストも難しいです。一応ライブラリもあるのですが、難しいので手動テストしたほうがよいかもしれません。

そして、定時過ぎてからみなぎって仕事する人も少なくない業界ですが、そういうとき大体フローになっていることが多いと思います。 大体こういうときに書いたコードは、冷静に見直すと、なんじゃこれはみたいになっているのですが イベント……すなわち時を超えて到達するデータや時間によって変化するデータは簡単に結合できますので間に非同期処理が入ってると、その非同期処理が何度も呼ばれてしまう……といったバグを入れ込んでしまうかもしれません。 僕は今回のデモを作るときに Computer Vision API に多重アクセスしてしまう不具合に気づくまでに結構頭を悩ませました。

f:id:smallgeek:20170601204806p:plain 結局何の話をしてたかというと、リアクティブプログラミングというパラダイムからイベントを見てみよう、という話でした。 今回はリアクティブという言葉を使わないで説明することにしました。 リアクティブという言葉はバズり過ぎていますし、日本語訳も “反応性” と、よくわからないですね。 あえて “反応” という言葉を使って説明するならば、外部からの入力に、変わることのないデータの関係を自動更新するように反応させるプログラミングである、といったところでしょうか。 僕はリアクティブプログラミングが好きですが、リアクティブという言葉は好きではありません。

f:id:smallgeek:20170601204824p:plain まとめです。 イベントは時を超えて到達するデータの集まりでした。 データが通るフローをコンテナ・エンジン・モジュールで作成してやることで、通常のコレクションのように操作できます。 イベントは時間の経過とともに変化するデータでした。 時間の経過では変化しない関係性をコードで表現し、エンジンに渡すことで状態管理が簡単になります。

イベントの見方を変えることで、イベントが持つ複雑性を緩和できるパラダイム、それがリアクティブプログラミングの考え方だと言えます。

f:id:smallgeek:20170601204857p:plain 仕事でリアクティブプログラミングしているのですが、割と GUI を扱う処理で便利に使えています。 あとはモデル部分のステートフルな関係を維持するのにも一役買ってくれています。 昔 Excel を置き換えるだけの案件があったのですが、それにも使えたんじゃないかと思っています。 リアクティブプログラミングライブラリでは、時を超えたデータを扱うために非同期処理をサポートしていますが、 ゴールデンハンマー的な使い方をせず、言語標準の機能を使ったほうが楽な場合も多々ありました。 使えるところを見極められれば強力な武器になると思っています。

f:id:smallgeek:20170601204930p:plain F# でリアクティブプログラミングしようと思った時に、標準の機能で事足りればそれでいいのですが、 もっと強力、あるいはもっと凶悪なことがしたいと思った時はオープンソースソフトウェアの力を借りることになります。 ここでは僕が使ったことのある OSS を 2つ紹介します。 Fsharp.Control.Reactive は Reactive Extensions のラッパー + α を備えたライブラリです。 SodiumFRP は純粋な Functional Reactive Programming をサポートするライブラリです。 どちらにも共通して言えることは、F# だけに留まらず他のプログラミング言語にも実装が存在するため、 パラダイムを理解しておけば、イディオムの差はあれど、導入に大きな障壁はないのかな、と思っています。

f:id:smallgeek:20170601204953p:plain 今回は時間に着目してイベントを見てきました。 イベントは時を超えてやってきたり時を超えて変化したりするのだから、タイトルは Time-over のほうがよかったんじゃない、と思われるかもしれません。 リアクティブプログラミングのパラダイムは Observer パターンから生まれたもので、Observer パターンは GoF の時代……1995年ですので、今からもう 23年前になります。 また、ファンクショナルリアクティブプログラミングは Functional Reactive Animation という論文が萌芽となっていますが、これもまた 1997年ですので、 20年前に生まれたパラダイムです。 そして現代、Reactive Extensions をはじめとする、リアクティブプログラミングをサポートするライブラリが広まっています。 リアクティブプログラミングのパラダイムが時を超えてやってきた、そして今も使われている。なので、Timeless という言葉を使用させていただきました。 でも本当はクリストファー・アレクサンダーの “The Timeless Way of Building” “時を超えた建設の道” のオマージュです。

Xamarin.Forms DataPages のデータソースをユーザー定義型にする

はじめに

公式ドキュメントによると、DataPages は 3種類のデータソースに対応しています。

  • JsonDataSource
  • AzureDataSource
  • AzureEasyTableDataSource

使い方はシンプルで、データソースの URL を指定するだけです。

<p:ListDataPage.DataSource>
    <p:JsonDataSource Source="http://demo3143189.mockable.io/sessions" />
</p:ListDataPage.DataSource>

簡単ですね。この XAML を眺めていると、ユーザー定義型の DataSource を使用できるように見えてきます。今回はユーザー定義型 DataSource を作成して DataPages に表示させることを目標とします。

<p:ListDataPage.DataSource>
    <l:CsvDataSource Path="https://raw.githubusercontent.com/smallgeek/DataPagesSample/master/sample.csv" />
</p:ListDataPage.DataSource>

こんな感じ。誰もが一度はお世話になるであろう CSV を使用します。

コード

CsvDataSource

まずは BaseDataSource から派生した CsvDataSource を作成します。

// https://github.com/smallgeek/DataPagesSample/blob/master/src/DataPagesSample/CsvDataSource.cs
public class CsvDataSource : BaseDataSource
{
    protected override async Task<IList<IDataItem>> GetRawData()
    {
        if (initialized == false)
        {
            IsLoading = true;

            var csv = await LoadCsv();

            csv.Rows
               .Select(row => new CsvDataRow(row, csv))
               .Select((data, i) => new DataItem(i.ToString(), data))
               .ForEach(item => dataItems.Add(item));

            IsLoading = false;

            initialized = true;
        }

        return dataItems;
    }

    protected override object GetValue(string key) { ... }
    
    protected override bool SetValue(string key, object value) { ... }
}

自分でパーサーを書いてもいいのですが、今回は FSharp.DataCSV パーサー & リーダーを使用しました。また、C# から F# の非同期処理を呼び出すときに便利なライブラリ FSharp.Control.FusionTasks も使用しています。これがあると F# の非同期処理を async / await で呼び出し可能になります (C# の async と F# の async は別物)。

CsvDataRow

次に、CSV 1行を表す CsvDataRow 型を作成します。

public class CsvDataRow : BaseDataSource
{
    private CsvRow raw;
    private CsvFile parent;

    public CsvDataRow(CsvRow raw, CsvFile parent)
    {
        this.raw = raw;
        this.parent = parent;
    }

    protected override Task<IList<IDataItem>> GetRawData()
    {
        // 型をうまく推論してくれない……
        IList<IDataItem> data = raw
                .Columns.Select((c, i) => new DataItem(parent.Headers.Value[i], c))
                .Cast<IDataItem>()
                .ToList();

        return Task.FromResult(data);
    }

    protected override object GetValue(string key) { ... }
    
    protected override bool SetValue(string key, object value) { ... }
}

ここで重要なのは、行を表すデータでも BaseDataSource から派生する必要があることです。単純なデータ型を使用した場合、DataPages の一覧表示はされますが、詳細表示したときに真っ白な画面になってしまいます。

実行

事前に CSV と画像を GitHub にあげておき、実行します。

f:id:smallgeek:20161127100407p:plain f:id:smallgeek:20161127100411p:plain

表示できましたね。データソースの title・presenter・image がタイトル・詳細・画像に対応しています。

DataTemplate

この一覧表示だと詳細ページの意味があまりないのと、詳細ページの画像の比率が気になるので、DataPages の DataTemplate を変更します。

<p:ListDataPage.DefaultItemTemplate>
    <DataTemplate>
        <ViewCell>
            <p:ListItemControl
                Title="{p:DataSourceBinding title}"
                Detail="{p:DataSourceBinding presenter}"
                ImageSource="{p:DataSourceBinding image}"
                DataSource="{Binding Value}"
                HeightRequest="90" />
        </ViewCell>
    </DataTemplate>
</p:ListDataPage.DefaultItemTemplate>

<p:ListDataPage.DetailTemplate>
    <DataTemplate>
        <l:DetailPage
            Title="Step Details"
            DataSource="{Binding Value}"
            DefaultItemTemplate="{StaticResource DetailCell}">
            <p:HeroImage 
                Aspect="AspectFill"
                Text="{p:DataSourceBinding title}" 
                Detail="{p:DataSourceBinding presenter}" 
                ImageSource="{p:DataSourceBinding image}" 
                DataSource="{Binding Value}" />
        </l:DetailPage>
    </DataTemplate>
</p:ListDataPage.DetailTemplate>

f:id:smallgeek:20161127101105p:plain f:id:smallgeek:20161127101109p:plain

こんな感じで拡張できます。DataPages のコントロールについては公式ドキュメントに詳しく描かれています。

サンプルで使用されている画像は本人が描いたもので、キャラクターデザインはありまさんです。念のため。本当は福祉に関するマークの意味を調べられるアプリを考案していたのですが、使用するには面倒な手続きが必要みたいです。

まとめ

はまりどころもありましたが、DataPages を拡張するのは難しくありません。DataPages 自体は公式がやる気あるのか定かではないところですが、いろいろなデータソースに対応させてパッケージを公開するのも楽しそうです。

ソースコードはこちら

github.com