[ Fennel ]: Getting Started with Fennel

Fennel を使い始める

では、どのように作業するのでしょうか?

  • 関数とラムダ

  • ローカル変数と変数

  • 数値と文字列

  • テーブル

  • シーケンシャル テーブル

  • 反復

  • ループ

  • 条件文

  • 少しだけテーブルに戻ります

  • エラー処理

  • 可変引数関数

  • 厳密なグローバル チェック

  • 落とし穴

  • その他の機能はそのまま動作します

  • モジュールと複数のファイル

  • 相対 require

  • コンパイル時の相対 include

  • `init.fnl` 以外のモジュールからのモジュールの要求

プログラミング言語は、構文セマンティクス で構成されます。Fennel のセマンティクスは、Lua とわずかに異なります (すべて以下に記載)。Fennel の構文は、Lisp ファミリーの言語に由来しています。 Lisp の構文は非常に統一されていて予測可能なので、コードを操作するコードを書く だけでなく 構造化編集 も簡単になります。

Lua と Lisp をすでに知っている場合は、Fennel にすぐに慣れることができます。そうでなくても、Lua は存在するプログラミング言語の中で最もシンプルなものの 1 つなので、以前にプログラミングをしたことがある人なら、特にクロージャ付きの別の動的命令型言語を使ったことがある人なら、それほど苦労せずに習得できるはずです。Lua リファレンス マニュアル は詳細を調べるのに良い場所ですが、Fennel 独自の Lua 入門書 の方が短く、要点を網羅しています。

すでに Lua のサンプル コードがあり、Fennel でどのように表示されるかを確認したい場合は、antifennel に配置すれば多くのことを学ぶことができます。

では、どのように行うのでしょうか?

関数とラムダ

関数を作成するには `fn` を使用します。オプションの名前を指定すると、関数はローカル スコープでその名前にバインドされます。それ以外の場合は、単に匿名の値になります。

命名に関する簡単な注意: 識別子は通常、ダッシュで区切られた小文字です (別名「ケバブ ケース」)。先頭でない限り、数字を含めることもできます。疑問符を使用することもできます (通常、true または false を返す関数の場合、例: `at-max-velocity?`)。アンダースコア (`_`) は、使用しない予定の変数に名前を付けるときによく使用されます。

引数リストは角括弧で囲まれています。本体の最後の値が返されます。

(これまで Lisp を使用したことがない場合は、呼び出される関数またはマクロが括弧の外側ではなく内側にあることに注意してください。)

    (fn print-and-add [a b c]
      (print a)
      (+ b c))

関数は、引数リストの直後に続く文字列の形式でオプションの docstring を取ることができます。通常のコンパイルでは、これは生成された Lua から削除されますが、REPL での開発中は、`,doc` コマンドを使用して docstring と関数の使用状況を表示できます。

    (fn print-sep [sep ...]
      "Prints args as a string, delimited by sep"
      (print (table.concat [...] sep)))
    ,doc print-sep ; -> outputs:
    ;; (print-sep sep ...)
    ;;   Prints args as a string, delimited by sep

他の Lisp と同様に、Fennel はコメントにセミコロンを使用します。

`fn` で定義された関数は高速で、Lua と比較して実行時のオーバーヘッドがありません。ただし、引数チェックもありません。(つまり、間違った数の引数で関数を呼び出してもエラーは発生しません。) より安全なコードにするには、`lambda` を使用できます。これにより、名前の先頭に `?` を付けて省略できることを示さない限り、定義した数以上の引数が取得されます。

    (lambda print-calculation [x ?y z]
      (print (- x (* (or ?y 1) z))))
    
    (print-calculation 5) ; -> error: Missing argument z

2 番目の引数 `?y` は `nil` にすることができますが、 `z` は許可されないことに注意してください。

    (print-calculation 5 nil 3) ; -> 2

`fn` と同様に、ラムダは引数リストの後にオプションの docstring を受け入れます。

ローカルと変数

