Belajar Elm Dari Drum Sequencer (Bagian 1)
Diterbitkan: 2022-03-10Jika Anda seorang pengembang front-end yang mengikuti evolusi aplikasi satu halaman (SPA), kemungkinan Anda pernah mendengar tentang Elm, bahasa fungsional yang mengilhami Redux. Jika belum, ini adalah bahasa kompilasi-ke-JavaScript yang sebanding dengan proyek SPA seperti React, Angular, dan Vue.
Seperti itu, ia mengelola perubahan status melalui dom virtualnya yang bertujuan untuk membuat kode lebih mudah dipelihara dan berkinerja. Ini berfokus pada kebahagiaan pengembang, perkakas berkualitas tinggi, dan pola sederhana yang dapat diulang. Beberapa perbedaan utamanya termasuk diketik secara statis, pesan kesalahan yang sangat membantu, dan bahwa itu adalah bahasa fungsional (sebagai lawan dari Berorientasi Objek).
Perkenalan saya datang melalui ceramah yang diberikan oleh Evan Czaplicki, pencipta Elm, tentang visinya untuk pengalaman pengembang front-end dan pada gilirannya, visi untuk Elm. Karena seseorang juga berfokus pada pemeliharaan dan kegunaan pengembangan front-end, pembicaraannya benar-benar selaras dengan saya. Saya mencoba Elm dalam proyek sampingan setahun yang lalu dan terus menikmati fitur dan tantangannya dengan cara yang belum pernah saya lakukan sejak pertama kali memulai pemrograman; Saya seorang pemula lagi. Selain itu, saya menemukan diri saya dapat menerapkan banyak praktik Elm dalam bahasa lain.
Mengembangkan Kesadaran Ketergantungan
Ketergantungan ada di mana-mana. Dengan menguranginya, Anda dapat meningkatkan kemungkinan situs Anda dapat digunakan oleh banyak orang dalam berbagai skenario terluas.Baca artikel terkait →
Dalam artikel dua bagian ini, kita akan membuat step sequencer untuk memprogram ketukan drum di Elm, sambil menampilkan beberapa fitur terbaik bahasa tersebut. Hari ini, kita akan membahas konsep dasar di Elm, yaitu memulai, menggunakan tipe, merender tampilan, dan memperbarui status. Bagian kedua dari artikel ini kemudian akan membahas topik yang lebih lanjut, seperti menangani refactor besar dengan mudah, menyiapkan acara berulang, dan berinteraksi dengan JavaScript.
Mainkan tugas akhir di sini, dan lihat kodenya di sini.
Memulai Dengan Elm
Untuk mengikuti artikel ini, saya sarankan menggunakan Ellie, pengalaman pengembang Elm dalam browser. Anda tidak perlu menginstal apa pun untuk menjalankan Ellie, dan Anda dapat mengembangkan aplikasi yang berfungsi penuh di dalamnya. Jika Anda lebih suka menginstal Elm di komputer Anda, cara terbaik untuk menyiapkannya adalah dengan mengikuti panduan memulai resmi.
Sepanjang artikel ini, saya akan menautkan ke versi Ellie yang sedang dalam proses, meskipun saya mengembangkan sequencer secara lokal. Dan sementara CSS dapat ditulis seluruhnya dalam Elm, saya telah menulis proyek ini di PostCSS. Ini memerlukan sedikit konfigurasi ke Reaktor Elm untuk pengembangan lokal agar gaya dimuat. Demi singkatnya, saya tidak akan menyentuh gaya dalam artikel ini, tetapi tautan Ellie menyertakan semua gaya CSS yang diperkecil.
Elm adalah ekosistem mandiri yang meliputi:
- Elm Membuat
Untuk mengkompilasi kode Elm Anda. Sementara Webpack masih populer untuk memproduksi proyek Elm bersama aset lain, itu tidak diperlukan. Dalam proyek ini, saya telah memilih untuk mengecualikan Webpack, dan mengandalkanelm make
untuk mengkompilasi kode. - Paket Elm
Manajer paket yang sebanding dengan NPM untuk menggunakan paket/modul yang dibuat komunitas. - Reaktor Elm
Untuk menjalankan server pengembangan yang dikompilasi secara otomatis. Lebih penting lagi, ini termasuk Debugger Perjalanan Waktu yang memudahkan untuk menelusuri status aplikasi Anda dan memutar ulang bug. - Elm Repl
Untuk menulis atau menguji ekspresi Elm sederhana di terminal.
Semua file Elm dianggap sebagai modules
. Baris awal file apa pun akan menyertakan module FileName exposing (functions)
di mana FileName
adalah nama file literal, dan functions
adalah fungsi publik yang ingin Anda buat agar dapat diakses oleh modul lain. Segera setelah definisi modul adalah impor dari modul eksternal. Fungsi lainnya menyusul.
module Main exposing (main) import Html exposing (Html, text) main : Html msg main = text "Hello, World!"
Modul ini, bernama Main.elm
, mengekspos satu fungsi, main
, dan mengimpor Html
dan text
dari modul/paket Html
. Fungsi main
terdiri dari dua bagian: anotasi jenis dan fungsi sebenarnya. Jenis anotasi dapat dianggap sebagai definisi fungsi. Mereka menyatakan tipe argumen dan tipe pengembalian. Dalam hal ini, kami menyatakan fungsi main
tidak mengambil argumen dan mengembalikan Html msg
. Fungsi itu sendiri membuat simpul teks yang berisi "Halo, Dunia." Untuk meneruskan argumen ke suatu fungsi, kami menambahkan nama yang dipisahkan spasi sebelum tanda sama dengan dalam fungsi. Kami juga menambahkan tipe argumen ke anotasi tipe, dalam urutan argumen, diikuti oleh panah.
add2Numbers : Int -> Int -> Int add2Numbers first second = first + second
Dalam JavaScript, fungsi seperti ini sebanding:
function add2Numbers(first, second) { return first + second; }
Dan dalam bahasa yang Diketik, seperti TypeScript, sepertinya:
function add2Numbers(first: number, second: number): number { return first + second; }
add2Numbers
mengambil dua bilangan bulat dan mengembalikan bilangan bulat. Nilai terakhir dalam anotasi selalu merupakan nilai kembalian karena setiap fungsi harus mengembalikan nilai. Kami memanggil add2Numbers
dengan 2 dan 3 untuk mendapatkan 5 seperti add2Numbers 2 3
.
Sama seperti Anda mengikat komponen React, kita perlu mengikat kode Elm yang dikompilasi ke DOM. Cara standar untuk mengikat adalah dengan memanggil embed()
pada modul kita dan meneruskan elemen DOM ke dalamnya.
<script> const container = document.getElementById('app'); const app = Elm.Main.embed(container); <script>
Meskipun aplikasi kita tidak benar-benar melakukan apa-apa, kita memiliki cukup untuk mengkompilasi kode Elm dan merender teks. Lihat di Ellie dan coba ubah argumennya menjadi add2Numbers
di baris 26.
Pemodelan Data Dengan Jenis
Berasal dari bahasa yang diketik secara dinamis seperti JavaScript atau Ruby, jenis mungkin tampak berlebihan. Bahasa-bahasa tersebut menentukan jenis fungsi yang diambil dari nilai yang diteruskan selama waktu berjalan. Fungsi penulisan umumnya dianggap lebih cepat, tetapi Anda kehilangan keamanan untuk memastikan fungsi Anda dapat berinteraksi dengan baik satu sama lain.
Sebaliknya, Elm diketik secara statis. Itu bergantung pada kompilernya untuk memastikan nilai yang diteruskan ke fungsi kompatibel sebelum waktu berjalan. Ini berarti tidak ada pengecualian waktu proses untuk pengguna Anda, dan begitulah cara Elm dapat membuat jaminan "tidak ada pengecualian waktu proses". Di mana kesalahan ketik di banyak kompiler bisa sangat samar, Elm berfokus untuk membuatnya mudah dipahami dan diperbaiki.
Elm membuat memulai dengan tipe sangat ramah. Faktanya, inferensi tipe Elm sangat bagus sehingga Anda dapat melewatkan penulisan anotasi sampai Anda merasa lebih nyaman dengannya. Jika Anda baru mengenal tipe, saya sarankan untuk mengandalkan saran kompiler daripada mencoba menulisnya sendiri.
Mari kita mulai memodelkan data kita menggunakan tipe. Sequencer langkah kami adalah garis waktu visual tentang kapan sampel drum tertentu harus dimainkan. Garis waktu terdiri dari trek , masing-masing ditugaskan dengan sampel drum tertentu dan urutan langkah . Sebuah langkah dapat dianggap sebagai momen dalam waktu atau ketukan. Jika langkah aktif , sampel harus dipicu selama pemutaran, dan jika langkah tidak aktif , sampel harus tetap diam. Selama pemutaran, sequencer akan bergerak melalui setiap langkah memainkan sampel langkah-langkah aktif. Kecepatan pemutaran diatur oleh Beats Per Minute (BPM) .
Memodelkan Aplikasi Kami di JavaScript
Untuk mendapatkan ide yang lebih baik tentang tipe kami, mari pertimbangkan bagaimana memodelkan sequencer drum ini dalam JavaScript. Ada array trek. Setiap objek trek berisi informasi tentang dirinya sendiri: nama trek, sampel/klip yang akan dipicu, dan urutan nilai langkah.
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... ]
Kita perlu mengatur status pemutaran antara bermain dan berhenti.
playback: "playing" || "stopped"
Selama pemutaran, kita perlu menentukan langkah mana yang harus dimainkan. Kami juga harus mempertimbangkan kinerja pemutaran, dan daripada melintasi setiap urutan di setiap trek setiap kali langkah bertambah; kita harus mengurangi semua langkah aktif menjadi satu urutan pemutaran. Setiap koleksi dalam urutan pemutaran mewakili semua sampel yang harus dimainkan. Misalnya, ["kick", "hat"]
berarti sampel tendangan dan hi-hat harus dimainkan, sedangkan ["hat"]
berarti hanya hi-hat yang boleh dimainkan. Kami juga membutuhkan setiap koleksi untuk membatasi keunikan sampel, jadi kami tidak berakhir dengan sesuatu seperti ["hat", "hat", "hat"]
.
playbackPosition: 1 playbackSequence: [ ["kick", "hat"], [], ["hat"], [], ["snare", "hat"], [], ["hat"], [], ... ],
Dan kita perlu mengatur kecepatan pemutaran, atau BPM.
bpm: 120
Pemodelan Dengan Jenis Di Elm
Mentranskripsikan data ini ke dalam tipe Elm pada dasarnya menggambarkan apa yang kami harapkan dari data kami. Misalnya, kita sudah mengacu pada model data kita sebagai model , jadi kita menyebutnya dengan tipe alias. Ketik alias digunakan untuk membuat kode lebih mudah dibaca. Mereka bukan tipe primitif seperti boolean atau integer; mereka hanyalah nama yang kami berikan tipe primitif atau struktur data. Dengan menggunakan satu, kami mendefinisikan data apa pun yang mengikuti struktur model kami sebagai model daripada sebagai struktur anonim. Dalam banyak proyek Elm, struktur utama diberi nama Model.
type alias Model = { tracks : Array Track , playback : Playback , playbackPosition : PlaybackPosition , bpm : Int , playbackSequence : Array (Set Clip) }
Meskipun model kami terlihat sedikit seperti objek JavaScript, itu menggambarkan Elm Record. Catatan digunakan untuk mengatur data terkait ke dalam beberapa bidang yang memiliki anotasi jenisnya sendiri. Mereka mudah diakses menggunakan field.attribute
, dan mudah diperbarui yang akan kita lihat nanti. Objek dan catatan sangat mirip, dengan beberapa perbedaan utama:
- Bidang yang tidak ada tidak dapat dipanggil
- Bidang tidak akan pernah menjadi
null
atauundefined
-
this
danself
tidak dapat digunakan
Koleksi trek kami dapat terdiri dari salah satu dari tiga jenis yang mungkin: Daftar, Array, dan Set. Singkatnya, Daftar adalah koleksi penggunaan umum yang tidak diindeks, Array diindeks, dan Set hanya berisi nilai unik. Kami membutuhkan indeks untuk mengetahui langkah trek mana yang telah diubah, dan karena array diindeks, ini adalah pilihan terbaik kami. Atau, kita dapat menambahkan id ke trek dan memfilter dari Daftar.
Dalam model kami, kami telah mengeset trek ke larik track , record lain: tracks : Array Track
. Track berisi informasi tentang dirinya sendiri. Nama dan klip keduanya adalah string, tetapi kami telah mengetik klip alias karena kami tahu itu akan direferensikan di tempat lain dalam kode oleh fungsi lain. Dengan aliasing, kita mulai membuat kode yang mendokumentasikan diri sendiri. Membuat tipe dan alias tipe memungkinkan pengembang untuk memodelkan model data ke model bisnis, menciptakan bahasa yang ada di mana-mana.
type alias Track = { name : String , clip : Clip , sequence : Array Step } type Step = On | Off type alias Clip = String
Kita tahu urutannya akan menjadi array nilai on/off. Kita bisa mengaturnya sebagai array boolean, seperti sequence : Array Bool
, tapi kita akan kehilangan kesempatan untuk mengekspresikan model bisnis kita! Mengingat step sequencer terbuat dari step , kami mendefinisikan tipe baru yang disebut Step . A Step bisa menjadi tipe alias untuk boolean
, tapi kita bisa melangkah lebih jauh: Langkah memiliki dua nilai yang mungkin, aktif dan nonaktif, jadi begitulah cara kita mendefinisikan tipe gabungan. Sekarang langkah hanya bisa Hidup atau Mati, membuat semua status lainnya tidak mungkin.
Kami mendefinisikan tipe lain untuk Playback
, alias untuk PlaybackPosition
, dan menggunakan Clip saat mendefinisikan playbackSequence
sebagai Array yang berisi kumpulan Clips. BPM ditetapkan sebagai standar Int
.
type Playback = Playing | Stopped type alias PlaybackPosition = Int
Meskipun ada sedikit lebih banyak biaya untuk memulai dengan tipe, kode kita jauh lebih mudah dipelihara. Ini mendokumentasikan diri dan menggunakan bahasa di mana-mana dengan model bisnis kami. Keyakinan yang kita peroleh dalam mengetahui fungsi masa depan kita akan berinteraksi dengan data kita dengan cara yang kita harapkan, tanpa memerlukan tes, sepadan dengan waktu yang dibutuhkan untuk menulis anotasi. Dan, kita dapat mengandalkan inferensi tipe kompiler untuk menyarankan tipe sehingga menulisnya semudah menyalin dan menempel. Inilah deklarasi tipe lengkap.
Menggunakan Arsitektur Elm
Arsitektur Elm adalah pola manajemen keadaan sederhana yang secara alami muncul dalam bahasa. Ini menciptakan fokus di sekitar model bisnis dan sangat skalabel. Berbeda dengan kerangka kerja SPA lainnya, Elm berpendapat tentang arsitekturnya — begitulah cara semua aplikasi terstruktur, yang membuat on-boarding menjadi mudah. Arsitektur terdiri dari tiga bagian:
- Model , berisi status aplikasi, dan struktur yang kita ketik model alias
- Fungsi pembaruan , yang memperbarui status
- Dan fungsi tampilan , yang membuat status secara visual
Mari kita mulai membangun drum sequencer dengan mempelajari Arsitektur Elm dalam praktiknya. Kita akan mulai dengan menginisialisasi aplikasi kita, merender tampilan, kemudian memperbarui status aplikasi. Berasal dari latar belakang Ruby, saya cenderung memilih file yang lebih pendek dan membagi fungsi Elm saya menjadi modul meskipun sangat normal untuk memiliki file Elm besar. Saya telah membuat titik awal di Ellie, tetapi secara lokal saya telah membuat file-file berikut:
- Types.elm, berisi semua definisi tipe
- Main.elm, yang menginisialisasi dan menjalankan program
- Update.elm, berisi fungsi pembaruan yang mengelola status
- View.elm, berisi kode Elm untuk dirender menjadi HTML
Inisialisasi Aplikasi Kami
Yang terbaik adalah memulai dari yang kecil, jadi kami mengurangi model untuk fokus pada pembuatan trek tunggal yang berisi langkah-langkah yang mengaktifkan dan menonaktifkan. Meskipun kami sudah berpikir bahwa kami mengetahui seluruh struktur data, memulai dari yang kecil memungkinkan kami untuk fokus pada rendering trek sebagai HTML. Ini mengurangi kerumitan dan Anda Tidak Akan Membutuhkannya kode. Nanti, kompiler akan memandu kita melalui refactoring model kita. Dalam file Types.elm, kami menyimpan jenis Langkah dan Klip kami tetapi mengubah model dan trek.
type alias Model = { track : Track } type alias Track = { name : String , sequence : Array Step } type Step = On | Off type alias Clip = String
Untuk merender Elm sebagai HTML, kami menggunakan paket Elm Html. Ini memiliki opsi untuk membuat tiga jenis program yang saling membangun:
- Program Pemula
Program yang dikurangi yang mengecualikan efek samping dan sangat berguna untuk mempelajari Arsitektur Elm. - Program
Program standar yang menangani efek samping, berguna untuk bekerja dengan database atau alat yang ada di luar Elm. - Program Dengan Bendera
Program yang diperluas yang dapat menginisialisasi dirinya sendiri dengan data nyata, bukan data default.
Ini adalah praktik yang baik untuk menggunakan jenis program yang paling sederhana karena mudah untuk mengubahnya nanti dengan kompiler. Ini adalah praktik umum saat memprogram di Elm; gunakan hanya yang Anda butuhkan dan ubah nanti. Untuk tujuan kami, kami tahu kami harus berurusan dengan JavaScript, yang dianggap sebagai efek samping, jadi kami membuat Html.program
. Di Main.elm kita perlu menginisialisasi program dengan meneruskan fungsi ke bidangnya.
main : Program Never Model Msg main = Html.program { init = init , view = view , update = update , subscriptions = always Sub.none }
Setiap bidang dalam program melewati fungsi ke Elm Runtime, yang mengontrol aplikasi kita. Singkatnya, Elm Runtime:
- Memulai program dengan nilai awal dari
init
. - Membuat tampilan pertama dengan meneruskan model yang diinisialisasi ke
view
. - Terus merender ulang tampilan saat pesan diteruskan untuk
update
dari tampilan, perintah, atau langganan.
Secara lokal, fungsi view
dan update
kita akan diimpor dari View.elm
dan Update.elm
masing-masing, dan kita akan membuatnya sebentar lagi. subscriptions
mendengarkan pesan yang menyebabkan pembaruan, tetapi untuk saat ini, kami mengabaikannya dengan menetapkan always Sub.none
. Fungsi pertama kami, init
, menginisialisasi model. Pikirkan init
seperti nilai default untuk pemuatan pertama. Kami mendefinisikannya dengan satu lagu bernama "kick" dan urutan langkah Off. Karena kami tidak mendapatkan data asinkron, kami secara eksplisit mengabaikan perintah dengan Cmd.none
untuk menginisialisasi tanpa efek samping.
init : ( Model, Cmd.Cmd Msg ) init = ( { track = { sequence = Array.initialize 16 (always Off) , name = "Kick" } } , Cmd.none )
Anotasi jenis init kami cocok dengan program kami. Ini adalah struktur data yang disebut tuple, yang berisi sejumlah nilai tetap. Dalam kasus kami, Model
dan perintah. Untuk saat ini, kami selalu mengabaikan perintah dengan menggunakan Cmd.none
sampai kami siap untuk menangani efek samping nanti. Aplikasi kami tidak merender apa pun, tetapi mengkompilasi!
Membuat Aplikasi Kami
Mari kita membangun pandangan kita. Pada titik ini, model kita memiliki satu track, jadi hanya itu yang perlu kita render. Struktur HTML akan terlihat seperti:
<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>
Kami akan membangun tiga fungsi untuk merender tampilan kami:
- Satu untuk membuat satu trek, yang berisi nama trek dan urutannya
- Lain untuk membuat urutan itu sendiri
- Dan satu lagi untuk membuat setiap tombol langkah individu dalam urutan
Fungsi tampilan pertama kami akan membuat satu trek. Kami mengandalkan anotasi jenis kami, renderTrack : Track -> Html Msg
, untuk menerapkan satu jalur yang dilewati. Menggunakan jenis berarti kita selalu tahu bahwa renderTrack
akan memiliki trek. Kami tidak perlu memeriksa apakah bidang name
ada pada rekaman, atau jika kami telah mengirimkan string, bukan rekaman. Elm tidak akan dikompilasi jika kita mencoba untuk meneruskan apa pun selain Track
ke renderTrack
. Lebih baik lagi, jika kita membuat kesalahan dan secara tidak sengaja mencoba meneruskan apa pun selain trek ke fungsi tersebut, kompiler akan memberi kita pesan ramah untuk mengarahkan kita ke arah yang benar.
renderTrack : Track -> Html Msg renderTrack track = div [ class "track" ] [ p [ class "track-title" ] [ text track.name ] , div [ class "track-sequence" ] (renderSequence track.sequence) ]
Ini mungkin tampak jelas, tetapi semua Elm adalah Elm, termasuk menulis HTML. Tidak ada bahasa templating atau abstraksi untuk menulis HTML — semuanya Elm. Elemen HTML adalah fungsi Elm, yang mengambil nama, daftar atribut, dan daftar anak. Jadi div [ class "track" ] []
menghasilkan <div class="track"></div>
. Daftar dipisahkan dengan koma di Elm, jadi menambahkan id ke div akan terlihat seperti div [ class "track", id "my-id" ] []
.
Urutan track-sequence
trek ke fungsi kedua kita, renderSequence
. Dibutuhkan urutan dan mengembalikan daftar tombol HTML. Kita dapat menyimpan renderSequence
di renderTrack
untuk melewati fungsi tambahan, tetapi menurut saya memecah fungsi menjadi bagian-bagian yang lebih kecil jauh lebih mudah untuk dipikirkan. Selain itu, kami mendapatkan kesempatan lain untuk mendefinisikan anotasi jenis yang lebih ketat.
renderSequence : Array Step -> List (Html Msg) renderSequence sequence = Array.indexedMap renderStep sequence |> Array.toList
Kami memetakan setiap langkah dalam urutan dan meneruskannya ke fungsi renderStep
. Dalam pemetaan JavaScript dengan indeks akan ditulis seperti:
sequence.map((node, index) => renderStep(index, node))
Dibandingkan dengan JavaScript, pemetaan di Elm hampir terbalik. Kami memanggil Array.indexedMap
, yang mengambil dua argumen: fungsi yang akan diterapkan di peta ( renderStep
), dan array untuk dipetakan ( sequence
). renderStep
adalah fungsi terakhir kami dan menentukan apakah tombol aktif atau tidak aktif. Kami menggunakan indexedMap
karena kami harus menurunkan indeks langkah (yang kami gunakan sebagai ID) ke langkah itu sendiri untuk meneruskannya ke fungsi pembaruan.
renderStep : Int -> Step -> Html Msg renderStep index step = let classes = if step == On then "step _active" else "step" in button [ class classes ] []
renderStep
menerima indeks sebagai argumen pertamanya, langkah sebagai argumen kedua, dan mengembalikan HTML yang dirender. Menggunakan blok let...in
untuk mendefinisikan fungsi lokal, kita menetapkan kelas _active
ke On Steps, dan memanggil fungsi kelas kita dalam daftar atribut button.
Memperbarui Status Aplikasi
Pada titik ini, aplikasi kita merender 16 langkah dalam urutan tendangan, tetapi mengklik tidak mengaktifkan langkah tersebut. Untuk memperbarui status langkah, kita perlu mengirimkan pesan ( Msg
) ke fungsi pembaruan. Kami melakukan ini dengan mendefinisikan pesan dan melampirkannya ke event handler untuk tombol kami.
Di Types.elm, kita perlu mendefinisikan pesan pertama kita, ToggleStep
. Ini akan membutuhkan Int
untuk indeks urutan dan Step
. Selanjutnya, di renderStep
, kami melampirkan pesan ToggleStep
ke acara klik tombol, bersama dengan indeks urutan dan langkah sebagai argumen. Ini akan mengirim pesan ke fungsi pembaruan kami, tetapi pada titik ini, pembaruan tidak akan benar-benar melakukan apa pun.
type Msg = ToggleStep Int Step renderStep index step = let ... in button [ onClick (ToggleStep index step) , class classes ] []
Pesan adalah tipe reguler, tetapi kami mendefinisikannya sebagai tipe yang menyebabkan pembaruan, yang merupakan konvensi di Elm. Di Update.elm kami mengikuti Arsitektur Elm untuk menangani perubahan status model. Fungsi pembaruan kami akan mengambil Msg
dan Model
saat ini, dan mengembalikan model baru dan berpotensi menjadi perintah. Perintah menangani efek samping, yang akan kita lihat di bagian dua. Kami tahu kami akan memiliki beberapa jenis Msg
, jadi kami menyiapkan blok huruf besar-kecil yang cocok dengan pola. Ini memaksa kami untuk menangani semua kasus kami sambil juga memisahkan aliran status. Dan kompiler akan memastikan kami tidak melewatkan kasus apa pun yang dapat mengubah model kami.
Memperbarui catatan di Elm dilakukan sedikit berbeda dari memperbarui objek di JavaScript. Kami tidak dapat secara langsung mengubah bidang pada catatan seperti record.field = *
karena kami tidak dapat menggunakan this
atau self
, tetapi Elm memiliki pembantu bawaan. Diberikan catatan seperti brian = { name = "brian" }
, kita dapat memperbarui bidang nama seperti { brian | name = "BRIAN" }
{ brian | name = "BRIAN" }
. Formatnya mengikuti { record | field = newValue }
{ record | field = newValue }
.
Ini adalah cara memperbarui bidang tingkat atas, tetapi bidang bersarang lebih sulit di Elm. Kita perlu mendefinisikan fungsi pembantu kita sendiri, jadi kita akan mendefinisikan empat fungsi pembantu untuk menyelami catatan bersarang:
- Satu untuk mengaktifkan nilai langkah
- Satu untuk mengembalikan urutan baru, berisi nilai langkah yang diperbarui
- Lain untuk memilih trek mana yang menjadi milik urutan
- Dan satu fungsi terakhir untuk mengembalikan trek baru, berisi urutan yang diperbarui yang berisi nilai langkah yang diperbarui
Kita mulai dengan ToggleStep
untuk mengubah nilai langkah urutan trek antara Hidup dan Mati. Kami menggunakan blok let...in
lagi untuk membuat fungsi yang lebih kecil dalam pernyataan kasus. Jika langkahnya sudah Off, kita buat On, dan sebaliknya.
toggleStep = if step == Off then On else Off
toggleStep
akan dipanggil dari newSequence
. Data tidak dapat diubah dalam bahasa fungsional, jadi alih-alih mengubah urutan, kami sebenarnya membuat urutan baru dengan nilai langkah yang diperbarui untuk menggantikan yang lama.
newSequence = Array.set index toggleStep selectedTrack.sequence
newSequence
menggunakan Array.set
untuk menemukan indeks yang ingin kita alihkan, lalu membuat urutan baru. Jika set tidak menemukan indeks, ia mengembalikan urutan yang sama. Itu bergantung pada selectedTrack.sequence
untuk mengetahui urutan mana yang harus dimodifikasi. selectedTrack
adalah fungsi pembantu utama kami yang digunakan sehingga kami dapat mencapai catatan bersarang kami. Pada titik ini, sangat sederhana karena model kami hanya memiliki satu trek.
selectedTrack = model.track
Fungsi pembantu terakhir kami menghubungkan yang lainnya. Sekali lagi, karena data tidak dapat diubah, kami mengganti seluruh trek kami dengan trek baru yang berisi urutan baru.
newTrack = { selectedTrack | sequence = newSequence }
newTrack
dipanggil di luar blok let...in
, tempat kami mengembalikan model baru, yang berisi trek baru, yang merender ulang tampilan. Kami tidak melewatkan efek samping, jadi kami menggunakan Cmd.none
lagi. Seluruh fungsi update
kami terlihat seperti:
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 )
Saat kami menjalankan program kami, kami melihat trek yang dirender dengan serangkaian langkah. Mengklik salah satu tombol langkah akan memicu ToggleStep
, yang mengenai fungsi pembaruan kami untuk mengganti status model.
Saat aplikasi kita diskalakan, kita akan melihat bagaimana pola berulang Arsitektur Elm membuat status penanganan menjadi sederhana. Keakraban dalam model, pembaruan, dan fungsi tampilan membantu kami fokus pada domain bisnis kami dan memudahkan untuk melompat ke aplikasi Elm orang lain.
Istirahat
Menulis dalam bahasa baru membutuhkan waktu dan latihan. Proyek pertama yang saya kerjakan adalah klon TypeForm sederhana yang saya gunakan untuk mempelajari sintaks Elm, arsitektur, dan paradigma pemrograman fungsional. Pada titik ini, Anda sudah cukup belajar untuk melakukan hal serupa. Jika Anda bersemangat, saya sarankan untuk membaca Panduan Memulai Resmi. Evan, pencipta Elm, memandu Anda melalui motivasi untuk Elm, sintaksis, tipe, Arsitektur Elm, penskalaan, dan banyak lagi, menggunakan contoh praktis.
Di bagian kedua, kita akan menyelami salah satu fitur terbaik Elm: menggunakan kompiler untuk memfaktorkan ulang step sequencer kita. Selain itu, kita akan belajar bagaimana menangani kejadian yang berulang, menggunakan perintah untuk efek samping, dan berinteraksi dengan JavaScript. Pantau terus!