翻訳:昼休みに始めるHasp

[トップ][一覧][最近の更新]

このページは、「Hasp in a Lunch break」の翻訳です。

略称は「昼Hasp」。

基本的に超訳です。

訳の正しさは全く保証されません。 訳のおかしい部分は多数あります。

翻訳元サイトの許可を取ったりはしていません。 無認可です。



訳者による前書き

訳者によるHasp概要

昼休みに始めるHasp

Scheme処理系をダウンロードする

HaspはSchemeで書かれたプログラムですので、Scheme処理系が必要になります。 HaspはGuileGambitの 両方で動くので、 もしあなたが違う種類のScheme処理系を利用している場合でも、 Haspをその処理系で走らす際に悩む事がないように、 GuileかGambitのどちらかを今インストールしておく事をおすすめします。 ほとんどのLinuxディストリビューションでは、 パッケージ化されたGuileを簡単にインストールできます。

Haskell処理系をダウンロードする

HaspはGHCiHugsで動きます。 GHCもHugsも、あなたの利用しているディストリビューションにパッケージが用意されている事でしょう。

Haspをダウンロードする

gitが使えるなら、以下のコマンドを実行するだけです。

 git clone git://github.com/aliclark/hasp.git

そうでない場合は、tarballをダウンロードして解凍してください。

(訳注:tarballは最新でないかもしれません。githubから取るのが無難だと思います。)

Haspをセットアップする

Haspをダウンロードしたらまず最初に設定を行います。 自分の環境に対して正しく設定する為に、 インストールスクリプト を実行しましょう。

 cd hasp
 bin/install

まず最初に、ghciとhugsどちらのHaskellインタープリタを使用するか聞いてきます(訳注:片方しかインストールしていなくても、必ずどちらか選ぶ必要あり)。

次に、S式のインデント記法を使うかどうか聞かれます(訳注:インデント記法の詳細についてはSRFI-49参照)。 私自身はS式の後に二つの改行を毎回入力するのにはもう嫌になったので、 今は "n" を設定する事をおすすめしておきます。

最後に、guileとgambitどちらのScheme処理系を使用するか答えてください。

(訳注:ここでguileでもgambitでもないScheme処理系の名前を入力すると、そのScheme処理系の為に追加の質問が五個ぐらい出て、それに答えると「ありがとう。この内容をメールしてくれたら将来に本体に取り込むかも(すごい意訳)」みたいなメッセージが出る。尚このメッセージに書いてあるメールアドレスは古いっぽいので注意。)

Haspを使ってみる

設定ができたので、Haspが使えるようになりました! gaspsスクリプトを実行してみましょう。

 bin/gasps

gaspsは、上で選んだHaskellインタープリタと同じウェルカムメッセージを 出力するでしょう。 そして、何個かの基本的なHaspy Haskell thingを含む、Haspという名のHaskellモジュールがロードされます。

これは通常のHaskellインタープリタとは少し違います。 Haspソースコードを入力として取るのです。

Haspでのhello worldをやってみましょう。

 Hasp> (def printHi (putStrLn "Hello, world!"))
 ()
 Hasp> printHi  
 Hello, world!
 
 Hasp> 

このコードの見た目は、Haskellで同じ事をするソースコードに非常によく似ています(訳注:そうか?)。 このHaspのREPLの良い点は、 プロンプト中にて、 def フォーム をそのままの形で記述する事ができ、 あとで何かの式を更にプロンプトに記述する際に先ほどdefフォームで定義した関数が 利用可能だという事です(訳注:HaskellのREPLはdoの内側と同じ状態なので、関数定義や変数束縛する際にletが必要になる等の違いがあり、それと比べた話だと思う)。

大体ほとんどの Hasp式は、 gaspsのトップレベルに置く事ができます。 defフォームと 型シグネチャ は、 どんな式が中に入ってもletフォームと共に こっそり 包み込む事ができます(訳注:訳が怪しい)。