ローカルは、名前と値を 1 組の角括弧で囲んだ `let` を使用して導入されます。

    (let [x (+ 89 5.2)
          f (fn [abc] (print (* 2 abc)))]
      (f x))

ここで、`x` は 89 と 5.2 を加算した結果にバインドされ、`f` は引数を 2 倍にして出力する関数にバインドされます。これらのバインドは、`let` 呼び出しの本体内でのみ有効です。

`local` を使用してローカルを導入することもできます。これは、ファイル全体で使用される場合に便利ですが、一般的には、関数内では、値がどこで使用できるかが一目でわかるため、`let` が好まれます。

    (local tau-approx 6.28318)

この方法で設定されたローカルには新しい値を与えることはできませんが、外部の名前をシャドウする新しいローカルを導入することはできます。

    (let [x 19]
      ;; (set x 88) <- not allowed!
      (let [x 88]
        (print (+ x 2))) ; -> 90
      (print x)) ; -> 19

ローカルの値を変更する必要がある場合は、`set` が動作することを除いて `local` のように動作する `var` を使用できます。`var` に相当するネストされた `let` のようなものはありません。

    (var x 19)
    (set x (+ x 8))
    (print x) ; -> 27

数値と文字列

もちろん、`+`、`-`、`*`、`/` などの標準的な算術演算子はすべて、ここではプレフィックス形式で機能します。数値は、整数が導入された 5.3 より前のすべての Lua バージョンでは倍精度浮動小数点数であることに注意してください。5.3 以降では、整数除算には `//` が使用され、ビット演算には `lshift`、`rshift`、`bor`、`band`、`bnot`、`xor` が使用されます。ホスト Lua 環境がバージョン 5.3 より古い場合、ビット演算子と整数除算は機能しません。

長い数値のセクションを区切るためにアンダースコアを使用することもできます。アンダースコアは値には影響しません。

    (let [x (+ 1 99)
          y (- x 12)
          z 100_000]
      (+ z (/ y 10)))

文字列は本質的に不変のバイト配列です。UTF-8 のサポートは、Lua 5.3+ の `utf8` テーブルで、またはそれ以前のバージョンの サードパーティ ライブラリ で提供されます。文字列は `..` で連結されます。

    (.. "hello" " world")

テーブル

Lua (および Fennel) では、テーブルが唯一のデータ構造です。テーブルの主な構文では、キーと値のペアを含む中括弧を使用します。

    {"key" value
     "number" 531
     "f" (fn [x] (+ x 2))}

テーブルから値を取得するには、`.` を使用できます。

    (let [tbl (function-which-returns-a-table)
          key "a certain key"]
      (. tbl key))

そして、`tset` を使ってそれらを配置します:

    (let [tbl {}
          key1 "a long string"
          key2 12]
      (tset tbl key1 "the first value")
      (tset tbl key2 "the second one")
      tbl) ; -> {"a long string" "the first value" 12 "the second one"}

シーケンシャル テーブル

一部のテーブルは、シーケンシャルに使用されるデータを格納するために使用されます。この場合のキーは、1 から始まり、増加する数字です。Fennel は、角括弧を使用してこれらのテーブルに代替構文を提供します。

    ["abc" "def" "xyz"] ; equivalent to {1 "abc" 2 "def" 3 "xyz"}

Lua の組み込み `table.insert` 関数は、連続テーブルで使用するためのものです。挿入された値の後のすべての値は、1 つのインデックスだけ上にシフトされます。`table.insert` にインデックスを指定しないと、テーブルの末尾に追加されます。

`table.remove` 関数も同様に動作します。テーブルとインデックス (デフォルトではテーブルの末尾) を受け取り、そのインデックスにある値を削除して返します。

    (local ltrs ["a" "b" "c" "d"])
    
    (table.remove ltrs)       ; Removes "d"
    (table.remove ltrs 1)     ; Removes "a"
    (table.insert ltrs "d")   ; Appends "d"
    (table.insert ltrs 1 "a") ; Prepends "a"
    
    (. ltrs 2)                ; -> "b"
    ;; ltrs is back to its original value ["a" "b" "c" "d"]

