Skip to content

Instantly share code, notes, and snippets.

@mopemope
Created May 21, 2015 03:11
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mopemope/19c3aa9b631d4a47f5f7 to your computer and use it in GitHub Desktop.
Save mopemope/19c3aa9b631d4a47f5f7 to your computer and use it in GitHub Desktop.
LFE入門の進捗

LFE 入門

近年、ネットワークアプリケーションを書くにあたり Erlang/OTP に対する注目が集まっています。 大規模サービスで使わているなどの事例が多く紹介されるようになり、導入を考えている方も多いでしょう。 ですが、Erlang は少々癖があり、難しく感じる方も少なくありません。 今回はそのような人向けに LFE を紹介しようと思います。

LFE とは

LFE とは (lisp (flavored (erlang))) の略です。その名の通り、 Erlang Virtual Machine (BEAM) 上で動く Lisp です。

LFE はそれなりに古く、Robert Virding 氏が 2007 年頃にプロトタイプを開発開始し、R12B の時に初リリースされています。 それからしばらく更新されていましたが一時開発が止まっているような状態になっていました。(私にはそのように見えた) ですが、近年 Duncan McGreggor 氏などが参画し、再度開発が活発化し lfetool やドキュメントなどが整備され始めています。 現在も Github 上で開発が行われています。

rvirding/lfe

LFE とはどんなものでしょうか?LFE の特徴は以下です。

  • Erlang VM で動作する
  • Erlang とシームレスに統合
  • S 式で記述(マクロも含め)
  • Common Lisp スタイルのドキュメントをサポート
  • Lisp-2
  • REPLのサポート

基本的には Erlang と何も変わりません。Erlang VM で動作しているので当たり前です。 Erlang とシームレスに統合されているため、Erlang で出来ることは全て可能です。 パターンマッチング、レコードでのパターンマッチング、ガードも出来ます。 もちろん OTP も使用出来ます。また言語的に変な癖も特にありません。 そのため、通常の Erlang コードから乗り換えることも容易です。

以下は LYSE でのサンプルコードです。Erlang と LFE で比較してみます。

-module(kitty_gen_server).

-export([start_link/0, order_cat/4, return_cat/2, close_shop/1]).

-record(cat, {name, color=green, description}).

%%% Client API
start_link() -> spawn_link(fun init/0).

%% Synchronous call
order_cat(Pid, Name, Color, Description) ->
    Ref = erlang:monitor(process, Pid),
    Pid ! {self(), Ref, {order, Name, Color, Description}},
    receive
        {Ref, Cat} ->
            erlang:demonitor(Ref, [flush]),
            Cat;
        {'DOWN', Ref, process, Pid, Reason} ->
            erlang:error(Reason)
    after 5000 ->
            erlang:error(timeout)
    end.

