非常に興味深く革新的なプロジェクトの主任開発者がAngularJSからElmへの切り替えを提案したとき、私の最初の考えは次のとおりでした。なぜですか?
ソリッドステートで、十分にテストされ、本番環境で実証された、うまく記述されたAngularJSアプリケーションがすでにありました。そして、AngularJSからの価値のあるアップグレードであるAngular 4は、書き換えの自然な選択であった可能性があります。ReactまたはVueも同様です。 Elmは、人々がほとんど聞いたことのない奇妙なドメイン固有言語のように見えました。
ええと、それは私がエルムについて何も知る前でした。さて、特にAngularJSから移行した後は、ある程度の経験があれば、その「理由」に答えることができると思います。
λ aですか、それともわかりませんか?
この記事では、Elmの長所と短所、およびそのエキゾチックな概念のいくつかがどのようにニーズに完全に適合するかについて説明します。 フロントエンドWeb開発者 。よりチュートリアルのようなElm言語の記事については、以下をご覧ください。 このブログ投稿 。
JavaまたはJavaScriptでのプログラミングに慣れていて、それがコードを書く自然な方法であると感じている場合、Elmを学ぶことはうさぎの穴に落ちるようなものです。
最初に気付くのは、奇妙な構文です。中括弧がなく、矢印や三角形がたくさんあります。
中括弧なしで生活することを学ぶかもしれませんが、コードのブロックをどのように定義してネストしますか?または、for
はどこにありますかループ、または他のループ、そのことについては?明示的なスコープはlet
で定義できますがブロック、古典的な意味でのブロックはなく、ループもまったくありません。
Elmは、純粋に関数型で、強く型付けされ、反応的で、イベント駆動型のWebクライアント言語です。
この方法でプログラミングが可能かどうか疑問に思うかもしれません。
実際には、これらの品質が合わさって、優れたソフトウェアのプログラミングと開発の驚くべきパラダイムが得られます。
新しいバージョンのJavaまたはECMAScript6を使用することで、関数型プログラミングを実行できると思うかもしれません。しかし、それはその表面にすぎません。
それらのプログラミング言語では、言語構造の武器庫にアクセスでき、その機能しない部分に頼りたいという誘惑に駆られます。違いに本当に気付くのは、関数型プログラミング以外に何もできないときです。最終的に、関数型プログラミングがいかに自然であるかを感じ始めるのはその時点です。
Elmでは、ほとんどすべてが関数です。レコード名は関数であり、共用体型の値は関数です。すべての関数は、引数に部分的に適用された関数で構成されています。プラス(+)やマイナス(-)のような演算子も関数です。
プログラミング言語をそのような構造の存在ではなく純粋に機能的なものとして宣言するには、他のすべてが存在しないことが最も重要です。そうして初めて、純粋に機能的な方法で考え始めることができます。
Elmは関数型プログラミングの成熟した概念に基づいてモデル化されており、HaskellやOCamlなどの他の関数型言語に似ています。
JavaまたはTypeScriptでプログラミングする場合は、これが何を意味するかを知っています。すべての変数は正確に1つのタイプでなければなりません。
もちろん、いくつかの違いがあります。 TypeScriptと同様に、型宣言はオプションです。存在しない場合は、推測されます。しかし、「任意の」タイプはありません。
Javaはジェネリック型をサポートしていますが、より良い方法です。 Javaのジェネリックは後で追加されたため、特に指定がない限り、型はジェネリックではありません。そして、それらを使用するには、醜い構文が必要です。
Elmでは、特に指定がない限り、タイプは汎用です。例を見てみましょう。特定のタイプのリストを取得して数値を返すメソッドが必要だとします。 Javaでは次のようになります。
public static int numFromList(List list){ return list.size(); }
そして、エルム言語で:
numFromList list = List.length list
オプションですが、常に型宣言を追加することを強くお勧めします。 Elmコンパイラは、間違った型の操作を許可しません。人間にとって、特に言語を学んでいる間は、そのような間違いを犯すのははるかに簡単です。したがって、タイプアノテーションを使用した上記のプログラムは次のようになります。
numFromList: List a -> Int numFromList list = List.length list
最初は別の行で型を宣言するのは珍しいように思えるかもしれませんが、しばらくすると自然に見え始めます。
これが意味するのは、ElmがJavaScriptにコンパイルされるため、ブラウザーがWebページで実行できるということです。
そのため、Node.jsを使用したJavaやJavaScriptのような汎用言語ではなく、Webアプリケーションのクライアント部分を作成するためのドメイン固有言語です。それ以上に、Elmには、ビジネスロジック(JavaScriptの機能)とプレゼンテーション部分(HTMLの機能)の両方を1つの関数型言語で記述することが含まれています。
これらはすべて、エルムアーキテクチャと呼ばれる非常に特殊なフレームワークのような方法で行われます。
androidstudioユニットテストチュートリアル
Elm Architectureは、リアクティブなWebフレームワークです。モデルの変更は、明示的なDOM操作なしで、ページにすぐにレンダリングされます。
そのように、AngularやReactに似ています。しかし、エルムも独自の方法でそれを行います。その基本を理解するための鍵は、view
の署名にあります。およびupdate
機能:
view : Model -> Html Msg update : Msg -> Model -> Model
Elmビューは、モデルのHTMLビューだけではありません。種類のメッセージを生成できるのはHTMLですMsg
ここで、Msg
は、定義する正確な共用体型です。
標準のページイベントであれば、メッセージを生成できます。また、メッセージが生成されると、Elmはそのメッセージを使用して更新関数を内部的に呼び出し、メッセージと現在のモデルに基づいてモデルを更新し、更新されたモデルが再び内部でビューにレンダリングされます。
JavaScriptとよく似て、Elmはイベント駆動型です。ただし、たとえば、非同期アクションに個別のコールバックが提供されるNode.jsとは異なり、Elmイベントは、1つのメッセージタイプで定義された個別のメッセージセットにグループ化されます。また、他の共用体型と同様に、個別の型値が運ぶ情報は何でもかまいません。
メッセージを生成できるイベントのソースは3つあります。Html
でのユーザーアクション表示、コマンドの実行、およびサブスクライブした外部イベント。そのため、Html
、Cmd
、Sub
の3つのタイプすべてがあります。含むmsg
彼らの議論として。そして、一般的なmsg
タイプは、3つの定義すべてで同じである必要があります。つまり、更新関数に提供されるものと同じであり(前の例では、大文字のMが付いたMsg
タイプでした)、すべてのメッセージ処理が集中化されます。
あなたはこれで完全なElmウェブアプリケーションの例を見つけることができます GitHubリポジトリ 。シンプルですが、RESTエンドポイントからのデータの取得、JSONデータのデコードとエンコード、ビュー、メッセージ、その他の構造の使用、JavaScriptとの通信、コンパイルとパッケージ化に必要なすべての機能など、日常のクライアントプログラミングで使用されるほとんどの機能を示しています。 Webpackを使用したElmコード。
アプリケーションは、サーバーから取得したユーザーのリストを表示します。
セットアップ/デモプロセスを簡単にするために、Webpackの開発サーバーを使用してElmを含むすべてをパッケージ化し、ユーザーのリストを提供します。
一部の機能はElmにあり、一部はJavaScriptにあります。これは、相互運用性を示すという1つの重要な理由で意図的に行われます。 Elmを起動するか、既存のJavaScriptコードを徐々に切り替えるか、Elm言語で新しい機能を追加することをお勧めします。相互運用性により、アプリは引き続きElmコードとJavaScriptコードの両方で動作します。これは、Elmでアプリ全体を最初から開始するよりもおそらく優れたアプローチです。
サンプルコードのElm部分は、最初にJavaScriptの構成データで初期化され、次にユーザーのリストが取得され、Elm言語で表示されます。 JavaScriptにすでにいくつかのユーザーアクションが実装されているとしましょう。そのため、Elmでユーザーアクションを呼び出すと、コールがJavaScriptにディスパッチされます。
このコードでは、次のセクションで説明する概念と手法の一部も使用しています。
実世界のシナリオで、Elmプログラミング言語のエキゾチックな概念のいくつかを見ていきましょう。
これはエルム語の純金です。構造的に異なるデータを同じアルゴリズムで使用する必要がある状況をすべて覚えていますか?これらの状況をモデル化することは常に困難です。
次に例を示します。リストのページ付けを作成していると想像してください。各ページの終わりに、前、次、およびすべてのページへのリンクが番号で示されているはずです。ユーザーがクリックしたリンクの情報を保持するようにどのように構成しますか?
前、次、ページ番号のクリックに複数のコールバックを使用したり、1つまたは2つのブールフィールドを使用してクリックした内容を示したり、負の数やゼロなどの特定の整数値に特別な意味を与えたりすることができます。これらのソリューションは、まさにこの種のユーザーイベントをモデル化できます。
エルムでは、それはとても簡単です。共用体タイプを定義します。
type NextPage = Prev | Next | ExactPage Int
そして、それをメッセージの1つのパラメーターとして使用します。
type Msg = ... | ChangePage NextPage
最後に、関数を更新してcase
を設定しますnextPage
のタイプを確認するには:
update msg model = case msg of ChangePage nextPage -> case nextPage of Prev -> ... Next -> ... ExactPage newPage -> ...
それは物事を非常にエレガントにします。
<|
を使用した複数のマップ関数の作成多くのモジュールにはmap
が含まれています関数。異なる数の引数に適用するいくつかのバリアントがあります。たとえば、List
map
、map2
、…、最大map5
があります。しかし、6つの引数を取る関数がある場合はどうなるでしょうか。 map6
はありません。しかし、それを克服するためのテクニックがあります。 <|
を使用しますパラメータとしての関数、および部分関数。一部の引数は中間結果として適用されます。
簡単にするために、List
と仮定します。 map
しかありませんおよびmap2
であり、3つのリストで3つの引数を取る関数を適用します。
実装は次のようになります。
map3 foo list1 list2 list3 = let partialResult = List.map2 foo list1 list2 in List.map2 (<|) partialResult list3
foo
を使用するとします。これは、次のように定義された数値引数を乗算するだけです。
foo a b c = a * b * c
したがって、map3 foo [1,2,3,4,5] [1,2,3,4,5] [1,2,3,4,5]
の結果[1,8,27,64,125] : List number
です。
ここで何が起こっているのかを分解してみましょう。
まず、partialResult = List.map2 foo list1 list2
、foo
でlist1
のすべてのペアに部分的に適用されますおよびlist2
。結果は[foo 1 1, foo 2 2, foo 3 3, foo 4 4, foo 5 5]
です。これは、1つのパラメーター(最初の2つは既に適用されているため)を取り、数値を返す関数のリストです。
次のList.map2 (<|) partialResult list3
では、実際にはList.map2 (<|) [foo 1 1, foo 2 2, foo 3 3, foo 4 4, foo 5 5] list3
です。これら2つのリストのすべてのペアについて、(<|)
と呼びます。関数。たとえば、最初のペアの場合、(<|) (foo 1 1) 1
はfoo 1 1 <| 1
と同じであり、foo 1 1 1
と同じで1
を生成します。 2番目の場合は、(<|) (foo 2 2) 2
、つまりfoo 2 2 2
、つまり8
と評価されます。
この方法は、mapN
で特に役立ちます。 Json.Decode
のように多くのフィールドを持つJSONオブジェクトをデコードするための関数map8
まで提供します。
Maybe
のリストがあるとしましょう値を取得し、値を含む要素から値のみを抽出します。たとえば、リストは次のとおりです。
会社におけるCFOの役割は何ですか
list : List (Maybe Int) list = [ Just 1, Nothing, Just 3, Nothing, Nothing, Just 6, Just 7 ]
そして、[1,3,6,7] : List Int
を取得したいと思います。解決策は次の1行の式です。
List.filterMap identity list
これが機能する理由を見てみましょう。
List.filterMap
最初の引数は、指定されたリストの要素(2番目の引数)に適用される関数(a -> Maybe b)
であると想定され、結果のリストはすべてのNothing
を省略するようにフィルター処理されます。値を入力すると、Maybe
sから実際の値が抽出されます。
この場合、identity
を指定したので、結果のリストは再び[ Just 1, Nothing, Just 3, Nothing, Nothing, Just 6, Just 7 ]
になります。フィルタリングした後、[ Just 1, Just 3, Just 6, Just 7 ]
を取得し、値を抽出した後、必要に応じて[1,3,6,7]
になります。
JSONデコード(または逆シリアル化)のニーズがJson.Decode
で公開されているものを超え始めるとモジュールでは、新しいエキゾチックデコーダーの作成に問題が発生する可能性があります。これは、これらのデコーダーがデコードプロセスの途中から呼び出されるためです(例:Http
内)。メソッドであり、特に提供されたJSONに多くのフィールドがある場合は、それらの入力と出力が常に明確であるとは限りません。
このような場合の処理方法を示す2つの例を次に示します。
最初のフィールドには、着信JSONにa
という2つのフィールドがあります。およびb
は、長方形の領域の辺を示します。ただし、Elmオブジェクトでは、その領域のみを格納する必要があります。
import Json.Decode exposing (..) areaDecoder = map2 (*) (field 'a' int) (field 'b' int) result = decodeString areaDecoder '''{ 'a':7,'b':4 }''' -- Ok 28 : Result.Result String Int
フィールドはfield int
で個別にデコードされますデコーダー、そして両方の値がmap2
で提供された関数に提供されます。乗算(*
)も関数であり、2つのパラメーターを必要とするため、このように使用できます。結果のareaDecoder
適用されたときに関数の結果を返すデコーダーです。この場合はa*b
です。
2番目の例では、nullまたは空を含む任意の文字列である可能性のある乱雑なステータスフィールドを取得していますが、操作が成功したのは「OK」の場合のみです。その場合、True
として保存します。他のすべての場合はFalse
として。デコーダーは次のようになります。
okDecoder = nullable string |> andThen (ms -> case ms of Nothing -> succeed False Just s -> if s == 'OK' then succeed True else succeed False )
それをいくつかのJSONに適用しましょう:
decodeString (field 'status' okDecoder) '''{ 'a':7, 'status':'OK' }''' -- Ok True decodeString (field 'status' okDecoder) '''{ 'a':7, 'status':'NOK' }''' -- Ok False decodeString (field 'status' okDecoder) '''{ 'a':7, 'status':null }''' -- Ok False
ここで重要なのは、andThen
に提供される関数です。この関数は、以前のnull許容文字列デコーダー(Maybe String
)の結果を取得し、それを必要なものに変換して、結果をデコーダーとして返します。 succeed
の助けを借りて。
クレジットカードのトップアップハック
これらの例からわかるように、機能的な方法でのプログラミングは、JavaおよびJavaScript開発者にとってあまり直感的ではない場合があります。慣れるまでには少し時間がかかり、試行錯誤がたくさんあります。それを理解しやすくするために、elm-repl
を使用できます式の戻り値の型を実行して確認します。
この記事の前半にリンクされているサンプルプロジェクトには、カスタムデコーダーとエンコーダーの例がいくつか含まれており、それらを理解するのにも役立ちます。
Elm言語は、他のクライアントフレームワークとは大きく異なるため、「まだ別のJavaScriptライブラリ」ではありません。このように、それはそれらと比較したときにポジティブまたはネガティブと見なすことができる多くの特徴を持っています。
まずはポジティブな面から始めましょう。
最後に、あなたはそれをすべて行うことができる言語を持っています。これ以上の分離、およびそれらの混合の厄介な組み合わせはありません。 JavaScriptでHTMLを生成することも、いくつかの簡略化されたロジックルールを使用してカスタムテンプレート言語を作成することもありません。
Elmを使用すると、構文と言語が1つだけになります。
ほとんどすべての概念は関数といくつかの構造に基づいているため、構文は非常に簡潔です。何らかのメソッドがインスタンスまたはクラスレベルで定義されているのか、それとも単なる関数であるのかを心配する必要はありません。これらはすべて、モジュールレベルで定義された関数です。また、リストを反復処理する方法は100通りありません。
ほとんどの言語では、コードがその言語の方法で記述されているかどうかについて、常にこの議論があります。多くのイディオムを習得する必要があります。
Elmでは、コンパイルする場合、それはおそらく「Elm」の方法です。そうでなければ、まあ、そうではありません。
簡潔ですが、Elm構文は非常に表現力豊かです。
これは主に、共用体型、正式な型宣言、および関数型を使用することで実現されます。これらはすべて、より小さな関数の使用を促します。最後に、ほとんど自己文書化されたコードを取得します。
JavaまたはJavaScriptを非常に長い間使用する場合、null
プログラミングの必然的な部分である、あなたにとって完全に自然なものになります。そして、私たちは常にNullPointerException
sとさまざまなTypeError
sを見ていますが、それでも本当の問題はnull
の存在であるとは考えていません。まさにそうです ナチュラル 。
エルムとしばらくするとすぐに明らかになります。 null
がないランタイムnull参照エラーが何度も発生するのを防ぐだけでなく、実際の値がない可能性があるすべての状況を明確に定義して処理することで、より良いコードを記述できるようになります。したがって、| _ + _を延期しないことで技術的負債を削減できます。 |何かが壊れるまで処理します。
構文的に正しいJavaScriptプログラムの作成は非常に迅速に行うことができます。しかし、それは実際に機能しますか?さて、ページをリロードして徹底的にテストした後、見てみましょう。
エルムでは、それは反対です。静的型チェックと強制null
チェック、特に初心者がプログラムを書くときは、コンパイルするのに少し時間がかかります。ただし、コンパイルすると、正しく機能する可能性が高くなります。
これは、クライアントフレームワークを選択する際の重要な要素になる可能性があります。大規模なWebアプリの応答性は、多くの場合、ユーザーエクスペリエンス、つまり製品全体の成功にとって非常に重要です。そして、 テストは示しています 、エルムはとても速いです。
従来のWebフレームワークのほとんどは、Webアプリを作成するための強力なツールを提供します。しかし、その力には代償が伴います。複雑すぎるアーキテクチャで、さまざまな概念やルールをいつどのように使用するかについてのルールがあります。すべてをマスターするには多くの時間がかかります。コントローラ、コンポーネント、およびディレクティブがあります。次に、コンパイルフェーズと構成フェーズ、および実行フェーズがあります。そして、提供されたディレクティブで使用されるサービス、ファクトリ、およびすべてのカスタムテンプレート言語があります。null
を呼び出す必要があるすべての状況です。直接ページを更新するなど。
ElmのJavaScriptへのコンパイルも確かに非常に複雑ですが、開発者はそのすべての要点を知る必要から保護されています。 Elmをいくつか書いて、コンパイラーに任せてください。
エルムを称賛するのに十分です。それでは、それほど優れていない側面をいくつか見てみましょう。
これは本当に大きな問題です。 Elm言語には詳細なマニュアルがありません。
公式チュートリアルでは、言語をざっと見て、未回答の質問をたくさん残しています。
公式のAPIリファレンスはさらに悪いです。多くの関数には、説明や例がありません。そして、次のような文があります。「これが紛らわしい場合は、Elmアーキテクチャチュートリアルを実行してください。それは本当に役に立ちます!」公式のAPIドキュメントで見たい最高の行ではありません。
うまくいけば、これはすぐに変わるでしょう。
Elmは、このような貧弱なドキュメント、特にそのような概念や機能がまったく直感的ではないJavaやJavaScriptから来た人々に広く採用できるとは思いません。それらを把握するには、多くの例を含むはるかに優れたドキュメントが必要です。
中括弧や括弧を取り除き、インデントに空白を使用すると見栄えがよくなります。たとえば、Pythonコードは非常にきれいに見えます。しかし、$scope.$apply()
の作成者にとっては、それだけでは不十分でした。
すべての二重行スペース、および式と割り当てが複数の行に分割されているため、Elmコードは水平よりも垂直に見えます。古き良きCのワンライナーは、Elm言語では複数の画面に簡単に拡大できます。
あなたが書かれたコードの行によって支払われるならば、これは良いように聞こえるかもしれません。ただし、150行前に開始された式に何かを揃えたい場合は、適切なインデントを見つけてください。
彼らと一緒に働くのは難しいです。レコードのフィールドを変更するための構文は見苦しいです。ネストされたフィールドを変更したり、名前で任意のフィールドを参照したりする簡単な方法はありません。また、アクセサ関数を一般的な方法で使用している場合は、適切な入力に多くの問題があります。
sdrレシーバーとは
JavaScriptでは、レコードまたはオブジェクトは、さまざまな方法で構築、アクセス、および変更できる中心的な構造です。 JSONでさえ、レコードのシリアル化されたバージョンにすぎません。開発者はWebプログラミングでレコードを操作することに慣れているため、レコードを主要なデータ構造として使用すると、Elmでのレコードの処理の難しさが目立つようになる可能性があります。
Elmでは、JavaScriptよりも多くのコードを記述する必要があります。
文字列と数値の演算には暗黙的な型変換がないため、多くのint-float変換、特にelm-format
があります。呼び出しが必要です。呼び出しは、正しい数の引数と一致するように括弧または関数適用記号を必要とします。また、toString
関数には引数として文字列が必要です。 Html.text
s、Maybe
、タイプなど、すべてのケース式が必要です。
これの主な理由は厳密な型システムであり、それは支払うべき公正な価格である可能性があります。
より多くのタイピングが本当に際立っている領域の1つは、JSON処理です。単にResults
とはJavaScriptでの呼び出しは、Elm言語で数百行に及ぶ可能性があります。
もちろん、JSON構造とElm構造の間の何らかのマッピングが必要です。しかし、同じJSONに対してデコーダーとエンコーダーの両方を作成する必要があることは深刻な問題です。 REST APIが数百のフィールドを持つオブジェクトを転送する場合、これは大変な作業になります。
Elmについて見てきましたが、おそらくプログラミング言語自体と同じくらい古い、よく知られた質問に答える時が来ました。それは競合他社よりも優れているのでしょうか。プロジェクトで使用する必要がありますか?
すべてのツールがハンマーであるとは限らず、すべてが釘であるとは限らないため、最初の質問に対する答えは主観的かもしれません。 Elmは輝いていて、他のWebクライアントフレームワークよりも優れた選択肢である場合が多く、他のフレームワークでは不十分です。しかし、それはWebフロントエンド開発を他の方法よりもはるかに安全で簡単にすることができるいくつかの本当にユニークな価値を提供します。
2番目の質問については、古くからの「状況によって異なります」との回答を避けるために、簡単な答えは次のとおりです。はい。言及されたすべての不利な点があっても、Elmがあなたのプログラムが正しいことについてあなたに与える自信だけがそれを使うのに十分な理由です。
Elmでのコーディングも楽しいです。これは、「従来の」Webプログラミングパラダイムに慣れている人にとってはまったく新しい視点です。
実際の使用では、アプリ全体をすぐにElmに切り替えたり、アプリ内で完全に新しいアプリを起動したりする必要はありません。 JavaScriptとの相互運用性を活用して、インターフェースの一部またはElm言語で記述された機能から始めて試してみることができます。あなたはそれがあなたのニーズに合っているかどうかをすぐに見つけて、それからその使用法を広げるか、それを残すでしょう。そして、誰が知っているか、あなたはまた、機能的なウェブプログラミングの世界に恋をするかもしれません。
関連: フロントエンド開発のためのClojureScriptの発掘Elmは、純粋に関数型で、強く型付けされ、反応的で、イベント駆動型のWebクライアント言語です。