`length` 形式は連続したテーブルと文字列の長さを返します。

    (let [tbl ["abc" "def" "xyz"]]
      (+ (length tbl)
         (length (. tbl 1)))) ; -> 6

ギャップのあるテーブルの長さは未定義であることに注意してください。nil 値と非 nil 値の間のテーブルの「境界」位置のいずれかに対応する数値を返すことができます。

Lua の標準ライブラリは非常に小さいため、`map`、`reduce`、`filter` など、含まれていると予想されるいくつかの関数がありません。Fennel では、代わりにマクロが使用されます。`icollect`、`collect`、`accumulate` を参照してください。

反復

テーブル要素のループは、`each` と、`pairs` (一般的なテーブルに使用) または `ipairs` (連続テーブル用) などの反復子を使用して行われます。

    (each [key value (pairs {"key1" 52 "key2" 99})]
      (print key value))
    
    (each [index value (ipairs ["abc" "def" "xyz"])]
      (print index value))

テーブルが連続しているかどうかは、テーブル固有のプロパティではなく、どのイテレータが使用されるかによって決まることに注意してください。任意のテーブルで `ipairs` を呼び出すことができ、1 から始まる数値キーのみを `nil` に達するまで反復します。

`each` では任意の Lua イテレータ を使用できますが、これらが最も一般的です。次に、文字列内の一致 を調べる例を示します。

    (var sum 0)
    (each [digits (string.gmatch "244 127 163" "%d+")]
      (set sum (+ sum (tonumber digits))))

テーブルを返したい場合は、連続テーブルを取得するには `icollect` を、キー/値テーブルを取得するには `collect` を試してください。nil を返す本体は、結果のテーブルから省略されます。

    (icollect [_ s (ipairs [:greetings :my :darling])]
      (if (not= :my s)
          (s:upper)))
    ;; -> ["GREETINGS" "DARLING"]
    
    (collect [_ s (ipairs [:greetings :my :darling])]
      s (length s))
    ;; -> {:darling 7 :greetings 9 :my 2}

低レベルの反復構造は `for` であり、指定された開始値から終了値まで数値的に反復します。

    (for [i 1 10]
      (print i))

オプションのステップ値を指定できます。このループは 10 未満の奇数のみを出力します。

    (for [i 1 10 2]
      (print i))

ループ

ループする必要があるが、何回ループするか分からない場合は、`while` を使用できます。

    (while (keep-looping?)
      (do-something))

条件文

最後に条件文があります。Fennel の `if` 形式は他の Lisp 言語と同じように使用できますが、`elseif` ブランチにコンパイルされる複数の条件の `cond` として使用することもできます。

    (let [x (math.random 64)]
      (if (= 0 (% x 2))
          "even"
          (= 0 (% x 9))
          "multiple of nine"
          "I dunno, something else"))

引数が奇数の場合、最後の節は「else」として解釈されます。

Fennel は Lisp なので文がないので、`if` は式として値を返します。 Lua プログラマーは、値を取得するためだけに `and`/`or` の不安定な連鎖を構築する必要がないことを知って喜ぶでしょう。

もう 1 つの条件は `when` で、これは任意の数の副作用に使用され、else 節はありません。

    (when (currently-raining?)
      (wear "boots")
      (deploy-umbrella))

少しだけテーブルに戻ります

スペースや予約文字を含まない文字列では、代わりに `:storhand` 構文を使用できます。これは、テーブル キーによく使用されます。

    {:key value :number 531}

テーブルに次のような文字列キーがある場合、キーが事前にわかっていれば、ドットを使用して簡単に値を取得できます。

    (let [tbl {:x 52 :y 91}]
      (+ tbl.x tbl.y)) ; -> 143

この構文は `set` でも使用できます。

    (let [tbl {}]
      (set tbl.one 1)
      (set tbl.two 2)
      tbl) ; -> {:one 1 :two 2}

