エリキシル

こんにちは、今日はBeamVM(またはErlangVM)の最新のプログラミング言語について説明します。

最初の部分は基本の不完全な紹介であり、記事の2番目の部分では、アーラン開発者にとって新しい言語の主な機能を簡単な例で示します。



2年前、0.1バージョンのエリキシルがリリースされました。これは以前にhabrasocietyに導入されました。



引用:



「Erlangはその機能においてユニークなプラットフォームであり、それにもかかわらず、この言語はまだエキゾチックです。 いくつかの理由があります。 たとえば、厳密な算術演算、珍しい構文、機能。 これらは欠陥ではありません。 これらは、ほとんどのプログラマーが使用できない、または使用したくないものです。」



現時点では、elixirはBeamVMの上に構築された(当然、アーランに加えて)最も人気のあるプログラミング言語になりました。 アーランの著者であるジョー・アームストロングが記事を書き、デイブ・トーマスが本を書いた程度まで。 2年で、多くの変更が行われ、言語は非常に安定し、バージョン1.0の最終バージョンが多少なりとも増えました。 この間、オブジェクトモデルはエリキシルから消え、Rubyのような構文が残りましたが、メタプログラミングとポリモーフィズムが追加されました。これは、オブジェクト指向パラダイムとは異なり、Beam VMに適合します。



Elixirの新機能:





同時に、アーランビームコードにコンパイルされます。 elixirでは、データ型を変換せずにerlangモジュールを呼び出すこともできるため、erlangコードを呼び出すときにパフォーマンスが低下することはありません。



試してみるには、githubからダウンロードできます。



$ git clone https://github.com/elixir-lang/elixir.git $ cd elixir $ make test
      
      







または、 プリコンパイル済みバージョンをインストールします。



また、Fedora、Mac OS、またはArch Linuxの所有者の場合、パッケージマネージャーを使用してelixirをインストールできます。





Elixirには、すべてをすぐに試すことができるインタラクティブなiexコンソールがあります。 アーランとは異なり、以下に示すように、エリクサーコンソールでモジュールを作成できます。



コメント:

 # This is a commented line
      
      







さらに、「#=>」は式の意味を示します。

 1 + 1 # => 2
      
      







コンソールからの例:



 $ bin/iex defmodule Hello do def world do IO.puts "Hello World" end end Hello.world
      
      





elixirのデータ型はerlangと同じです:

 1 # integer 0x1F # integer 1.0 # float :atom # atom / symbol {1,2,3} # tuple [1,2,3] # list <<1,2,3>> # binary
      
      







erlang-eのように、エリクシルの文字列はリストまたはバイナリで表現できます:



 'I am a list' "I am a binary or a string" name = "World" "Hello, #{name}" # => string interpolation
      
      





erlangとは異なり、エリクサーは文字リストの前にその速度とコンパクトさがあるため、標準の文字列実装としてどこでもバイナリを使用します。



Aには複数行の文字列もあります。

 """ This is a binary spawning several lines. """
      
      







関数呼び出しは、モジュールについて既に上で見ましたが、角括弧を省略して可能です。

 div(10, 2) div 10, 2
      
      





elixirの優れたプログラミングスタイルでは、角かっこを省略する場合、マクロを使用することをお勧めします。

標準ライブラリのコーディングスタイルでは、関数を呼び出すには括弧が必要であると述べています。



elixirの変数はまだ不変ですが、再割り当てを行うことができます:

 x = 1 x = 2
      
      







変数は式間でのみ変更でき、1つの式の中で変数は一致します。 同時に、アーランからのパターンマッチング全体が保持され、同時に、アーランのように^を使用して不変にすることができます。

 {x, x} = {1,2} # => ** (MatchError) no match of right hand side value: {1,2} {a, b, [c | _]} = {1,2,["a", "b", "c"]} # => a = 1 b = 2 c = "a" a = 1 # => 1 a = 2 # => 2 ^a = 3 # => ** (MatchError) no match of right hand side value: 3
      
      







elixirの構文、機能、機能の詳細については、こちらをご覧ください。

公式チュートリアル

アーラン開発者向けクラッシュコース

エリキシルとの週。 ジョーアームストロングエリクサーの記事

Dave ThomasによるElixirのプログラミング。本から2つのビデオチュートリアルといくつかの抜粋があります。

公式文書



エリクサーでプログラミングを始めた後、1つの値を変更してコピーアンドペーストで作成されるアーランコードを確認します(このようなニーズは、私が遭遇したほとんどすべてのプロジェクトに存在します)。コードを増やして、エリクサー上で正しく書き直したいだけです。



