ドラムシーケンサーからエルムを学ぶ(パート1)

公開: 2022-03-10
クイックサマリー↬フロントエンド開発者のBrianHoltが、Elmでドラムシーケンサーを構築する方法を読者に説明します。 この2部構成のシリーズのパート1では、Elmの構文、セットアップ、およびコアの概念を紹介します。 簡単なアプリケーションを作成するためにElmアーキテクチャを操作する方法を学習します。

シングルページアプリケーション(SPA)の進化に続くフロントエンド開発者であれば、Reduxに影響を与えた関数型言語であるElmについて聞いたことがあるでしょう。 まだの場合は、React、Angular、VueなどのSPAプロジェクトに匹敵するJavaScriptへのコンパイル言語です。

それらのように、コードをより保守可能でパフォーマンスの高いものにすることを目的として、仮想domを介して状態の変化を管理します。 これは、開発者の幸福、高品質のツール、およびシンプルで繰り返し可能なパターンに焦点を当てています。 その主な違いのいくつかには、静的に型付けされた、素晴らしく役立つエラーメッセージと、(オブジェクト指向ではなく)関数型言語であることが含まれます。

私の紹介は、Elmの作成者であるEvan Czaplickiによる、フロントエンド開発者エクスペリエンスに対する彼のビジョン、ひいてはElmに対するビジョンについての講演を通じて行われました。 フロントエンド開発の保守性と使いやすさにも焦点を当てている人がいたので、彼の話は本当に私に共感しました。 私は1年前にサイドプロジェクトでElmを試しましたが、プログラミングを始めて以来、その機能と課題の両方を楽しんでいます。 私はまたもや初心者です。 さらに、私はエルムの実践の多くを他の言語で適用できることに気づきました。

依存関係の認識の開発

依存関係はどこにでもあります。 それらを減らすことで、さまざまなシナリオで最も多くの人がサイトを使用できる可能性を高めることができます。関連記事を読む→

この2部構成の記事では、Elmでドラムビートをプログラムするためのステップシーケンサーを作成し、言語の最高の機能のいくつかを紹介します。 今日は、Elmの基本的な概念、つまり、開始、型の使用、ビューのレンダリング、状態の更新について説明します。 次に、この記事の第2部では、大規模なリファクターの簡単な処理、定期的なイベントの設定、JavaScriptの操作など、より高度なトピックについて詳しく説明します。

ここで最終プロジェクトを試して、ここでそのコードを確認してください。

動作中のステップシーケンサー
完成したステップシーケンサーの動作は次のようになります。

Elm入門

この記事をフォローするために、ブラウザー内のElm開発者エクスペリエンスであるEllieを使用することをお勧めします。 エリーを実行するために何もインストールする必要はなく、その中で完全に機能するアプリケーションを開発することができます。 コンピュータにElmをインストールする場合は、公式のスタートガイドに従うのが最善の方法です。

ジャンプした後もっと! 以下を読み続けてください↓

シーケンサーはローカルで開発しましたが、この記事全体を通して、進行中のEllieバージョンにリンクします。 CSSは完全にElmで記述できますが、私はこのプロジェクトをPostCSSで記述しました。 これには、スタイルをロードするために、ローカル開発用にElmReactorを少し構成する必要があります。 簡潔にするために、この記事ではスタイルについては触れませんが、エリーのリンクにはすべての縮小されたCSSスタイルが含まれています。

Elmは、以下を含む自己完結型のエコシステムです。

  • ニレメイク
    Elmコードをコンパイルするため。 Webpackは、他のアセットと一緒にElmプロジェクトを作成するために今でも人気がありますが、必須ではありません。 このプロジェクトでは、Webpackを除外し、コードのコンパイルをelm makeに依存することを選択しました。
  • エルムパッケージ
    コミュニティで作成されたパッケージ/モジュールを使用するためのNPMに匹敵するパッケージマネージャー。
  • エルムリアクター
    自動コンパイル開発サーバーを実行します。 さらに注目すべきは、Time Traveling Debuggerが含まれているため、アプリケーションの状態を簡単に調べたり、バグを再生したりできることです。
  • Elm Repl
    ターミナルで単純なElm式を記述またはテストするため。

