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


90

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

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


これがその裏話です。

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

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

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


HaskellのTyping Haskellをベースとして実装されたWIP Haskell方言を持っています。デモはこちらchrisdone.com/toys/duet-delta公開のオープンソースリリースの準備ができていませんが、興味があればソースを共有できます。
Christopher Done 2017

回答:


76

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

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

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

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


2
GHCについてのコメントはまだ正確なのだろうか。GHCは複雑ですが、十分に文書化されています。特に、内部Notesは低レベルの詳細を理解するのに役立ちます。オープンソースアプリケーションのアーキテクチャの GHCに関する章は、優れた高レベルの概要を提供します。
sjy 2014

37

完全な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コードを作成したい場合、移行は透過的です。


24

通訳をゼロから構築しますか?ラムダ計算やlispバリアントのようなより簡単な関数型言語を実装することから始めます。後者の場合は、48時間でWrite your a Schemeと呼ばれる非常に優れたウィキブックがあり、構文解析と解釈のテクニックをクールで実用的な方法で紹介しています。

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

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

添加:

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

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


12
型推論は実際には非常に簡単な20〜30行のアルゴリズムです。そのシンプルさで美しいです。遅延評価もエンコードが難しくありません。難しいのは、非常識な構文、パターンマッチング、そして言語の膨大な量にあると思います。
Claudiu

興味深い-型推論アルゴリズムのリンクを投稿できますか?
ダリオ

5
ええ、この無料の本をチェックしてください-cs.brown.edu/~sk/Publications/Books/ProgLangs/2007-04-26-、それは273ページ(pdfの289)にあります。alg疑似コードはP296にあります。
Claudiu

1
関数型プログラミング言語の実装」には、(?)型推論/チェックアルゴリズムの実装もあります。
Phil Armstrong

1
ただし、型クラスによる型推論は簡単ではありません。
Christopher Done 2017

20

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

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

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

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

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


6

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

しかし、Pierceのタイプとプログラミング言語で開発されたさまざまな言語、またはヘリウムインタープリター(学生向けの不自由なHaskell http://en.wikipedia.org/wiki/Helium_(Haskell))も確認できます。


6

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

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

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

  • 遅延評価。インタープリターがHaskellにいる場合は、何もする必要がない可能性があります。

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

  • 単純な式の構文:

    • 整数リテラル

    • 文字リテラル

    • [] (なし)

    • 関数適用(左結合)

    • 接頭辞:(cons、右関連)

    • 括弧

    • 変数名

    • 関数名

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

-- 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コンパイラに移行することは非常に困難です。上記のインタープリターを作成することから始める場合、タイプチェックを追加することはそれほど難しくありません。


「遅延評価。インタプリタがHaskellにある場合、これのために何もする必要がない場合があります。」これは本当ではないかもしれません。Haskellでのレイジーインタープリターの実装に関する詳細については、haskell.org / wikiupload / 0 / 0a / TMR-Issue10.pdfにある Naylorの記事を参照してください。
Jared Updike 14年


3

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


リンクは死んでいます。このコンテンツがまだどこかにまだ存在している可能性はありますか?気になります...
Nicolas Payette

うーん、それはブログ投稿でした、そして、それを検索するためにどのキーワードを使うべきか私にはわかりません。リンクを提供するときに、より重要な情報を含めるための良いレッスン...
Claudiu

1
「netlogo haskell」のGoogle検索が表示されます...この質問。とにかく、大したことはありません。ありがとう!
Nicolas Payette



2

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


2

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

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


1

独自のHaskellインタープリターをゼロから作成するよりも、GHCソースを取得して不要なものを取り除くほうが簡単だと思いませんか?一般的に言えば、機能の作成/追加とは対照的に、機能の削除に伴う労力は大幅に少なくなります。

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

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


1
多くの機能は他の機能に依存しているため、これは思ったほど簡単ではありません。しかし、おそらくOPは単にPreludeをインポートせず、代わりに独自のものを提供したいと考えています。ご覧のHaskellのほとんどは、ランタイムの特定の機能ではなく、通常の関数です。(もちろん、たくさんあります。)
jrockway 2009

0

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

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

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.