なお、インタープリタにロードされる時に、カレントディレクトリにある hasp-repl.hasp という名前のファイルから追加のHasp式を読み込ませる事ができます(訳注:…と書いてあり、確かにそれらしいコードもスクリプトに入っているが、どこにhasp-repl.haspを置いても読み込まれなかった。あとでもうちょっと調べる。後述の「:hload」を使って読み込む事は普通に可能)。

フックを使う

通常のHaskellインタープリタのフックは、Haspからも利用可能です。 例えば「(:?)」と入力すると、Haskellインタープリタのヘルプ文章が表示されます。 Haspでは何個か追加のフックも提供しています。hload, scm, hsです。

(訳注:「(:q)」フックを実行すると、Haskellインタープリタは終了するけど^Dか^Cを押すまで制御が戻ってこない。これは既知の不具合との事。また、def系フォームを使って定義した関数等を「:t」「:i」を使って調べる事もできない様子。)

hload」は、Haspコードが書かれたファイルを対話形式で使えるようにロードします。 以下はその例です。

 (:hload "mycode.hasp")

上記の例を実行すると、副作用として「mycode.hs」という名前のファイルを生成し、それからHaspインタープリタにコードをロードします。

hs」を使う事で、あらゆるテキストを、Haspをスルーして直にHaskellインタープリタに渡せます。 以下はその例です。

 Hasp> (>>= getLine putStrLn)   
 (:hs "This is the input")
 This is the input
 
 Hasp> 

上の例でのHaskellインタープリタに直に渡したいコードは、「(:hs "...")」のように文字列として括る必要があります。 なぜなら、そうしないとHaspのコードとして処理されてしまうからです(訳注:hsにある通り、文字列以外でも、文字列として印字可能なオブジェクトなら渡せるとの事。勿論Haskellコード片を渡したいなら普通に文字列で渡すのがベター)。

移住する

こうして、きちんとしたREPLを動かせるようになりました。 次は、Hasp一式設置場所をより恒久的なディレクトリへと移動させ、 シンプルなスクリプトも設定して、 どのディレクトリからでも、絶対pathをつけなくても、 シェルに「gasps」と入力するだけでHaspのプロンプトに入れるようにする事を考えます。

私は、Haspの一式を /home/ali/code/hasp のディレクトリ位置に置いています。 しかし、より適切な設置場所は /usr/local/src/hasp だと、あなたは思うかもしれません。

しかしどちらにせよ、以下のような小さなスクリプトを /usr/local/bin/gasps に設置します。

 #!/bin/bash
 
 $(dirname "$0")/../../../home/ali/code/hasp/bin/gasps

もしHasp一式を /usr/local/src/hasp に設置している場合は、上記の代わりに以下のようにします。

 #!/bin/bash
 
 $(dirname "$0")/../src/hasp/bin/gasps

単純なシンボリックリンクではうまく動きません。Haspが動く為にはスクリプトから本体ディレクトリ一式が相対的に参照できる位置にある必要があるからです。

(訳注: ~/bin/gasps 辺りに設置してもいいと思います。どこに設置した場合も、忘れずにchmod a+xしておきましょう。この辺は普通にshellのノウハウで適当にやってください。)

Haspコードをコンパイルする

ここまでで、*.haspファイルを作り、それを「hload」フックを使ってインタープリタにロードする際に、副次的に*.hsファイルを生成する、という順序で、 HaspコードがHaskellコードに変化する手順を見てきました。

Hasp一式には、Haspコードをコマンドラインからコンパイルする為の、 faspsという名前のスクリプトが含まれています。 faspsのインターフェースはものすごくシンプルです。 faspsはHaspコードを標準入力から読み取り、そしてHaskellコードを標準出力に書き出すのです。

以下のように、shellのリダイレクト機能が利用できる事を憶えておいてください。

 bin/fasps < mycode.hasp > mycode.hs

(訳注:ここでの「コンパイル」はghc等のコンパイル機能を呼び出して実行バイナリを生成する、という意味ではなく、上記の通り、単なるHaskellソースコードを生成する事を指して「コンパイル」と言っている点に注意。もし実行バイナリが欲しい場合は、faspsで生成した*.hsを改めてghc等にかける必要がある。)