すべてのElmファイルはmodulesと見なされます。 すべてのファイルの最初の行には、 module FileName exposing (functions)が含まれます。ここで、 FileNameはリテラルファイル名であり、 functionsは他のモジュールにアクセスできるようにするパブリック関数です。 モジュール定義の直後は、外部モジュールからのインポートです。 残りの機能は次のとおりです。

 module Main exposing (main) import Html exposing (Html, text) main : Html msg main = text "Hello, World!"

Main.elmという名前のこのモジュールは、単一の関数mainを公開し、 Htmlモジュール/パッケージからHtmltextをインポートします。 main機能は、型アノテーションと実際の機能の2つの部分で構成されています。 タイプアノテーションは、関数定義と考えることができます。 それらは引数の型と戻りの型を述べています。 この場合、 main関数は引数をとらず、 Html msgを返すと述べています。 関数自体は、「Hello、World」を含むテキストノードをレンダリングします。 関数に引数を渡すために、関数の等号の前にスペースで区切られた名前を追加します。 また、引数の後に矢印が続く順に、引数の型を型注釈に追加します。

 add2Numbers : Int -> Int -> Int add2Numbers first second = first + second

JavaScriptでは、次のような関数は同等です。

 function add2Numbers(first, second) { return first + second; }

また、TypeScriptのような型付き言語では、次のようになります。

 function add2Numbers(first: number, second: number): number { return first + second; }

add2Numbersは2つの整数を取り、整数を返します。 すべての関数は値を返す必要があるため、アノテーションの最後の値は常に戻り値です。 add2Numbers 2 3のように5を取得するには、2と3でadd2Numbersを呼び出します。

Reactコンポーネントをバインドするのと同じように、コンパイルされたElmコードをDOMにバインドする必要があります。 バインドする標準的な方法は、モジュールでembed()を呼び出し、その中にDOM要素を渡すことです。

 <script> const container = document.getElementById('app'); const app = Elm.Main.embed(container); <script>

私たちのアプリは実際には何もしませんが、Elmコードをコンパイルしてテキストをレンダリングするのに十分です。 エリーでそれをチェックして、26行目のadd2Numbersに引数を変更してみてください。

Elmアプリは追加された数字をレンダリングします
追加された数字を画面に表示する、必要最低限​​のElmアプリ。

タイプを使用したデータモデリング

JavaScriptやRubyのような動的に型付けされた言語から来ているので、型は不必要に見えるかもしれません。 これらの言語は、実行に渡される値からどの型関数が取得するかを決定します。 関数の記述は一般的に高速であると考えられていますが、関数が相互に適切に相互作用できることを保証するセキュリティが失われます。

対照的に、Elmは静的に型付けされています。 関数に渡される値が実行前に互換性があることを確認するために、コンパイラーに依存しています。 これは、ユーザーにランタイム例外がないことを意味し、Elmが「ランタイム例外なし」を保証する方法です。 多くのコンパイラのタイプエラーが特に不可解である可能性がある場合、Elmはそれらを理解して修正しやすくすることに重点を置いています。

Elmは、タイプの使用を非常にフレンドリーにします。 実際、Elmの型推論は非常に優れているため、注釈に慣れるまで注釈の記述をスキップできます。 型を初めて使用する場合は、自分で作成するのではなく、コンパイラの提案に頼ることをお勧めします。

タイプを使用してデータのモデリングを始めましょう。 ステップシーケンサーは、特定のドラムサンプルをいつ再生するかを視覚的に示すタイムラインです。 タイムラインはトラックで構成され、各トラックには特定のドラムサンプルと一連のステップが割り当てられています。 ステップは、瞬間またはビートと見なすことができます。 ステップがアクティブな場合、サンプルは再生中にトリガーされ、ステップが非アクティブな場合、サンプルはサイレントのままである必要があります。 再生中、シーケンサーはアクティブなステップのサンプルを再生しながら各ステップを移動します。 再生速度は、 Beats Per Minute(BPM)によって設定されます。