そして今、私はアーラン開発者のための革新、すなわちメタプログラミング、ポリモーフィズム、そしてコードを大幅に簡素化する構文糖衣を簡単な例で示したいと思います。



メタプログラミングから始めましょう。 エリクサーでは、すべてが少なくとも可能な限り表現です(「すべてが表現です」)。

最初の例では、1つの関数を持つ最も一般的なモジュールを実験として使用します。

 defmodule Hello do def world do IO.puts "Hello World" end end
      
      







ファイルにクリーンアップし、次のようにコンパイルします。



 $ elixirc hello.ex
      
      





または、コンソールにコピーすると、モジュールがそこでコンパイルされます。 いずれにしても、コンパイル中に何が起こるかを注意深く監視します。 現時点では特別なことはありません。



サンプルを少し変更しましょう。

 defmodule Hello do IO.puts "Hello Compiler" def world do IO.puts "Hello World" end end
      
      







これで、コンパイル中に「Helloコンパイラ」が表示されます。



コンパイルに応じて、モジュール内の何かを変更してみましょう。

 defmodule Hello do if System.get_env("MY_ENV") == "1" do def world do IO.puts "Hello World with my Variable = 1" end else def world do IO.puts "Hello World" end end end
      
      





さて、コードのコンパイル方法に応じて、コードをコンパイルすると、次のことがわかります。

 $ elixirc hello.ex $ iex iex> Hello.world # => "Hello World"
      
      







または、次のようにモジュールをコンパイルすると、関数の別のアクションが取得されます。

 $ MY_ENV=1 elixirc hello.ex $ iex iex> Hello.world # => "Hello World with my Variable = 1"
      
      







次に、コードを生成するなど、もっと面白いことを試してみましょう。

アーランコードでは、次のようなコードがよく見られます。

 my_function(bad_type) -> 1; my_function(bad_stat) -> 2; ....... my_function(1) -> bad_type; my_function(2) -> bad_stat; .....
      
      







たとえば、次のように使用する関数を取得します。

 Hello.mapper(:bad_type) # => 1 Hello.mapper(:bad_stat) # => 2 Hello.mapper(1) # => :bad_type .....
      
      







elixirでは、コンパイル時に同じ関数を生成すると、繰り返さずに同じ関数速度を得ることができます。

 list = [{:bad_type, 1}, {:bad_stat, 2}] lc {type, num} inlist list do def my_function(unquote(type)), do: unquote(num) def my_function(unquote(num)), do: unquote(type) end)
      
      







lc inlist doは、エリクサー言語でのリスト圧縮です。使用例:

 lc a inlist [1,2,3], do: a * 2 # => [2,4,6]
      
      







ここで、リスト圧縮を使用して、それぞれ2つの関数を生成しました(または関数と一致します)。



実際のコードから例を示します。

一方通行他方通行

そして両方向で、エリキシルで



elixir自体では、たとえば次のように表示することもできます。

github.com/elixir-lang/elixir/blob/master/lib/elixir/lib/string.ex#L478-L486



elixir-eのマクロはclojureのように動作します(lispプログラマーは自宅にいるように感じるでしょう)。どのコードでもそのASTを見ることができます。

 quote do: 1+1 # => {:+,[context: Elixir, import: Kernel],[1,1]} quote do: {1,2,3,4} # => {:"{}",[],[1,2,3,4]} quote do: sum(1,2) # => {:sum,[],[1,2]} quote do: sum(1, 2 + 3, 4) # => {:sum,[],[1,{:+,[context: Elixir, import: Kernel],[2,3]},4]}
      
      





例からわかるように、ASTは{name、meta、arguments}の3つの要素を持つタプルで構成されています

さて、最初のマクロを書きましょう:

 defmodule MyMacro do defmacro unless(clause, options) do quote do: if(!unquote(clause), unquote(options)) end end
      
      







次に、マクロを使用します。

 require MyMacro MyMacro.unless 2 < 1, do: 1 + 2
      
      







次の例では、取得した知識を使用して、たとえば最適化する方法を示します。

どこかに正規表現を使用すると、次のようになります。

 defmodule TestRegex do def myregex_test do :re.run("abc", "([az]+)", [{:capture, :all_but_first, :list}]) end end
      
      





そして今、上記の知識を使用して、正規表現のコンパイルを行うことができます。これにより、ランタイムコードが高速になります。

 defmodule TestRegexOptimized do {:ok, regex} = :re.compile("([az]+)") escaped_regex = Macro.escape(regex) def myregex_test do :re.run("abc", unquote(escaped_regex), [{:capture, :all_but_first, :list}]) end end
      
      





