26
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ElmAdvent Calendar 2017

Day 19

ElmのStreamライブラリで画面を作るなら

Posted at

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

動かしてみた

↓こんな感じ

pic.twitter.com/f5pC44UHfb

— ス (@4245Ryomt) 2017年12月19日

List version

  • 300000レコード程度流し込むと画面の更新がガクガクしだす
  • Perfomanceツールから状況を見るとheapをものすごい勢いで消費している(消費600MBまでアガる)
  • その影響かminor gcが頻発し時間が取られて画面がガクガクになっているようだ(?)
  • スクリーンショット 2017-12-19 13.06.32.png

Stream version

  • 300000レコード流しこんでも描画がガクガクになっていない
  • heapの消費を見ても100MB程度で収まっている
  • minor gc に時間がとられていないようだ
  • スクリーンショット 2017-12-19 13.05.45.png

グラフ見てて動き全然ちげえな。。。

感想

  • 適当なサンプルでの比較で、これでいいのか‥?感あるがStreamを使ってメリットを感じることができた。
  • なぜStreamだとheapを消費しないのか?
    • mapとかfilterとか読んでもすぐ新しいインスタンスにならないから?Array無駄に生成されないから?
  • どうしてこのStreamだとイイ感じなのか、説明のつくように精進いたします。
26
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?