ステップのシーケンスを持つトラックで構成される最終的なアプリケーション
ステップのシーケンスを含むトラックで構成された、最終的なアプリケーションのスクリーンショット。

JavaScriptでのアプリケーションのモデリング

私たちのタイプをよりよく理解するために、JavaScriptでこのドラムシーケンサーをモデル化する方法を考えてみましょう。 トラックの配列があります。 各トラックオブジェクトには、トラック名、トリガーされるサンプル/クリップ、ステップ値のシーケンスなど、それ自体に関する情報が含まれています。

 tracks: [ { name: "Kick", clip: "kick.mp3", sequence: [On, Off, Off, Off, On, etc...] }, { name: "Snare", clip: "snare.mp3", sequence: [Off, Off, Off, Off, On, etc...] }, etc... ]

再生から停止までの再生状態を管理する必要があります。

 playback: "playing" || "stopped"

再生中に、どのステップを再生するかを決定する必要があります。 また、ステップがインクリメントされるたびに各トラックの各シーケンスをトラバースするのではなく、再生パフォーマンスも考慮する必要があります。 すべてのアクティブなステップを1つの再生シ​​ーケンスに減らす必要があります。 再生シーケンス内の各コレクションは、再生する必要のあるすべてのサンプルを表します。 たとえば、 ["kick", "hat"]はキックとハイハットのサンプルが再生されることを意味し、 ["hat"]はハイハットのみが再生されることを意味します。 また、サンプルの一意性を制限するために各コレクションが必要なので、 ["hat", "hat", "hat"]ようなものになってしまうことはありません。

 playbackPosition: 1 playbackSequence: [ ["kick", "hat"], [], ["hat"], [], ["snare", "hat"], [], ["hat"], [], ... ],

そして、再生のペース、つまりBPMを設定する必要があります。

 bpm: 120

Elmのタイプを使用したモデリング

このデータをElmタイプに転記することは、基本的に、データが何でできているかを説明することです。 たとえば、すでにデータモデルをモデルと呼んでいるので、タイプエイリアスを使用してそれを呼び出します。 タイプエイリアスは、コードを読みやすくするために使用されます。 それらはブール値や整数のようなプリミティブ型ではありません。 これらは、プリミティブ型またはデータ構造に付ける名前です。 1つを使用して、モデル構造に従うデータを匿名構造ではなくモデルとして定義します。 多くのElmプロジェクトでは、主要な構造はModelという名前です。

 type alias Model = { tracks : Array Track , playback : Playback , playbackPosition : PlaybackPosition , bpm : Int , playbackSequence : Array (Set Clip) }

モデルはJavaScriptオブジェクトに少し似ていますが、Elmレコードを記述しています。 レコードは、関連データを独自のタイプ注釈を持ついくつかのフィールドに編成するために使用されます。 これらはfield.attributeを使用して簡単にアクセスでき、後で簡単に更新できます。 オブジェクトとレコードは非常に似ていますが、いくつかの重要な違いがあります。

  • 存在しないフィールドは呼び出すことができません
  • フィールドがnullまたはundefinedになることはありません
  • thisselfは使用できません

トラックのコレクションは、リスト、配列、セットの3つのタイプのいずれかで構成できます。 つまり、リストはインデックス付けされていない汎用コレクションであり、配列はインデックス付けされており、セットには一意の値のみが含まれています。 どのトラックステップが切り替えられたかを知るためのインデックスが必要です。配列にはインデックスが付けられているため、これが最善の選択です。 または、トラックにIDを追加して、リストからフィルタリングすることもできます。