この例では、関数の外側に正規表現をコンパイルしました。 Macro.escape(Macroモジュールには他にも多くの便利な関数があります)を使用して、既にコンパイル済みの正規表現を関数に挿入しましたが、コードには読みやすいバージョンが残っています。 実際、正規表現を使用するエリクサーでは、これを行う必要はありません。%rマクロが既にこれを行っているため、すぐに正規表現をコンパイルできるかどうかによって異なります。



したがって、関数の速度を比較できます。

 Enum.map(1..1000, fn(_) -> elem(:timer.tc(TestRegex, :myregex_test, []), 0) end) |> List.foldl(0, &1 + &2) # => 4613 Enum.map(1..1000, fn(_) -> elem(:timer.tc(TestRegexOptimized, :myregex_test, []), 0) end) |> List.foldl(0, &1 + &2) # => 3199
      
      







多態性:

 list = [{:a, 1}, {:b, 2}, {:c, 3}, {:d, 4}, {:e, 5}, {:f, 6}, {:g, 7}, {:h, 8}, {:k, 9}] Enum.map(list, fn({a, x}) -> {a, x * 2} end) dict = HashDict.New(list) Enum.map(dict, fn({a, x}) -> {a, x * 2} end) file = File.iterator!("README.md") lines = Enum.map(file, fn(line) -> Regex.replace(%r/"/, line, "'") end) File.write("README.md", lines)
      
      





この例は、Enumerableプロトコルを実装する任意のデータ型でEnumライブラリを使用できることを示しています。



プロトコルの実装は、プロトコル自体に関係なく、どこにでも配置できます。主なことは、コンパイルされたコードがBeamVMが見つけられる場所(つまり、source.get_path)にあることです。 つまり たとえば、データ型のコードを変更せずに既存のライブラリを拡張できます。



もう1つの興味深い組み込みプロトコルはアクセスプロトコルです-シンボル値をトップリストの例として取り上げます。

 list = [{:a, 1}, {:b, 2}, {:c, 3}, {:d, 4}, {:e, 5}, {:f, 6}, {:g, 7}, {:h, 8}, {:k, 9}] list[:a] # => 1
      
      







バイナリツリーを使用して非常に簡単な例を作成します。これは、ツリーのレコードに含まれます。ツリーには、アクセスプロトコルも実装します。

 defmodule TreeM do def has_value(nil, val), do: nil def has_value({{key, val}, _, _}, key), do: val def has_value({_, left, right}, key) do has_value(left, key) || has_value(right, key) end end defrecord Tree, first_node: nil defimpl Access, for: Tree do def access(tree, key), do: TreeM.has_value(tree.first_node, key) end
      
      







同じように、アクセスプロトコルを介して値を見つけることができます。

 tree = Tree.new(first_node: {{:a, 1}, {{:b, 2}, nil, nil}, {{:c, 3}, nil, nil}}) tree[:a] # => 1
      
      





プロトコルはポリモーフィズムを提供します。



そして今、特定の状況でコードの記述と読み取りを容易にする、少しの構文上の砂糖。

[{:a、1}]は次のように書くことができます:[a:1]



同様に、次のような構造を記述する必要があります。

func3(func2(func1(list)))、func1関数が最初に呼び出されるという事実にもかかわらず、この場合のようにfunc3を最初に記述するか、変数を入力する必要があります。

 file = File.iterator!("README.md") lines = Enum.map(file, fn(line) -> Regex.replace(%r/"/, line, "'") end) File.write("README.md", lines)
      
      







パイプライン(|>)演算子を使用して、次のように例を書き換えることができます。

 lines = File.iterator!("README.md") |> Enum.map(fn(line) -> Regex.replace(%r/"/, line, "'") end) File.write("README.md", lines)
      
      





elixirライブラリでは、標準化された主題が最初の引数です。 そして、これは、前のアクションの結果を次の呼び出しの関数の最初の引数に置き換える|>演算子の助けを借りて、より理解しやすく、コンパクトでシーケンシャルなコードを書くことを可能にします。



また、単純なケースではカレーまたはパーシャルを使用してこの例を単純化できます。

 lines = File.iterator!("README.md") |> Enum.map( Regex.replace(%r/"/, &1, "'") ) File.write("README.md", lines)
      
      







Elixirは、コードの品質、生産性を向上させ、メタプログラミングを実際に試してみたいアーラン開発者にとって興味深いものになると思います。 同様に、他の言語やプラットフォームの開発者も興味を持っています。 たとえば、BeamVMを試したいが、アーラン構文やライブラリの混乱のために敢えてしませんでした。 ここで、エリクシルの重要な利点は、標準化されたコンパクトな標準ライブラリです。



All Articles