(訳注:このfaspsも、先のgaspsと同じように、ラッパースクリプトを作っておくといいでしょう。)

マクロ

ある時あなたは、書いてみたコードのいくつかに共通したパターンがある事に気付くでしょう。 そういう時は大体、うまい高階抽象化や、抽象化用のモナド利用でそのパターンを共通化する事ができます。 ですが時たま、lambdaやモナドでは抽象化できない繰り返しが出現します。 その時がマクロの出番です。

『全てのマクロにおいて、元コードと、そのコードをマクロで変形して生成した、 冗長かつ抽象度が低くなったコードとは、 完全に等価である。』
この事を憶えておく事が、マクロを理解する秘訣です。

まあ、こんなマクロの自慢話はもうたくさんでしょう、 話を進めて要件を明確にしてみましょう。

HaspコンパイラはSchemeで書かれているので、 Schemeコードを使ってHaspマクロを定義するのは手軽で便利です。 Hasp同梱の「src」ディレクトリの中には 「std-macros.scm」と「usr-macros.scm」という 二つのファイルがあります。 「std-macros.scm」ファイルは標準的なHaspマクロの定義を含んでいます。 「usr-macros.scm」ファイルはほとんど空で、 ここにはあなたがSchemeマクロ定義を書いていく事ができます。 ここで「std-macros.scm」ファイルが「usr-macros.scm」ファイルよりも優遇されていたりという事がある訳ではありません。 この二つのファイルの内容をそっくり交換しても、多分そのまま動くでしょう。 しかしこの二つを区別しておく事で、 「std-macros.scm」ファイルをアップデートする際に あなたが書いたマクロが上書きされる心配がないというメリットが得られます。

(訳注:tarballから導入した人は依然として気をつけないとアップデート時に上書きされてしまう点に注意。githubから取得した人は普通にgitに保存しておけば、アップデートやマージはgitに任せる事ができるでしょう。)

(訳注:マクロ定義は上の「*-macros.scm」ファイル内でのみ可能なようです。replや*.hasp内での定義は普通にはできない様子。)

丁寧なマクロの解説

それでは「std-macros.scm」にあるマクロを見ていきましょう。 その際には、 どうして私がこれを書いたのか、 そしてどのように私がこれを実装したのか、 という辺りに注意してもらえればと思います。 ここではその内の「defn」を見てみます。

通常、Haskellで関数を定義する際には、 まず型シグネチャ定義を行い、 場合によっては更に関数のための基本的な引数パターンマッチ定義も行うので、 最低でも一個以上の引数パターンマッチ定義を書く事になります(訳注:訳が微妙、あとで考え直す)。 つまり関数定義の度に、関数名を二回(訳注:型指定の為と関数本体定義の為)と、 それぞれの関数定義の為の少量のボイラープレートを繰り返させられます。 冗長なのが嫌いな私は、この事をずっと前から気にしていたので、Clojureのdefnフォームをベースにしたdefnマクロを作りました。

マクロを書く第一歩は、 まずマクロ化したい元コードを用意する事です。 ここでは以下のコードを考えます。

 ;; このコードは少し奇妙に見えるかもしれません。
 ;; Haskellとは違い、Haspでの全てのコードはlambda式であり、
 ;; 引数パターンマッチ定義ではありません。
 ;; 後述のdefnのおかげで、まだ正気の内に以下のコードと同一の定義を行う事ができます。
 ;; (訳注:下のコードは冗長なので発狂しそうだ、と言いたいんだと思う。)
 
 (:: myfunc typex)
 (def (myfunc 引数群...)
   (case-of (tup 引数群...)
     ((tup パターン...) 定義...)
     ((tup パターン...) 定義...)))
 
 ;; (訳注:::, typex, def, case-of, tupの詳細についてはリファレンスを参照)

そして実際に書きたいコードは以下になります。

 (defn myfunc typex
   ((パターン... ) 定義...)
   ((パターン... ) 定義...))

上記より、このdefnマクロは 「(defn name typex . 定義群)」の形を取る事が分かります。 確かに、 defnのリファレンスを見ても、 全く同じ形が記述されています。

