nekoroll.hatenablog.com
の続きというか修正版というか…
今回やること
- 指定したマスの色を変えられるようにする
…の前に何があったか
( ゚д゚)ツッコミどころがあったがたぶん続きを作るとわかるはず
— ABAB↑↓BA (@ababupdownba) 2020年9月9日
あらかじめ計画を練ってはいけないの綺麗な例を見た
/(^o^)\
— 🐊すがわに🐊 (@nek0roll) 2020年9月9日
ふふふ
— ABAB↑↓BA (@ababupdownba) 2020年9月9日
ちょうど直近の見ていただいた手続き脳から切り替えが出来ていない部分のミスを発見しました…w
(AB先生ありがとうございます)
AB先生が想定している問題かどうかはわからないんですが
問題に直面したので発覚した問題と解決策をメモ
Model
渡せねえ問題
🐊「Box
とRow
を作る関数を作って…それを受け取った数でリスト化する関数も作る!」
🐊「これで数も可変なマス目を作れるし完璧でしょ!クリックして色変えれるようにしよ!」
関数化しまくったのはいいけど、これどうやってModel渡すんだ…?
— 🐊すがわに🐊 (@nek0roll) 2020年9月10日
バケツリレーですね!
— TheSacredLipton (@SacredLipton) 2020年9月10日
(TheSacredLiptonさんAB先生ありがとうございます)https://t.co/crqtW1snAu
— ABAB↑↓BA (@ababupdownba) 2020年9月10日
よかったらさんこうに
問題点
Model
をバケツリレーする必要が産まれて物凄い使いづらい関数になっていた
驚くほど不便。単に描画できるだけで何も操作できないマス目になっていた- 関数を細かく作成したのに、結局それをリスト化する関数から呼び出しているだけなので、使い勝手が最悪
makeBox
をList
の関数でうまく操作してマス目を作るべきだった気がする
makeBoxByCount
が一見使いやすかったんだけど拡張性が最悪 - 引数がプリミティブな型で扱いづらい
qiita.com
実際実装中もInt -> Int
が凄い分かりづらくて苦労してました
記事にもある通り今回作った関数は拡張されていく可能性が大いにあるにも関わらず、プリミティブな型なので拡張性が最悪
🐊「ああ……これはアンチパターンですね……なんだこれは……たまげたなぁ」
ということで色々考えて、拡張性や使いやすさを自分なりに意識して直してみた
ellie-app.com
Box
を作る所から考えていく
Model
にはBox
リストを持つようにして、これを軸に処理していく
表示も操作もこれ。前はBoxSize
だけ持っていて操作が出来なかったBox
リストを作る関数を作るBox
リストを表示する関数を作る
こんな感じでまずはゼロから作り直した
Model
type alias Box = { x : Int , y : Int , isLightOn : Bool } type alias Model = { boxList : List Box } initialModel : Model initialModel = { boxList = [ Box 0 0 False, Box 1 0 False ] }
Box
は操作に使うため、座標と点灯中フラグを持つようにした
initialModel
関数でBox
をn個(検証なので2個で固定)生成しboxList
に詰めるようにした
これをベースに表示や操作を行っていく
Box
リストの表示
.box { border: 1px solid; width: 50px; height: 50px; }
view : Model -> Html Msg view model = div [] (List.map showBox model.boxList) showBox : Box -> Html Msg showBox box = div [ class "box" , setLampMode box.isLightOn , onClick (InvertLight box) ] []
showBox
関数でBox
をHtml Msg
に変換して表示できるようにした
表示するだけの関数なので、これをList.map
でboxList
に適用して表示できるようにした
setLampMode
とonClick
については後述
クリックで色を変更する
type Msg = InvertLight Box update : Msg -> Model -> Model update msg model = case msg of InvertLight box -> let invertLight b = if b.x == box.x && b.y == box.y then { b | isLightOn = not b.isLightOn } else b in { model | boxList = List.map invertLight model.boxList }
以前作ったTODO
リストの更新処理の応用
クリックされたBox
を受け取り、x,y
が一致するBox
の点灯中フラグを反転させる関数を作成
List.map
でboxList
に適用してboxList
再度詰め直すようにした
これでクリックしたBox
の点灯フラグを更新できるようになった
点灯フラグで背景色を変更する
setLampMode : Bool -> Attribute Msg setLampMode isLampTurnOn = if isLampTurnOn then style "background-color" "red" else style "background-color" "white"
Box.isLightOn
を受け取り、True
なら赤False
なら白の背景色に変更するようにした
これで操作可能なBox
をn個表示するという部分まで実現できた
しかしまだ2個Box
を表示できているだけなので、任意の個数のBox
に修正する
boxList
を任意の個数のBox
で詰めるようにする
row * column
個のリストを作るrow
,column
を受けて生成する関数はやめる
同じことは繰り返さない- それぞれに
x,y
を振る必要があるので2重のforeach
みたいな処理を想定 - データとしては
List Box
表示側ではList (List Box)
としたい
7*10=70
であれば、7個ずつ区切って10行にして表示するイメージ
こんな感じで考えた
ellie-app.com
n * n
個のBox
を生成する関数とModel
type alias Model = { boxList : List (List Box) } initialModel : Model initialModel = { boxList = makeRow 9 } makeRow : Int -> List (List Box) makeRow rowCount = List.map makeBox (List.range 0 rowCount) makeBox : Int -> List Box makeBox y = List.map (\x -> Box x y False) (List.range 0 6)
makeBox
でn個のBox
リストを作成(x軸)
makeRow
でn行分Box
リストを作成(y軸)
これでn * n
, row * column
個のBox
が作れた
表示する
view : Model -> Html Msg view model = div [] (List.map (\boxList -> div [ style "display" "flex" ] (List.map showBox boxList)) model.boxList)
List (List Box)
となっているのでList Box
をdiv
で囲って表示してあげた
これで任意の個数表示できるようになった
…と思ったが問題が発生
InvertLight
が処理できない
List.map
で1Box
ずつチェックしているので、ネストしたList
は想定していない
- 操作側は
List Box
が欲しい - 表示側は
List (List Box)
が欲しい
操作を軸に考える
List (List Box)
ではなくList Box
で持つ- 表示側はn個で分割して表示する
とするように修正した
ellie-app.com
List.box
でデータを持つ
type alias Model = { boxList : List Box } initialModel : Model initialModel = { boxList = List.concat (makeRow 9) }
丁度いい関数List.concat
があったので結合して
List (List Box)
からList Box
に変更した
これで操作側は問題なく扱えるようになった
表示側は分割して表示する
view : Model -> Html Msg view model = div [] (List.map (\boxList -> div [ style "display" "flex" ] (List.map showBox boxList)) (split 7 model.boxList)) split : Int -> List Box -> List (List Box) split n boxList = case List.take n boxList of [] -> [] takedList -> takedList :: split n (List.drop n boxList)
(List.map showBox boxList)) model.boxList)
だった部分が
(List.map showBox boxList)) (split 7 model.boxList))
になっただけ。分割して表示するようにした
そして今回の肝なのがsplitBox
関数
package.elm-lang.org
List
のドキュメントを探したが期待する関数が無かったので、無い知恵を絞って作った
再帰関数苦手だけど、わかりやすくて割と簡単に実装できた(時間がかからなかったとは言ってない)
これでようやくその1の完成段階に戻ってきて、かつ指定したマスを光らせることが出来るようになった
今はonClick
イベントで光らせているけど、処理を変えるだけなので大丈夫(だと思う)
次回はsubscription
を使ってn秒毎にペカペカさせる所を進めていく
おまけ
boxList
初期化のmakeRow
とmakeBox
がなんか怪しい
固定値持ってるし、なんか違和感がある
ただ今はとりあえず動くことを重視しているという点と
今後自由にサイズを変えるかどうかわからないので、まずはこのまま進めてヨシとしている- 苦労した
splitBox
関数がList.Extra
に存在していた
package.elm-lang.org
groupsOf
いい加減にしろって感じだよ(笑う
完全に車輪の再発明です/(^o^)\
でも再帰関数の勉強になったしdrop
とtake
の使い方分かった…業務じゃなくて勉強だし…
車輪の再発明しないとわからないこともあるし…ええんや…と自分を慰めています
🐊「トホホw」