ElmのStreamライブラリを試した
Elm Advent Calender 19日目です。
Elmのアドカレは活発ですね!
去年のelm Advent Calenderではlm-native-uiでiosアプリ作ってました -> 記事
stream
- github
- Elm package
- elmにはlazy listを扱う標準ライブラリがないから作ったぜ
- 他にも似たような機能を実現するライブラリはあるけどstack over flowに対して脆弱だ
- stack over flowしない lazy listライブラリだぜ
- ほかにもStreamライブラリはたくさんありますが、これのみ試します
どんな時に使えるだろうか?
- ドキュメントにもある通り無限に流れるデータに対してどうこうする時につかえそうだ
- elmがクライアントアプリケーションを作るのに使われるものだったら無限スクロールに表示されるようなデータだろうか? タイムライン表示のようなリアルタイムでバシバシデータが流れて来るもの?
簡単な使い方
- 一つの要素なStream
singleHoge : Stream String
singleHoge = Stream.singleton "hoge"
- リストから生成されるStream
streamFromList : Stream String
streamFromList = Stream.fromList ["hogo", "huga", "hugi"]
- Stream同士を結合
concatStream : Stream String
concatStream = Stream.concat singleHoge streamFromList
- Streamの先頭から要素を1つと要素を抜いたStreamを取る
elementAndTail : (String, Stream String)
elementAndTail = Stream.next concatStream
-- -> ("hoge", ["hogo", "huga", "hugi"])
- StreamをListに変換
listFromStream : List String
listFromStream = Stream.toList concatStream
-- -> ["hoge", "hogo", "huga", "hugi"]
- その他
- map, filter, reduce, zip... Listにある関数と同じようなものたち
タイムライン的なものを作ってみて検証
- サーバサイドから無限にデータが流れて来る(websocketで流しまくるよ)
- 表示するのは最新の10件
Listで作って見る
type alias Feed = {
id: Int,
subject: String,
text: String
}
type alias Model = {
feeds: List Feed
}
type Msg =
ReceiveFeed Feed
update msg model =
case msg of
ReceiveFeed feed ->
( { feeds = feed :: model.feeds }, Cmd.none )
feedView feed = div [] [ text feed.subject ]
feedList = List.map feedView >> List.take 10
view model = div [] (model.feeds |> feedList)
port receiveFeedPort : (Feed -> msg) -> Sub msg
init = ({ feeds = [] }, Cmd.none)
main =
Html.program
{ view = view
, init = init
, update = update
, subscriptions = always <| receiveFeedPort ReceiveFeed
}
Streamで作って見る(変更箇所のみ記載)
type alias Model = {
feeds: Stream Feed
}
update msg model =
case msg of
ReceiveFeed feed ->
( { feeds = Stream.concat (Stream.singleton feed) model.feeds }, Cmd.none )
init = ({ feeds = Stream.fromList [] }, Cmd.none)
feedList = Stream.map feedView >> Stream.nextN 10 >> Tuple.second
動かしてみた
↓こんな感じ
— ス (@4245Ryomt) 2017年12月19日
List version
- 300000レコード程度流し込むと画面の更新がガクガクしだす
- Perfomanceツールから状況を見るとheapをものすごい勢いで消費している(消費600MBまでアガる)
- その影響かminor gcが頻発し時間が取られて画面がガクガクになっているようだ(?)
Stream version
グラフ見てて動き全然ちげえな。。。
感想
- 適当なサンプルでの比較で、これでいいのか‥?感あるがStreamを使ってメリットを感じることができた。
- なぜStreamだとheapを消費しないのか?
- mapとかfilterとか読んでもすぐ新しいインスタンスにならないから?Array無駄に生成されないから?
- どうしてこのStreamだとイイ感じなのか、説明のつくように精進いたします。