%% This call is asynchronous
return_cat(Pid, Cat = #cat{}) ->
    Pid ! {return, Cat},
    ok.

%% Synchronous call
close_shop(Pid) ->
    Ref = erlang:monitor(process, Pid),
    Pid ! {self(), Ref, terminate},
    receive
        {Ref, ok} ->
            erlang:demonitor(Ref, [flush]),
            ok;
        {'DOWN', Ref, process, Pid, Reason} ->
            erlang:error(Reason)
    after 5000 ->
            erlang:error(timeout)
end.

%%% Server functions
init() -> loop([]).

loop(Cats) ->
    receive
        {Pid, Ref, {order, Name, Color, Description}} ->
            if Cats =:= [] ->
                    Pid ! {Ref, make_cat(Name, Color, Description)},
                    loop(Cats);
               Cats =/= [] -> % got to empty the stock
                    Pid ! {Ref, hd(Cats)},
                    loop(tl(Cats))
            end;
        {return, Cat = #cat{}} ->
            loop([Cat|Cats]);
        {Pid, Ref, terminate} ->
            Pid ! {Ref, ok},
            terminate(Cats);
        Unknown ->
            %% do some logging here too
            io:format("Unknown message: ~p~n", [Unknown]),
            loop(Cats)
    end.

%%% Private functions
make_cat(Name, Col, Desc) ->
    #cat{name=Name, color=Col, description=Desc}.

terminate(Cats) ->
    [io:format("~p was set free.~n",[C#cat.name]) || C <- Cats],
    ok.

LFE では以下のようになります。

(defmodule kitty-gen-server
  (export all)
  (behavior gen_server))

(defrecord cat
  name
  (color 'green)
  description)

(defun start-link ()
  (gen_server:start_link (MODULE) '() '()))

(defun order-cat (pid name color description)
  (gen_server:call pid `#(order ,name ,color ,description)))

(defun return-cat
  ((pid (= (match-cat) cat))
   (gen_server:cast pid `#(return ,cat))))

(defun close-shop (pid)
  (gen_server:call pid 'terminate))

(defun init
  (('()) `#(ok ,())))

(defun handle_call
  ((`#(order ,name ,color ,description) _from cats)
   (if (=:= cats '())
     `#(reply ,(make-cat name name color color description description) ,cats)
     `#(reply ,(hd cats) ,(tl cats))))
  (('terminate _from cats)
   `#(stop normal ok ,cats)))

(defun handle_cast
  ((`#(return ,(= (match-cat) cat)) cats)
   `#(noreply ,(cons cat cats))))

(defun handle_info (msg cats)
  (io:format "Unexpected message ~p~n" `(,msg))
  `#(noreply ,cats))

(defun terminate
  (('normal cats)
   (lc ((<- cat cats))
     (leave-msg cat))
   'ok))

(defun leave-msg
  (((match-cat name name))
   (io:format "~p was set free.~n" `(,name))))

(defun code_change (_oldvsn state _extra)
  `#(ok ,state))

LFE 固有のところが少しあるかも知れませんが、基本的な部分は Erlang と同様です。 むしろスッキリ書けてるとこすらあります。

Erlang とほぼ同様ならば LFE を使用する利点は何でしょうか?

  • Lisp である

これ一択といっていいでしょう。Lisp での特徴である S 式を使用したメタプログラミングを強力に推し進めることができます。

同様に Lisp ベースのものでは joxa, lol などもあります。 こちらも最初は勢いがあったのですが、だいぶ廃れてしまっています。 lol は LFE に近いですが Lisp-1 を採択しています。

joxa

joxa/joxa

lol

Duncan McGreggor について

Duncan McGreggor といえば一部の人からは非同期厨として知られています。 そうです、かの divmod のメンバーの一人でもあります。Twisted など非同期フレームワークのエキスパートと言えるでしょう。 この手のプログラマーが行き着くとこはやはり Erlang といった感じでしょうか。

LFE のインストール

それでは LFE をインストールします。 公式ドキュメントにのっとってインストールします。

$ git clone https://github.com/rvirding/lfe.git
$ cd lfe
$ make compile
$ make install

インストール先を変更したい場合には以下のコマンドでインストールします。

$ make install DESTBINDIR=/home/ma2/bin

正常にインストールができているか確認します。lfe コマンドを実行して REPL が立ち上がるか確認します。

Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

LFE Shell V6.4 (abort with ^G)
>

lfetool のインストール

LFE にはサポートツールとして lfetool というものがあります。 使い方などは割愛しますが、lfetool が提供している機能の一部を以下に挙げます。

  • lfe およびその関連ツールの のインストール
  • lfetool のアップデート、依存のアップデート
  • アプリケーションの雛形の作成
  • テスト実行
  • REPL の実行

lfetool 自体のコマンドはプラグインになっているので拡張していく事も可能です。 雛形作成やテスト実行は重宝しますのでインストールしておきましょう。 インストール方法は以下です。

$ curl -L -o ./lfetool https://raw.github.com/lfe/lfetool/master/lfetool
$ bash lfetool install
$ lfetool -x

https://github.com/lfe/lfetool

LFE の実行

lfe コマンドを実行します。すると LFE Shell (REPL) が立ち上がります。


$ lfe
Erlang/OTP 17 [erts-6.4] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]

LFE Shell V6.4 (abort with ^G)
>

REPL での操作は基本的に erl での REPL と同じです。 REPL は関数の挙動を調べたり、調査のために小さなコード片を実行したりといったり、マクロを展開するなど開発時にはとても重宝します、

> (+ 1 1 1 1)
4
> (defun double (x) (* x 2))
double
> (set x 3)
3
> (double x)
6
> (set f (lambda (x) (* x x)))
#Fun<lfe_eval.12.2018457>
> (funcall f 3)
9
>

lfetool であれば以下のコマンドで REPL が立ち上がります。

$ lfetool repl

LFE の基礎

それでは次に LFE の基本的な部分を見ます。まずは基本的な構文、データ型などを見ていきます。 また前にも書きましたが、LFE には変な癖がありません。 LFE になろうともデータ型などは Erlang のものと同じです。

基本的な文法

LFE は Lisp なので S 式で記述します。

(defmodule trade-fsm
  (export all)
  (behavior gen_fsm))

(defrecord state
  (name '"")
  other
  (ownitems '())
  (otheritems '())
  monitor
  from)

(defmacro monitor% (pid)
  `(monitor 'process ,pid))

(defun init (name)
  `#(ok idle ,(make-state name name)))

(defun notice
  (((match-state name name) str args)
   (let ((fmt (string:join `("~s: " ,str "~n") "")))
     (io:format fmt (cons name args)))))

LFE は Common Lisp からかなり影響を受けており、 かなり近いものになっています。 defun, defmacro, let など多くを Common Lisp スタイルの form をサポートしています。 一部 Scheme スタイルもサポートしていますがあまり使われていません。

コードフォーマットも一般的な Commom Lisp スタイルで記述します。 但し、グローバルな値(関数ですが)の命名は LFE だと耳あてしない (aaa のような変数名) というケースも見受けられます。

それではまずは LFE でも特別扱いになっているシンタックスから見ていきます。 特別なシンタックスルールは以下です。

シンタックス 定義
#b #o #d #x #23r Intger (基数指定)
#(e e ... ) Tuple
#b(e e ... ) Binary
#m(k v ... ) Map
[ ... ] ( ... ) と同じ

数は少なくシンプルです。但し、特別なシンタックスで tuple などを作成した場合には 少し評価を意識する必要があります。

> #(a b)
#(a b)
> (set x 1)
1
> #(a x)
#(a x)
> `#(a ,x)
#(a 1)
> (tuple 'a x)
#(a 1)
>

このように #(e e ... ) では内部が評価されずそのまま atom 扱いになります。 そのため、このシンタックスを使う場合は後述する Backquote と組み合わせて使うのが一般的です。 もちろん無理に使う必要はありません。通常の tuple 関数でタプルを作成しても構いません。

次に主要な form を簡単に紹介します。多くは special form と呼ばれる特殊形式になります。

quote

(quote e)

cons

(quote e)

命名

LFE の場合、Erlang と異なり、大文字小文字を区別する必要がありません、 任意の名前をつけることができます。また LFE は Lisp 扱いのため、 Lisp 系の命名規則で 記述します。注意する点は単語をつなげる際には - でつなげるという点です。

例:

(defun start-link
  ((name limit sup mfa) (when (and (is_atom name) (is_integer limit)))
   (gen_server:start_link `#(local ,name) (MODULE) `#(,limit ,mfa ,sup) '())))

(defun start_link (name limit sup mfa)
  (start-link name limit sup mfa))

但し、- であると behavior のコールバックが呼ばれないので、上記のようにラップしたものを 定義するケースも多く見受けられます。 命名なども含め、LFE にはスタイルガイドがあります。参考にしてみてください。

Backquote Macro

あまり Lisp に馴染みのない方もいるかも知れません。簡単に Backquote Macro につい て触れておきます。Backquote は後続のS式評価を止めます。内部も評価されません。

以下簡単な例です。

> (set x 1)
1
> x
1
> (list x 2)
(1 2)
> `(x 2)
(x 2)
>

x に 1 をセットし、その値が評価され、1 として扱われるか見ます。 (list x 2) は評価され、(1 2) が表示されました。 ``(x 2)(x 2)` とそのまま `x` が atom 扱いになります。 さらに実行してみます。

> `(,x 2)
(1 2)
>

,x に前に記述します。, を使うとその変数が評価され、(1 2) が表示されました。 backquote 内で , を使うとその後続の式が評価されます。もう少し例を書きます。

> (set msg "lfe !")
"lfe !"
> (io:format "hello ~s~n" `(,msg))
hello lfe !
ok
> (io:format "hello ~s~n" `(msg))
hello msg
ok
> `(1 2 (tuple 1 2))
(1 2 (tuple 1 2))
> `(1 2 ,(tuple 1 2))
(1 2 #(1 2))
>

上記のように Backquote と , を使うことでどの部分を評価し、どの部分を評価しない かをコントロールすることが出来ます。すなわち、コードのテンプレートを作ることが出来るのです。 そのため、マクロを定義する場合などでよく使用されます。

(defmacro caar (x) `(car (car ,x)))

その他にも、tuple, list, の省略化にも使われます。特に tuple 部はよく使用されます。

(defun handle_cast
  ((`#(return ,(= (match-cat) cat)) cats)
   `#(noreply ,(cons cat cats))))

データ型

LFE で扱えるデータ型をみます。 基本的に Erlang と何も変わりません。

レコード

関数の定義

関数定義は Common Lisp と同様です。

無名関数

マクロ

パターンマッチング

ガード

CASE

リスト内包表記

コードを書いてみる

組み込み関数

モジュールの定義

マクロの定義

OTP

パッケージとアプリケーション

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment