web-dev-qa-db-ja.com

HaskellでHaskellインタープリターを作成する

古典的なプログラミングの練習は、LISP/SchemeでLISP/Schemeインタープリターを作成することです。完全な言語の能力を活用して、言語のサブセットのインタープリターを作成できます。

Haskellにも同様の演習はありますか? Haskellをエンジンとして使用して、Haskellのサブセットを実装したいと思います。もちろんそれはできますことができますが、確認できるオンラインリソースはありますか?


私はHaskellを言語として使用して、 離散構造 コースのいくつかの概念を学習するという考え方を模索しています。今学期は、Haskellに影響を与えた小さな言語 Miranda を選択しました。ミランダは私がやりたいことの約90%をしますが、ハスケルは約2000%します。 :)

だから私の考えは、私が望むHaskellの機能を正確に備え、他のすべてを許可しない言語を作成することです。生徒が上達するにつれ、基本をマスターしたら、さまざまな機能を選択的に「オン」にすることができます。

教育学的な「言語レベル」は、 Java および Scheme を教えるためにうまく使用されています。彼らができることを制限することによって、彼らがあなたが教えようとしている構文と概念をまだ習得している間、彼らが足で自分自身を撃つことを防ぐことができます。そして、あなたはより良いエラーメッセージを提供することができます。

88
Barry Brown

私はあなたの目標を愛していますが、それは大きな仕事です。いくつかのヒント:

  • 私はGHCに取り組みましたが、ソースの一部は必要ありません。 Hugs ははるかにシンプルでクリーンな実装ですが、残念ながらCで実装されています。

  • これは小さなパズルのピースですが、マークジョーンズは Typing Haskell in Haskell と呼ばれる美しい論文を書きました。フロントエンドの開始点。

幸運を!教室からの証拠を裏付けて、Haskellの言語レベルを特定することは、コミュニティにとって大きな利益となり、間違いなく出版可能な結果となります。

75
Norman Ramsey

完全なHaskellパーサーがあります: http://hackage.haskell.org/package/haskell-src-exts

いったんそれを解析したら、特定のものを取り除くか禁止することは簡単です。私はこれをtryhaskell.orgがインポート文を禁止するため、トップレベルの定義をサポートするためなどに行いました。

モジュールを解析するだけです:

parseModule :: String -> ParseResult Module

次に、ASTがあります:

Module SrcLoc ModuleName [ModulePragma] (Maybe WarningText) (Maybe [ExportSpec]) [ImportDecl] [Decl]    

Declタイプは広範囲にわたります: http://hackage.haskell.org/packages/archive/haskell-src-exts/1.9.0/doc/html/Language-Haskell-Exts-Syntax.html#t% 3ADecl

あなたがする必要があるのは、ホワイトリストを定義することです-どのような宣言、インポート、シンボル、構文が利用可能であるか、そしてASTを歩き、あなたが何もしないときに「解析エラー」を投げますASTのすべてのノードにアタッチされているSrcLoc値を使用できます。

data SrcLoc = SrcLoc
     { srcFilename :: String
     , srcLine :: Int
     , srcColumn :: Int
     }

Haskellを再実装する必要はありません。よりわかりやすいコンパイルエラーを提供する場合は、コードを解析し、フィルター処理して、コンパイラーに送信し、コンパイラーの出力を解析します。それが「予想されるタイプaと推測されるa -> bとを一致させることができませんでした」である場合は、関数への引数が少なすぎることがわかります。

Haskellを最初から実装したり、Hugsの内部​​をいじったりして本当に時間を費やしたくないのでない限り、GHCに渡されるものをフィルターにかけるだけでよいと思います。そうすれば、生徒がコードベースを次のステップに進め、本格的な本格的なHaskellコードを書きたい場合、移行は透過的です。

37

通訳をゼロから構築しますか?ラムダ計算やLISPバリアントのようなより簡単な関数型言語を実装することから始めます。後者については 48時間以内に自分でスキーマを書く と呼ばれる非常に素晴らしいWikibookがあり、構文解析と解釈のテクニックをクールで実用的な方法で紹介しています。

型クラス、非常に強力な型システム(型推論!)、遅延評価(簡約手法)などの非常に複雑な機能を扱う必要があるため、Haskellを手作業で解釈することははるかに複雑になります。