テーブル キーの名前が設定先の変数と同じ場合は、キー名を省略して代わりに `:` を使用できます。

    (let [one 1 two 2
          tbl {: one : two}]
      tbl) ; -> {:one 1 :two 2}

最後に、`let` はテーブルを複数のローカルに分解できます。

位置による分解があります:

    (let [data [1 2 3]
          [fst snd thrd] data]
      (print fst snd thrd)) ; -> 1       2       3

キーによるテーブルの構造化解除:

    (let [pos {:x 23 :y 42}
          {:x x-pos :y y-pos} pos]
      (print x-pos y-pos)) ; -> 23      42

上記のように、テーブル キーの名前が、それを分解する変数と同じ場合は、キー名を省略して、代わりに `:` を使用できます。

    (let [pos {:x 23 :y 42}
          {: x : y} pos]
      (print x y)) ; -> 23      42

これはネストしたり、組み合わせたりすることができます:

    (let [f (fn [] ["abc" "def" {:x "xyz" :y "abc"}])
          [a d {:x x : y}] (f)]
      (print a d)
      (print x y))

テーブルのサイズがバインディング ローカルの数と一致しない場合、欠落した値は `nil` で埋められ、余分な値は破棄されます。多くの言語とは異なり、Lua の `nil` は実際には値がないことを示すため、テーブルに `nil` を含めることはできません。`nil` をキーとして使用しようとするとエラーになり、`nil` を値として使用すると、そのキーに以前あったエントリがすべて削除されます。

エラー処理

Lua のエラーには 2 つの形式があります。Lua の関数は任意の数の値を返すことができ、失敗する可能性のあるほとんどの関数は、2 つの戻り値 (`nil` の後に失敗メッセージ文字列が続く) を使用して失敗を示します。Fennel では、角括弧の代わりに括弧を使用して構造化分解することで、このスタイルの関数を操作できます。

    (case (io.open "file")
      ;; when io.open succeeds, it will return a file, but if it fails
      ;; it will return nil and an err-msg string describing why
      f (do (use-file-contents (f:read :*all))
            (f:close))
      (nil err-msg) (print "Could not open file:" err-msg))

`values` を使用して複数の値を返す独自の関数を作成できます。

    (fn use-file [filename]
      (if (valid-file-name? filename)
          (open-file filename)
          (values nil (.. "Invalid filename: " filename))))

: エラーは関数から複数の値を返す最も一般的な理由ですが、他の場合にも使用できます。これは Lua の最も複雑な点であり、このチュートリアルでは詳しく説明できませんが、他の場所で詳しく説明されています

このタイプのエラーの問題は、うまく構成されないことです。エラー ステータスは、呼び出しチェーンに沿って内側から外側までずっと伝播する必要があります。これに対処するには、`error` を使用できます。これは、保護された呼び出し内でない限り、プロセス全体を終了します。これは、例外をスローすると、try/catch 内でない限りプログラムが停止する他の言語と同様です。`pcall` を使用して保護された呼び出しを行うことができます。

    (let [(ok? val-or-msg) (pcall potentially-disastrous-call filename)]
      (if ok?
          (print "Got value" val-or-msg)
          (print "Could not get value:" val-or-msg)))

ここでの `pcall` 呼び出しは、`(potentially-disasterus-call filename)` を保護モードで実行していることを意味します。`pcall` は、関数に渡される任意の数の引数を取ります。`pcall` は、呼び出しが成功したかどうかを知らせるブール値 (ここでは `ok?`) と、成功した場合は実際の値、成功しなかった場合はエラー メッセージである 2 番目の値 (`val-or-msg`) を返すことがわかります。

`assert` 関数は値とエラー メッセージを取ります。値が `nil` の場合は `error` を呼び出し、それ以外の場合はそれを返します。これを使用して、複数の値の失敗をエラーに変換できます (`error` を複数の値の失敗に変換する `pcall` の逆のようなものです)。

    (let [f (assert (io.open filename))
          contents (f.read f "*all")]
      (f.close f)
      contents)