このモデルでは、トラックをトラックの配列にタイプセットしました。別のレコード: tracks : Array Track 。 トラックには、それ自体に関する情報が含まれています。 nameとclipはどちらも文字列ですが、他の関数によってコード内の他の場所で参照されることがわかっているため、エイリアスクリップを入力しました。 エイリアスを作成することで、自己文書化コードの作成を開始します。 タイプとタイプエイリアスを作成すると、開発者はデータモデルをビジネスモデルにモデル化して、ユビキタス言語を作成できます。

 type alias Track = { name : String , clip : Clip , sequence : Array Step } type Step = On | Off type alias Clip = String

シーケンスはオン/オフ値の配列になることがわかっています。 sequence : Array Boolのように、ブール値の配列として設定することもできますが、ビジネスモデルを表現する機会を逃してしまいます。 ステップシーケンサーがステップで構成されていることを考慮して、 Stepと呼ばれる新しいタイプを定義します。 ステップはbooleanの型エイリアスである可能性がありますが、さらに一歩進むことができます。ステップにはオンとオフの2つの可能な値があるため、このようにして共用体型を定義します。 これで、ステップはオンまたはオフにしかできなくなり、他のすべての状態が不可能になります。

PlaybackPositionのエイリアスであるPlaybackの別のタイプを定義し、 playbackSequenceをクリップのセットを含む配列として定義するときにClipを使用します。 BPMは標準のIntとして割り当てられます。

 type Playback = Playing | Stopped type alias PlaybackPosition = Int

型の使用を開始するには少しオーバーヘッドがありますが、コードははるかに保守しやすくなっています。 それは自己文書化であり、私たちのビジネスモデルでユビキタス言語を使用しています。 将来の関数がテストを必要とせずに期待どおりにデータと相互作用することを知ることで得られる自信は、注釈を書くのにかかる時間の価値が十分にあります。 また、コンパイラの型推論を利用して型を提案できるため、型の記述はコピーと貼り付けと同じくらい簡単です。 これが完全な型宣言です。

Elmアーキテクチャの使用

Elmアーキテクチャは、言語で自然に出現する単純な状態管理パターンです。 それはビジネスモデルに焦点を合わせ、非常にスケーラブルです。 他のSPAフレームワークとは対照的に、Elmはそのアーキテクチャについて意見が分かれています。これは、すべてのアプリケーションの構造化方法であり、オンボーディングが簡単になります。 アーキテクチャは次の3つの部分で構成されています。

  • アプリケーションの状態を含むモデル、およびエイリアスモデルと入力した構造
  • 状態を更新する更新機能
  • そして、状態を視覚的にレンダリングするビュー機能

エルムアーキテクチャを実際に学習しながら、ドラムシーケンサーの構築を始めましょう。 まず、アプリケーションを初期化し、ビューをレンダリングしてから、アプリケーションの状態を更新します。 Rubyのバックグラウンドから来た私は、短いファイルを好み、Elm関数をモジュールに分割する傾向がありますが、大きなElmファイルがあるのはごく普通のことです。 エリーで開始点を作成しましたが、ローカルで次のファイルを作成しました。

  • すべての型定義を含むTypes.elm
  • Main.elmは、プログラムを初期化して実行します
  • Update.elm、状態を管理する更新関数が含まれています
  • View.elm、HTMLにレンダリングするためのElmコードが含まれています

アプリケーションの初期化

小さく始めるのが最善なので、モデルを減らして、オンとオフを切り替えるステップを含む単一のトラックの構築に焦点を合わせます。 すでにデータ構造全体を知っていると思いますが、小さいものから始めることで、トラックをHTMLとしてレンダリングすることに集中できます。 それは複雑さを軽減し、あなたはそれを必要としないでしょう。 後で、コンパイラーがモデルのリファクタリングをガイドします。 Types.elmファイルでは、ステップタイプとクリップタイプを保持しますが、モデルとトラックを変更します。

 type alias Model = { track : Track } type alias Track = { name : String , sequence : Array Step } type Step = On | Off type alias Clip = String