そのため、使用するHaskellのごくわずかなサブセットを定義し、Scheme-exampleの例を段階的に拡張することから始める必要があります。

添加:

Haskellでは、パーサー、コンパイラー、そしてもちろんインタープリターを含むインタープリターAPI(少なくともGHCの下で)への完全なアクセス権があることに注意してください。

使用するパッケージは hint(Language.Haskell。*) です。残念ながら、これに関するオンラインチュートリアルは見つかりませんでしたし、自分で試したこともありませんが、非常に有望に見えます。

24
Dario

私が望むHaskellの機能を正確に備え、他のすべてを許可しない言語を作成します。生徒が上達するにつれ、基本をマスターしたら、さまざまな機能を選択的に「オン」にすることができます。

この問題に対しては、より単純な(作業が少ないなど)解決策をお勧めします。機能をオフにできるHaskell実装を作成する代わりに、Haskellコンパイラをプログラムでラップし、最初に、コードが許可しない機能を使用していないことを確認してから、既成のコンパイラを使用してコンパイルします。

これは HLint に似ています(また、その逆も同様です)。

HLint(以前のDr. Haskell)はHaskellプログラムを読み、できれば読みやすくする変更を提案します。また、HLintを使用すると、不要な提案を簡単に無効にしたり、独自のカスタム提案を追加したりできます。

  • 独自のHLint「提案」を実装して、許可しない機能を使用しないようにする
  • 標準のHLint提案をすべて無効にします。
  • ラッパーに変更されたHLintを最初のステップとして実行させます。
  • HLintの提案をエラーとして扱います。つまり、HLintが「不平を言う」場合、プログラムはコンパイル段階に進みません。
20
yairchu

Baskellは教育の実装です http://hackage.haskell.org/package/baskell

たとえば、実装する型システムを選択することから始めます。これは、Schemeのインタプリタと同じくらい複雑です http://hackage.haskell.org/package/thih

16
Don Stewart

コンパイラのEHCシリーズはおそらく最善の策です。それは活発に開発されており、まさに望んでいるもののようです-Haskell '98で最高潮に達した一連の小さなラムダ計算コンパイラ/インタプリタ。

しかし、PierceのTypes and Programming Languagesで開発されたさまざまな言語、またはヘリウムインタープリター(学生向けの不自由なHaskell http://en.wikipedia.org/wiki/Helium_(Haskell) )。

6
gwern

実装が簡単なHaskellのサブセットを探している場合は、型クラスと型チェックを省略できます。型クラスがなければ、Haskellコードを評価するために型推論は必要ありません。

Code Golfチャレンジのために 自己コンパイル型Haskellサブセットコンパイラ を書きました。入力でHaskellサブセットコードを受け取り、出力でCコードを生成します。より読みやすいバージョンが利用できないのは残念です。ネストされた定義を自己コンパイルする過程で手動で持ち上げました。

Haskellのサブセットのインタープリターの実装に関心のある学生には、以下の機能から始めることをお勧めします。

  • 遅延評価。インタプリタがHaskellにいる場合は、このために何もする必要がない場合があります。

  • パターンが一致する引数とガードを持つ関数定義。変数、短所、nil、および_パターンのみを考慮してください。

  • 単純な式の構文:

    • 整数リテラル

    • 文字リテラル

    • [](なし)

    • 関数適用(左結合)

    • 接頭辞:(cons、right associative)

    • 括弧

    • 変数名

    • 関数名

より具体的には、これを実行できるインタープリターを作成します。

-- tail :: [a] -> [a]
tail (_:xs) = xs

-- append :: [a] -> [a] -> [a]
append []     ys = ys
append (x:xs) ys = x : append xs ys

-- zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
zipWith f (a:as) (b:bs) = f a b : zipWith f as bs
zipWith _ _      _      = []

-- showList :: (a -> String) -> [a] -> String
showList _    []     = '[' : ']' : []
showList show (x:xs) = '[' : append (show x) (showItems show xs)

-- showItems :: (a -> String) -> [a] -> String
showItems show []     = ']' : []
showItems show (x:xs) = ',' : append (show x) (showItems show xs)

-- fibs :: [Int]
fibs = 0 : 1 : zipWith add fibs (tail fibs)

-- main :: String
main = showList showInt (take 40 fibs)

型チェックはHaskellの重要な機能です。ただし、何もない状態から型チェックを行うHaskellコンパイラに移行することは非常に困難です。上記のインタープリターを作成することから始める場合、タイプチェックを追加することはそれほど難しくありません。

6
Joey Adams

これ は良い考えかもしれません-HaskellでNetLogoの小さなバージョンを作成してください。 ここ は小さなインタプリタです。

3
Claudiu

Haskellパーサーを持っている Happy (Haskellのyaccのようなパーサー)を見てください。

3
Kathy Van Stone

helium が標準のhaskellよりも上に構築するためのより良いベースになるかどうかを確認してください。

2
Martin DeMello

Uhc/Ehcは、Haskellのさまざまな機能を有効/無効にする一連のコンパイラです。 http://www.cs.uu.nl/wiki/Ehc/WebHome#What_is_UHC_And_EHC

2
ja.

Andrej Bauerの プログラミング言語動物園 は、「ミニハスケル」と呼ばれる、純粋に機能的なプログラミング言語の小さな実装です。 OCamlは約700行なので、非常に簡単に消化できます。

このサイトには、MLスタイル、Prologスタイル、およびOOプログラミング言語のおもちゃバージョンも含まれています。

2
niklas

Idris はかなりコンパクトなパーサーを持っていると言われていますが、変更に本当に適しているかどうかはわかりませんが、Haskellで書かれています。

2
Jeff Burdges

自分でHaskellインタープリターをゼロから作成するよりも、 GHCソース を使用して不要なものを取り除く方が簡単だと思いませんか?一般的に言って、削除機能に関与する労力はlot少ないはずですフィーチャーの作成/追加とは対照的に。

とにかくGHCはHaskellで書かれているので、技術的にはHaskellで書かれたHaskellインタープリターについてのあなたの質問に応えます。

全体を静的にリンクさせ、カスタマイズされたGHCiのみを配布して、学生が他のHaskellソースモジュールをロードできないようにするのは、それほど難しくありません。他のHaskellオブジェクトファイルを読み込まないようにするためにどれだけの作業が必要かについては、私にはわかりません。クラスに不正行為者がたくさんいる場合は、FFIも無効にすることをお勧めします。

1
Mark Rushakoff

LISPインタープリターが非常に多いのは、LISPが基本的にJSONの前身であるデータをエンコードする単純な形式だからです。これにより、フロントエンド部分の取り扱いが非常に簡単になります。それと比較して、Haskellは、特に言語拡張機能を備えているため、解析が最も簡単な言語ではありません。これらは、正しく理解するのが難しいように聞こえる構文の構成要素です。

  • 設定可能な優先順位、結合性、固定性を備えた演算子、
  • ネストされたコメント
  • レイアウト規則
  • パターン構文
  • do-ブロックとモナディックコードへの脱糖

オペレーターを除いて、これらのそれぞれは、コンパイラー構築コースの後に学生が取り組むことができますが、Haskellが実際に機能する方法から焦点を離します。それに加えて、Haskellのすべての構文構造を直接実装するのではなく、パスを実装してそれらを取り除くことができます。これは、問題の文字通りの核心に私たちを連れて行きます。

私の提案は、完全なHaskellではなくCoreのタイプチェックとインタープリターを実装することです。これらのタスクはどちらも、すでにそれ自体が非常に複雑です。この言語は、依然として強く型付けされた関数型言語ですが、最適化とコード生成の点で処理するのがはるかに簡単です。ただし、基盤となるマシンからはまだ独立しています。したがって、GHCはそれを中間言語として使用し、Haskellのほとんどの構文構成をそれに変換します。

さらに、GHC(または別のコンパイラ)のフロントエンドの使用をためらわないでください。カスタムLISPはHost LISPシステムのパーサーを使用するため(少なくともブートストラップ中)、私はそれを不正行為とは見なしません。 Coreスニペットをクリーンアップし、元のコードと一緒に生徒に提示することで、フロントエンドの機能の概要と、フロントエンドを再実装しない方が望ましい理由を説明できます。

GHCで使用されているCoreのドキュメントへのリンクをいくつか次に示します。

0
MauganRa