この例では、`io.open` は失敗時に `nil` とエラー メッセージを返すため、失敗すると `error` がトリガーされ、実行が停止します。

可変長関数

Fennel は、多くの言語と同様に可変長関数 (つまり、任意の数の引数を取る関数) をサポートしています。関数に可変数の引数を取る構文は `...` シンボルで、関数の最後のパラメーターである必要があります。この構文は、Lisp ではなく Lua から継承されています。

`...` 形式はリストやファースト クラス値ではなく、インラインで複数の値に展開されます。可変長引数の個々の要素にアクセスするには、括弧で分解するか、最初にテーブル リテラル (`[...]`) でラップして通常のテーブルのようにインデックスを付けるか、Lua のコア ライブラリの `select` 関数を使用します。多くの場合、可変長引数は、バインドせずに `print` などの別の関数に直接渡すことができます。

    (fn print-each [...]
      (each [i v (ipairs [...])]
        (print (.. "Argument " i " is " v))))
    
    (print-each :a :b :c)
    (fn myprint [prefix ...]
      (io.write prefix)
      (io.write (.. (select "#" ...) " arguments given: "))
      (print ...))
    
    (myprint ":D " :d :e :f)

可変引数のスコープは他の変数とは異なり、作成された関数からのみアクセスできます。通常の値とは異なり、関数は可変引数を閉じることはできません。つまり、内部関数の可変引数はスコープ外であるため、次のコードは機能しません。

    (fn badcode [...]
      (fn []
        (print ...)))

厳密なグローバル チェック

`unknown global in strict mode` というエラーが表示された場合、Fennel コンパイラが認識していないグローバルを使用するコードをコンパイルしようとしていることを意味します。ほとんどの場合、これはコーディング ミスが原因です。ただし、場合によっては、正当なグローバル参照でこのエラーが発生することがあります。これが発生した場合、Fennel の戦略に固有の制限が原因である可能性があります。`_G.myglobal` を使用して、このチェックを回避し、これが実際にはグローバルであるという事実に注意を喚起する方法で参照できます。

このエラーのもう 1 つの考えられる原因は、変更された 関数環境 です。解決策は、Fennel の使用方法によって異なります。

  • 組み込み Fennel では、`allowedGlobals` パラメーターを使用して、特定の (またはすべての) グローバルを無視するように検索を変更できます。手順については、Lua API ページを参照してください。

  • Fennel の CLI には `--globals` パラメータがあり、無視するグローバルのコンマ区切りリストを受け入れます。たとえば、グローバル x、y、z の厳密モードを無効にするには、次のようにします。

落とし穴