ElmをHTMLとしてレンダリングするには、ElmHtmlパッケージを使用します。 相互に構築する3種類のプログラムを作成するオプションがあります。

  • 初心者プログラム
    副作用を排除し、Elmアーキテクチャの学習に特に役立つ縮小プログラム。
  • プログラム
    Elmの外部に存在するデータベースまたはツールを操作するのに役立つ、副作用を処理する標準プログラム。
  • フラグ付きプログラム
    デフォルトデータの代わりに実際のデータで自身を初期化できる拡張プログラム。

後でコンパイラで簡単に変更できるため、可能な限り単純なタイプのプログラムを使用することをお勧めします。 これは、Elmでプログラミングする場合の一般的な方法です。 必要なものだけを使用し、後で変更してください。 私たちの目的のために、副作用と見なされるJavaScriptを処理する必要があることがわかっているので、 Html.programを作成します。 Main.elmでは、フィールドに関数を渡してプログラムを初期化する必要があります。

 main : Program Never Model Msg main = Html.program { init = init , view = view , update = update , subscriptions = always Sub.none }

プログラムの各フィールドは、アプリケーションを制御するElmランタイムに関数を渡します。 一言で言えば、Elmランタイム:

  • initからの初期値でプログラムを開始します。
  • 初期化されたモデルをviewに渡すことにより、最初のビューをレンダリングします。
  • ビュー、コマンド、またはサブスクリプションからupdateためにメッセージが渡されたときに、ビューを継続的に再レン​​ダリングします。

ローカルでは、 view関数とupdate関数はそれぞれView.elmUpdate.elmからインポートされ、すぐに作成します。 subscriptionsは更新を引き起こすメッセージをリッスンしますが、今のところ、 always Sub.noneを割り当てることにより、それらを無視します。 最初の関数initは、モデルを初期化します。 initは、最初のロードのデフォルト値のように考えてください。 「キック」という名前の単一のトラックと一連のオフステップで定義します。 非同期データを取得していないため、 Cmd.noneを使用したコマンドを明示的に無視して、副作用なしに初期化します。

 init : ( Model, Cmd.Cmd Msg ) init = ( { track = { sequence = Array.initialize 16 (always Off) , name = "Kick" } } , Cmd.none )

initタイプのアノテーションはプログラムと一致します。 これはタプルと呼ばれるデータ構造であり、固定数の値が含まれています。 この場合、 Modelとコマンドです。 今のところ、後で副作用を処理する準備ができるまで、 Cmd.noneを使用してコマンドを常に無視します。 私たちのアプリは何もレンダリングしませんが、コンパイルします!

アプリケーションのレンダリング

ビューを作成しましょう。 この時点で、モデルには1つのトラックがあるため、レンダリングする必要があるのはそれだけです。 HTML構造は次のようになります。

 <div class="track"> <p class "track-title">Kick</p> <div class="track-sequence"> <button class="step _active"></button> <button class="step"></button> <button class="step"></button> <button class="step"></button> etc... </div> </div>

ビューをレンダリングするための3つの関数を作成します。

  1. トラック名とシーケンスを含む単一のトラックをレンダリングするための1つ
  2. シーケンス自体をレンダリングする別の
  3. そして、シーケンス内の個々のステップボタンをレンダリングするためのもう1つ

最初の表示機能は、単一のトラックをレンダリングします。 タイプアノテーションrenderTrack : Track -> Html Msgを使用して、通過する単一のトラックを強制します。 タイプを使用するということは、 renderTrackにトラックがあることを常に知っていることを意味します。 nameフィールドがレコードに存在するかどうか、またはレコードの代わりに文字列を渡したかどうかを確認する必要はありません。 Track以外のものをrenderTrackに渡そうとすると、Elmはコンパイルされません。 さらに良いことに、間違いを犯して、誤ってトラック以外のものを関数に渡そうとした場合、コンパイラーは、正しい方向を示すためのわかりやすいメッセージを表示します。

 renderTrack : Track -> Html Msg renderTrack track = div [ class "track" ] [ p [ class "track-title" ] [ text track.name ] , div [ class "track-sequence" ] (renderSequence track.sequence) ]