ところで、元のコード中に出現していた「引数群...」が、実際に書きたい方のコード中には出現しない事に気付きましたか。 この「引数群...」は、あとでScheme手続きであるgensを使って生成します。

ここまではokですか?では定義を続けましょう。 (訳注:HaspやSchemeでは) 複数の式を生成したら、 以下のように「begin」式で囲む必要があります。

 (defmac (defn name typex . 定義群)
   `(begin
     (:: ,name ,typex)
     (def (,name ,@引数群) (case-of (tup ,@引数群) ,@いろいろ))))

「`」(バッククォート)は、この後の一群の式がこのマクロの返り値である事を示しています。 また「,」(コンマ)は、この場所に別の一つの式を埋め込む事を示しています。 そして「,@」(コンマとアットマーク)は、この場所に複数の式のリストを埋め込む事を示しています。 この式はまだ未完成で、 式中の「引数群」と「いろいろ」の値をどうにか解決する必要があります。 ではまず「引数群」から始めます。

 (defmac (defn name typex . 定義群)
   (let ((引数群 (map (lambda (x) (gens 'v)) (caar 定義群))))
     `(begin
       (:: ,name ,typex)
       (def (,name ,@引数群) (case-of (tup ,@引数群) ,@いろいろ)))))

ここでは、 最初の方の式にあった「(パターン...)」リストの中にある引数を、 「gens」手続きによって生成されたuniqueなシンボルを使って、 「引数群」に写します。

(訳注:バッククォートで結果として返すS式はHaspのコードだが、let, map, lambda, gens, caar等のある領域は完全にSchemeのコード。)

次は、「いろいろ」の値をどうにかして解決する必要があります。 その為に、定義群のそれぞれを横断しつつ処理します。 ここでは、下に示したSchemeによるlambda式を、map手続きに渡す為に用意しました。

 (lambda (定義) (cons (cons 'tup (car 定義)) (cdr 定義)))

このコードでの、 「(car 定義)」で取得しているのは、 二番目に出てきたdefnのコードでの「(パターン... )」部分のリストです。 これの直前に「tup」を追加する事で、一番最初のdefのコードに出てきた「(tup パターン...)」の形にします。 またその次の「(cdr 定義)」で取得しているのは同様に、 二番目に出てきたdefnのコードでの「定義...」部分で、 これはそのまま一番最初のdefのコードの「case-of」フォームに結合されます。

 (defmac (defn name typex . 定義群)
   (let ((引数群 (map (lambda (x) (gens 'v)) (caar 定義群))))
     `(begin
       (:: ,name ,typex)
       (def (,name ,@引数群)
         (case-of (tup ,@引数群)
           ,@(map
             (lambda (定義)
               (cons (cons 'tup (car 定義)) (cdr 定義)))
             定義群))))))

最後に、 「(tsk tsk)」のような、 定義群はあるけれど型シグネチャが省略されているコードを受け付けられるように、 typexに「()」(空リスト)を渡せるようにします。

 (defmac (defn name typex . 定義群)
   (let ((変数群 (map (lambda (x) (gens 'v)) (caar 定義群))))
     `(begin
       ,(if (null? typex) '(begin) `(:: ,name ,typex))
       (def (,name ,@変数群)
         (case-of (tup ,@変数群)
           ,@(map
             (lambda (定義)
               (cons (cons 'tup (car 定義)) (cdr 定義)))
             定義群))))))

上記のコード中にある、空のbeginである「(begin)」は、 NOPコードの類として機能し、 何もコードを出力しません。

さらなるマクロ用ツール

以上でdefnマクロが完全に定義されました。 この通り、 コード片を生成する為にSchemeの手続きを使ってもいいのです。 実際問題、 マクロはfirst-classなのです。 つまり、

 (defmac (foo a) `(list ,a))

というコードは完全に以下のコードと同等になります。

 (defmac foo (lambda (a) `(list ,a)))

これはつまり、 マクロ関数のカリー化のような、 lambdaが持つあらゆる利点を利用できるという事です。

(訳注:Schemeを知っている人なら、「(define (hoge arg) ...)」が「(define hoge (lambda (arg) ...))」の構文糖だという事を知っているでしょう。前者から後者への変換は処理系によって自動的に行われる為、「(define ((hoge arg1) arg2) ...)」と書いた場合、大抵のScheme処理系では自動的に「(define hoge (lambda (arg1) (lambda (arg2) ...)))」と解釈されます。「マクロ関数のカリー化」というのは、この機能の事を指しているんだと思います、多分)

尚、 Haspは「gens」と「gent」という、 ランダムなシンボルおよび型名を生成する為のScheme手続きを提供しています。 この二つは、 「gens」は小文字始まりのシンボルを作成し、 「gent」は大文字始まりのシンボルを作成する、 という点が違うだけです。

最後は「macroexpand」です。 「macroexpand」は Haspコードを入力として取り、 そのコードを返します。 ただし、 もし渡されたHaspコードがマクロ呼び出しだった場合、 返されるコードはマクロ展開が行われたものになります。

今どこ?

残りの昼休みは、以下のリンクを見る時間に使えます。

Haspを良くする為のフィードバックがあれば是非送って下さい。

(訳注:ここにAli Clarkさんのメールアドレスが貼ってあったけど省略。フィードバックのある方は元ページの一番下を見てメールしてあげてください。翻訳に関するフィードバックは下の「連絡」のところからおねがいします)

付録

以下は、 http://ali.clark.gb.net/hasp/ の一部セクションの翻訳です。

Haspについて

Haspのゴールは、 地上最強の二つの言語、HaskellとLispの持つ最強機能同士の合体です。 Haskellは強い型付けと遅延評価を持つ関数型言語で、 高速かつバグのないソフトウェア開発に優れていると多くの人々が評価しています。 Lispはあらゆる言語で最強のマクロ機能性を持ち、 Lisp内では万物がリストで扱われます (訳注:訳がおかしいかも)。 この二つが合体した結果、 プログラミング可能な構文と純粋な意味論を持つHaspが生まれました。

動作概要

(翻訳予定)

どうしてHaspが必要なのか

(翻訳予定)

Liskellとの比較

Liskellは、 HaskellにLisp風メタプログラミング機能を追加する別のアプローチ手法を取っています(訳注:現在はliskell.orgはなくなってしまっている様子)。 プロジェクトとしてのHaspは、 Liskellのサイトが落ちていた時に始められました。 しかしこれからはHaspの時代です(訳注:訳が微妙)。 なぜならHaspは目的達成の為に、 Liskellとは根本的に違うアプローチ手法を取っているからです。
LiskellはGHCのAPIの上に実装され、 Liskell自体のコードサイズは Haspよりも大きいです。
それに比べると、 Haspは寄生生物に近いです。 流動的にホスト間を移動する事ができ、 Haskellコンパイラと同じマシンで Haspを実行する事すら必要としません。
より具体的な話をすると、 Haspは、 最低でも二つのScheme処理系(訳注:GuileとGambit)と、 インストールスクリプト が何個かの質問の後でインストールを許容するScheme処理系 にて動かす事ができます。
Haskellインタープリタがコードを実行できる事に関してすら、 Haspはずっと柔軟です。
なぜならHaspは、 Haskellコードと 非Haskellコードの構文木を扱うので、 大抵の実験的なHaskellインタープリタすら、 Haspで上手く扱う事ができるのです。

HaspのLiskellに対する最大のアドバンテージは Haskellコンパイラとしての実装以外の部分にあります。 あなたはHaspコードを書き、 それをHaskellコードへとコンパイルし、 そしてほとんどのHaskellインタープリタでそれを実行する事ができるのです。 Haspによって、 あなたはHaskellとより身近になれます。 HaspコードがHaskellコードのように感じられるかどうか迷っているなら、 haspsを試してみる事ができます。

逆に、HaspのLiskellに対する最大のディスアドバンテージは、 Haspのメタプログラミングステージの抽象化された本質に対する手数料として、 完全なgensymが可能でない事です。 勿論、これが致命的な問題になるとは思いません。 この件の詳細については制約の項目を見てください。

HaspはHaskell寄りであり、LiskellはLisp(特にScheme)寄りだという事です。

哲学

このプロジェクトの、 パイプ可能なコマンドラインツールから、 インタープリタのフックや、 あなたが今みているこの意味論的マークアップまで、 これらが極端にオープンでhack可能である事、 そしてそれが実際にそうなっている事を実際に目で見て簡単に確認できる事を、 Haspは目指しています。

メタプログラミング言語を実装する為に、コンパイラをhackするような重い作業は必要ないという事を証明する事も、このプロジェクトは目指しています。 STDINとSTDOUTを利用できるパワフルなUNIX哲学の力を持つ ツールを作成する事と、 モジュール式インターフェースを提供する事も、 このプロジェクトは目指しています。 メタプログラミングは簡単な問題であり、シンプルな解法を持たなくてはならない、 とHaspは信じています(訳注:主語が変かも)。 コードが1000行に満たない事によって、 Haspは適切に小さく、 そしてそれによって メンテナンス性と改良のしやすさの両方を向上させています。

LispのS式リーダーとHaskellインタープリタ、 両方とも既に完成しているものなので、 この二つを適切に合体させて、 よりリッチかつパワフルな言語にする事がHaspの役割です。

制約

Hasp処理系によってHaspコードからHaskellコードを生成するフェーズと、Haskell処理系によってHaskellコードが実行されるフェーズは、完全に分離しているので、それが原因で、Liskellで実装されているような完全なgensymは実装できませんでした。 代わりにHaspは、「s_gensym_20」のような無作為な変数名を適切に使います。 これは極簡単なhackですが、 あなたが醜い変数名を使う癖を持っていない限りは、 あなたに影響を与える事はないでしょう。 この際の前置句として使われるのは「s_gensym_」および「T_gensym_」ですので、 これらで始まる変数名は決して使わないようにしてください。

Haspのgensymを安全に利用する為のガイドライン:

Haskellと同様に、Haspは、 あるものが中置記法なのか、それともそうでないのかを知る必要があります。 もしHaskellに標準で定義されている以外の中置記法演算子を利用したい時は、 「(definfix myop)」というコードをusr-macros.scmに追加する必要があります。 ここの「myop」は中置演算子の名前で、 これを行う事によって、 「myop」を普通の前置記法の手続きとして使えるようになります。

その他メモ

(翻訳予定)

TODO

訳語について

原文と翻訳との比較は、このページ右上の「編集履歴」から一番上のRevにて「View this version」を選んでwilikiソース表示する事で、それなりに見れます。

Haspy Haskell thing

考えた結果、これは訳さないでそのままにする事にした(無理に訳するなら「Hasp化されたHaskell構成要素」あたり?)

function

基本的には「関数」だが、Scheme(主にマクロ部分)での文脈では「手続き」と訳する事にした

form

基本的に「フォーム」のままとした(一箇所だけ「形」と訳したところあり)。これはLispでの特殊形式(special-form)の事で間違いないと思うが、「特殊形式」だとよく分からない。でももうちょっといい訳語があれば直したいところ

statement

いろいろ考えた結果、文面に応じて「式」か「S式」と訳す事に

pattern definition

これはどうも、Haskellのパターンマッチ機能(引数ディスパッチ的な奴)の事らしい。あとで日本語での正式名称調べておく事。→調べた。「パターンマッチ」という訳語でよいようだ。ここでは「引数パターンマッチ定義」とした

true gensyms

この語が出てくる文脈が「Haspのgensymは不完全だという話」(通常のLispのgensymはもし表示名が重なったとしてもシンボルとしては別物として扱ってもらえるが、Haspはそうなってない)なので、「完全なgensym」と訳す事にした。複数形なのは「gens」と「gent」の二つが提供されている為だと思われる。

訳者によるHasp感想

履歴

連絡

このページへの意見、感想、翻訳ミスの指摘などありましたら、下のリンクからおねがいします。

http://d.hatena.ne.jp/ranekov/comment?date=20120512#c


最終更新 : 2012/05/13 14:35:56 JST