熟練した Lisper を悩ませる驚きがいくつかあります。そのほとんどは、Fennel が Lua に対して実行時のオーバーヘッドをゼロにすることを主張していることから必然的に生じます。

  • 算術演算子、比較演算子、ブール演算子はファーストクラス関数ではありません。これらの演算子は、複数の戻り値を持つ関数では、コンパイル時に引数の数を知る必要があるため、意外な動作をすることがあります。

  • `apply` 関数はありません。代わりに、Lua のバージョンに応じて `table.unpack` または `unpack` を使用します: `(f 1 3 (table.unpack [4 9]))`。

  • [Baker](https://p.hagelb.org/equal-rights-for- functional-objects.html) に従って、テーブルは内容の値ではなく、同一性によって等価性を比較されます。

  • repl の戻り値はきれいに印刷されますが、`(print tbl)` を呼び出すと、`table: 0x55a3a8749ef0` のような出力が出力されます。まだ定義していない場合は、デバッグ用に、引数に対して `fennel.view` を呼び出して印刷するプリンタ関数を定義することをお勧めします: `(local fennel (require :fennel)) (fn _G.pp [x] (print (fennel.view x)))`。この定義を `~/.fennelrc` ファイルに追加すると、標準 repl で使用できるようになります。

  • Lua プログラマーは、Fennel 関数は早期リターンを実行できないことに注意してください。

その他の機能はそのまま動作します

上記の `math.random` のような Lua の標準ライブラリ の組み込み関数は、手間をかけずにオーバーヘッドなしで呼び出すことができます。

これには、他の言語で特別な構文を使用して実装されることが多いコルーチンなどの機能が含まれます。コルーチンを使用すると、コールバックなしで非ブロッキング操作を表現できます

Lua のテーブルは少し制限されているように見えるかもしれませんが、メタテーブル を使用すると、はるかに柔軟性が高まります。メタテーブルのすべての機能は、Lua の場合と同じように、Fennel コードからアクセスできます。

モジュールと複数のファイル

`require` 関数を使用して、他のファイルからコードを読み込むことができます。

    (let [lume (require :lume)
          tbl [52 99 412 654]
          plus (fn [x y] (+ x y))]
      (lume.map tbl (partial plus 2))) ; -> [54 101 414 656]

Fennel と Lua のモジュールは、関数やその他の値を含む単なるテーブルです。Fennel ファイルの最後の値は、モジュール全体の値として使用されます。技術的には、これはテーブルだけでなく任意の値にすることができますが、テーブルを使用するのが最も一般的であり、それには十分な理由があります。

サブディレクトリにあるモジュールを要求するには、ファイル名を取得し、スラッシュをドットに置き換え、拡張子を削除してから、それを `require` に渡します。たとえば、`lib/ui/menu.lua` というファイルは、モジュール `lib.ui.menu` をロードするときに読み込まれます。

`fennel` コマンドを使用してプログラムを実行すると、`require` を呼び出して Fennel または Lua モジュールをロードできます。ただし、他のコンテキスト (Lua にコンパイルしてから `lua` コマンドを使用する、または Lua を埋め込むプログラムなど) では、Fennel モジュールは認識されません。 `.fnl` ファイルを検索する方法を知っている検索ツールをインストールする必要があります。

    require("fennel").install()
    local mylib = require("mylib") -- will compile and load code in mylib.fnl

これを追加すると、`require` は Lua の場合と同じように Fennel ファイルでも機能します。たとえば、`(require :mylib.parser)` は Fennel の検索パス (Lua モジュールの検索に使用される `package.path` とは異なる `fennel.path` に格納されている) の "mylib/parser.fnl" を検索します。パスには通常、デフォルトで現在のディレクトリを基準にしてロードするためのエントリが含まれています。

相対 require

モジュールを使用するライブラリを作成する方法はいくつかあります。その 1 つは、LuaRocks のようなものに依存して、ライブラリのインストールとライブラリとそのモジュールの可用性を管理することです。もう 1 つの方法は、ネストされたモジュールをロードするために相対 require スタイルを使用することです。相対 require を使用すると、ライブラリは内部モジュール パスを解決するときにルート ディレクトリ名やその場所に依存しません。

たとえば、`init.fnl` ファイルとルート ディレクトリのモジュールを含む小さな `example` ライブラリを次に示します。

    ;; file example/init.fnl:
    (local a (require :example.module-a))
    
    {:hello-a a.hello}

ここで、メイン モジュールには、実装を保持する追加の `example.module-a` モジュールが必要です。

    ;; file example/module-a.fnl
    (fn hello [] (print "hello from a"))
    {:hello hello}

ここでの主な問題は、ライブラリへのパスが正確に `example` でなければならないことです。たとえば、ライブラリが機能するには `(require :example)` として要求される必要がありますが、これはライブラリ ユーザーに強制できません。たとえば、乱雑さを避けるためにライブラリがプロジェクトの `libs` ディレクトリに移動され、`(require :libs.example)` として要求された場合、ランタイム エラーが発生します。これは、ライブラリ自体が `:libs.example.module-a` で​​はなく `:example.module-a` を要求しようとするため発生します。`:libs.example.module-a` は正しいモジュール パスです。

    runtime error: module 'example.module-a' not found:
            no field package.preload['example.module-a']
            ...
            no file './example/module-a.lua'
            ...
    stack traceback:
      [C]: in function 'require'
      ./libs/example/init.fnl:2: in main chunk

LuaRocks は、ディレクトリ名とインストール パスの両方を強制し、`LUA_PATH` 環境変数を設定してライブラリを利用できるようにすることで、この問題に対処します。もちろん、ビルド パイプラインでプロジェクトごとに `LUA_PATH` を設定し、適切なディレクトリを指すように手動で行うこともできます。ただし、これはあまり透過的ではなく、プロジェクトのローカル ライブラリを要求する場合は、`LUA_PATH` が変更された場所を調べるのではなく、プロジェクトのファイル構造に直接マップされる完全なパスを確認する方が適切です。

Fennel エコシステムでは、プロジェクトの依存関係をより簡単に管理することを推奨しています。通常は、ライブラリをプロジェクトのツリーにドロップするか、git サブモジュールを使用するだけで十分であり、必要なパスはライブラリ自体によって処理される必要があります。

`example` ライブラリをそこに移動したと仮定して、`libs/example/init.fnl` で相対的な必要なパスを指定して、名前/パスに依存しないようにする方法を次に示します。

    ;; file libs/example/init.fnl:
    (local a (require (.. ... :.module-a)))
    
    {:hello-a a.hello}

これで、ライブラリの名前や配置場所は関係なくなり、どこからでも要求できます。`(require :lib.example)` でライブラリを要求すると、`...` の最初の値に `"lib.example"` 文字列が保持されるため、これが機能します。この文字列は `".module-a"` と連結され、`require` は実行時に `"lib.example.module-a"` パスの下にあるネストされたモジュールを適切に見つけてロードします。これは Lua の機能であり、Fennel 固有のものではありません。ライブラリが Lua に AOT コンパイルされている場合も同様に機能します。

コンパイル時の相対インクルード

Fennel v0.10.0 以降、`include` 特殊または `--require-as-include` フラグを使用すると、コンパイル時に式を計算できるという制約付きで、コンパイル時にも機能します。これは、式が自己完結的である必要があることを意味します。つまり、ローカルまたはグローバルを参照せず、すべての値を直接埋め込む必要があります。言い換えると、次のコードは実行時にのみ機能しますが、`current-module` はコンパイル時に不明であるため、`include` または `--require-as-include` では機能しません。

    (local current-module ...)
    (require (.. current-module :.other-module))

一方、これは実行時とコンパイル時の両方で機能します。

    (require (.. ... :.other-module))

`...` モジュール引数はコンパイル時に伝播されるため、このライブラリを使用するアプリケーションがコンパイルされると、すべてのライブラリ コードが自己完結型の Lua ファイルに正しく含まれます。

この `example` ライブラリを使用するプロジェクトを `--require-as-include` でコンパイルすると、結果の Lua コードに次のセクションが含まれます。

    package.preload["libs.example.module-a"] = package.preload["libs.example.module-a"] or function(...)
      local function hello()
        return print("hello from a")
      end
      return {hello = hello}
    end

`package.preload` エントリには、コンパイル時に解決された完全修飾パス `"libs.example.module-a"` が含まれていることに注意してください。

`init.fnl` 以外のモジュールからのモジュールの要求

`init` モジュール以外のモジュールからのモジュールを要求するには、現在のモジュールまでのパスを維持しながら、モジュール名を削除する必要があります。たとえば、`libs/example/utils/greet.fnl` に `greet` モジュールを追加し、`libs/example/module-a.fnl` から要求してみましょう。

    ;; file libs/example/utils/greet.fnl:
    (fn greet [who] (print (.. "hello " who)))

このモジュールは次のように要求されます。

    ;; file libs/example/module-a.fnl
    (local greet (require (.. (: ... :match "(.+)%.[^.]+") :.utils.greet)))
    
    (fn hello [] (print "hello from a"))
    
    {:hello hello :greet greet}

親モジュール名は、
`match`
現在のモジュール名文字列に対するメソッド(
`...`
)。


ホーム

このサイトのソース

いいなと思ったら応援しよう!