当たり前のように思えるかもしれませんが、HTMLの記述を含め、すべてのElmはElmです。 HTMLを書くためのテンプレート言語や抽象化はありません—それはすべてElmです。 HTML要素はElm関数であり、名前、属性のリスト、および子のリストを取ります。 したがって、 div [ class "track" ] [] <div class="track"></div>を出力します。 Elmではリストがコンマで区切られているため、divにIDを追加すると、 div [ class "track", id "my-id" ] []ようになります。

div wrapping track-sequenceは、トラックのシーケンスを2番目の関数renderSequenceに渡します。 シーケンスを受け取り、HTMLボタンのリストを返します。 追加の関数をスキップするためにrenderSequencerenderTrackを保持することもできますが、関数をより小さな部分に分割することは、推論するのがはるかに簡単です。 さらに、より厳密な型注釈を定義する別の機会があります。

 renderSequence : Array Step -> List (Html Msg) renderSequence sequence = Array.indexedMap renderStep sequence |> Array.toList

シーケンスの各ステップにマップし、それをrenderStep関数に渡します。 JavaScriptでは、インデックスを使用したマッピングは次のように記述されます。

 sequence.map((node, index) => renderStep(index, node))

JavaScriptと比較すると、Elmでのマッピングはほぼ逆になっています。 Array.indexedMapを呼び出します。これは、マップに適用される関数( renderStep )とマップする配列( sequence )の2つの引数を取ります。 renderStepは最後の関数であり、ボタンがアクティブか非アクティブかを判別します。 更新関数に渡すためにステップインデックス(IDとして使用)をステップ自体に渡す必要があるため、 indexedMapを使用します。

 renderStep : Int -> Step -> Html Msg renderStep index step = let classes = if step == On then "step _active" else "step" in button [ class classes ] []

renderStepは、最初の引数としてインデックスを受け入れ、2番目の引数としてステップを受け入れ、レンダリングされたHTMLを返します。 let...inブロックを使用してローカル関数を定義し、 _activeクラスをOn Stepsに割り当て、ボタン属性リストでクラス関数を呼び出します。

一連のステップを含むキックトラック
一連のステップを含むキックトラック

アプリケーション状態の更新

この時点で、アプリはキックシーケンスの16ステップをレンダリングしますが、クリックしてもステップはアクティブになりません。 ステップ状態を更新するには、メッセージ( Msg )を更新関数に渡す必要があります。 これを行うには、メッセージを定義し、それをボタンのイベントハンドラーに添付します。

Types.elmで、最初のメッセージToggleStepを定義する必要があります。 シーケンスインデックスとStepにはIntが必要です。 次に、 renderStepで、メッセージToggleStepをボタンのon clickイベントに、シーケンスインデックスとステップを引数として添付します。 これにより、メッセージが更新機能に送信されますが、この時点では、更新は実際には何も実行しません。

 type Msg = ToggleStep Int Step renderStep index step = let ... in button [ onClick (ToggleStep index step) , class classes ] []

メッセージは通常のタイプですが、Elmの規則である、更新を引き起こすタイプとして定義しまし。 Update.elmでは、Elmアーキテクチャに従って、モデルの状態の変更を処理します。 更新関数は、 Msgと現在のModelを受け取り、新しいモデルと、場合によってはコマンドを返します。 コマンドは副作用を処理します。これについては、パート2で説明します。 複数のMsgタイプがあることがわかっているので、パターンマッチングのケースブロックを設定します。 これにより、状態の流れを分離しながら、すべてのケースを処理する必要があります。 そしてコンパイラーは、モデルを変更する可能性のあるケースを見逃さないようにします。

Elmでのレコードの更新は、JavaScriptでのオブジェクトの更新とは少し異なる方法で行われます。 record.field = *のようにレコードのフィールドを直接変更することはできません。これは、 thisまたはselfを使用できないためですが、Elmにはヘルパーが組み込まれています。 brian = { name = "brian" }のようなレコードが与えられた場合、 { brian | name = "BRIAN" } { brian | name = "BRIAN" } 。 形式は次のとおりです{ record | field = newValue } { record | field = newValue }

これはトップレベルのフィールドを更新する方法ですが、ネストされたフィールドはElmでは扱いにくいです。 独自のヘルパー関数を定義する必要があるため、ネストされたレコードに飛び込むために4つのヘルパー関数を定義します。

  1. ステップ値を切り替えるための1つ
  2. 更新されたステップ値を含む新しいシーケンスを返すための1つ
  3. シーケンスが属するトラックを選択するもう1つ
  4. そして、更新されたステップ値を含む更新されたシーケンスを含む、新しいトラックを返す最後の関数

ToggleStepから始めて、トラックシーケンスのステップ値をオンとオフの間で切り替えます。 再びlet...inブロックを使用して、caseステートメント内でより小さな関数を作成します。 ステップがすでにオフになっている場合はオンにし、その逆も同様です。

 toggleStep = if step == Off then On else Off

toggleStepnewSequenceから呼び出されます。 データは関数型言語では不変であるため、シーケンスを変更するのではなく、実際には、古いシーケンスを置き換えるために更新されたステップ値を使用して新しいシーケンスを作成しています。

 newSequence = Array.set index toggleStep selectedTrack.sequence

newSequenceは、 Array.setを使用して切り替えたいインデックスを見つけ、新しいシーケンスを作成します。 setがインデックスを見つけられない場合、同じシーケンスを返します。 これは、 selectedTrack.sequenceに依存して、変更するシーケンスを認識します。 selectedTrackは、ネストされたレコードにアクセスできるようにするために使用される主要なヘルパー関数です。 この時点では、モデルにトラックが1つしかないため、驚くほど簡単です。

 selectedTrack = model.track

最後のヘルパー関数は、残りすべてを接続します。 ここでも、データは不変であるため、トラック全体を新しいシーケンスを含む新しいトラックに置き換えます。

 newTrack = { selectedTrack | sequence = newSequence }

newTrackは、 let...inブロックの外側で呼び出されます。ここで、ビューを再レンダリングする新しいトラックを含む新しいモデルを返します。 副作用を渡していないので、 Cmd.noneを再度使用します。 update機能全体は次のようになります。

 update : Msg -> Model -> ( Model, Cmd Msg ) update msg model = case msg of ToggleStep index step -> let selectedTrack = model.track newTrack = { selectedTrack | sequence = newSequence } toggleStep = if step == Off then On else Off newSequence = Array.set index toggleStep selectedTrack.sequence in ( { model | track = newTrack } , Cmd.none )

プログラムを実行すると、一連のステップを含むレンダリングされたトラックが表示されます。 ステップボタンのいずれかをクリックすると、 ToggleStepがトリガーされ、更新機能が実行されてモデルの状態が置き換えられます。

一連のアクティブなステップを含むキックトラック
一連のアクティブなステップを含むキックトラック

アプリケーションが拡張するにつれて、Elmアーキテクチャの繰り返し可能なパターンによって状態の処理がどのように簡単になるかがわかります。 モデル、更新、および表示機能に精通していると、ビジネスドメインに集中でき、他の人のElmアプリケーションに簡単にアクセスできます。

休憩を取って

新しい言語で書くには時間と練習が必要です。 私が最初に取り組んだプロジェクトは、Elmの構文、アーキテクチャ、関数型プログラミングのパラダイムを学ぶために使用した単純なTypeFormクローンでした。 この時点で、あなたはすでに同様のことをするのに十分なことを学びました。 熱心な方は、公式スタートガイドをご覧になることをお勧めします。 Elmの作成者であるEvanは、実用的な例を使用して、Elmの動機、構文、タイプ、Elmアーキテクチャ、スケーリングなどについて説明します。

パート2では、Elmの最も優れた機能の1つである、コンパイラーを使用したステップシーケンサーのリファクタリングについて詳しく説明します。 さらに、繰り返し発生するイベントの処理方法、副作用のコマンドの使用方法、JavaScriptの操作方法についても学習します。 乞うご期待!