web-dev-qa-db-ja.com

Haskellレコードの構文

Haskellのレコード構文は、その醜い構文と名前空間の汚染のために、他の点ではエレガントな言語ではいぼであると多くの人が考えています。一方、位置ベースの代替よりも多くの場合より便利です。

次のような宣言の代わりに:

data Foo = Foo { 
  fooID :: Int, 
  fooName :: String 
} deriving (Show)

これらの線に沿った何かがより魅力的であるように私には思えます:

data Foo = Foo id   :: Int
               name :: String
               deriving (Show)

私が見逃していることには十分な理由があるに違いないと思いますが、Cベースのレコード構文がよりクリーンなレイアウトベースのアプローチに採用されたのはなぜですか?

次に、名前空間の問題を解決するためにパイプラインに何かがあるので、id foo の代わりに fooID foo Haskellの将来のバージョンでは? (現在利用可能な、長期にわたる型クラスに基づく回避策は別として。)

52
Rob Agar

他の人が試してみるつもりがない場合は、これらの質問に答えるためにもう少し(少し慎重に調査された)試してみます。

tl; dr

質問1:サイコロが転がりました。それは状況に応じた選択であり、行き詰まりました。

質問2:はい(ちょっと)。いくつかの異なる当事者がこの問題について確かに考えてきました。

関連性があり興味深いとわかったリンクと引用に基づいて、各回答の非常に長い説明を読んでください。

Cのようなレコード構文が、よりクリーンなレイアウトベースのアプローチよりも採用されたのはなぜですか?

マイクロソフトの研究者は History of Haskell の論文を書きました。セクション5.6では、レコードについて説明します。洞察に満ちた最初の小さな部分を引用します。

Haskellの初期のバージョンで最も明らかな欠落の1つは、名前付きフィールドを提供するレコードがないことでした。レコードが実際に非常に役立つとすると、なぜ省略されたのですか?

Microsoftiesはそれから彼ら自身の質問に答えます

最も強い理由は、明らかな「正しい」デザインがなかったためと思われます。

詳細については自分で読むこともできますが、Haskellは「データ構造の名前付きフィールドへの圧力」により、最終的にレコード構文を採用したと言っています。

Haskell 1.3設計が進められていた頃には、1993年に、データ構造内の名前付きフィールドに対するユーザーからの圧力が強かったため、委員会は最終的にミニマリスト設計を採用しました...

なぜそれがなぜなのかを尋ねますか?まあ、私が理解していることから、初期のHaskellersのやり方があったら、そもそもレコード構文を持っていなかったかもしれません。このアイデアは、Cライクな構文に既に慣れていて、「Haskellの方法」ではなく、CライクなことをHaskellに取り入れることに興味を持っていた人々によって、Haskellにプッシュされたようです。 (はい、これは非常に主観的な解釈であることを理解しています。私は間違っている可能性がありますが、より良い答えがなければ、これは私が描くことができる最良の結論です。)

名前空間の問題を解決するためにパイプラインに何かありますか?

まず、誰もがそれが問題だと感じているわけではありません。数週間前、 Racket 愛好家は、同じ名前の異なる関数を使用することは悪い考えだと私(および他の人)に説明しました。「___という名前の関数は何をするのか」の分析が複雑になるためです。実際には1つの機能ではなく、多くの機能があります。型の推論が複雑になるため、Haskellにとってこのアイデアはさらに厄介なものになる可能性があります。

少し正直に言えば、MicrosoftはHaskellの型クラスについて興味深いことを言っています。

ワドラーとブロットが言語設計がまだ流動的だったちょうどその瞬間にこの重要なアイデアを生み出したのは、幸運なタイミングの偶然でした。

ハスケルがかつて若かったことを忘れないでください。一部の決定は、それらが行われたという理由だけで行われました。

とにかく、この「問題」に対処できる興味深い方法がいくつかあります。

Type Directed Name Resolution 、Haskellへの修正案(上記のコメントで言及)。そのページを読んで、言語の多くの領域に触れていることを確認してください。全体として、それは悪い考えではありません。物事と衝突しないように多くの考えが入れられています。ただし、今は(もっと)成熟したHaskell言語にするためには、かなりの注意が必要です。

別のマイクロソフトの論文 OO Haskell は、「アドホックオーバーロード」をサポートするためのHaskell言語への拡張を具体的に提案しています。かなり複雑なので、セクション4を自分で確認する必要があります。その要点は、「Has」型を自動的に(?)推論し、それらが「improvement」を呼び出すことを型検査する追加のステップを追加することです。

クラス制約Has_m (Int -> C -> r)がある場合、この制約に一致するmのインスタンスは1つだけです...ただ1つの選択肢があるため、ここでそれを実行する必要があります。これにより、rが次のように修正されます。 Intになります。したがって、fの予想されるタイプを取得します:f :: C -> Int -> IO Int... [this]は単なる設計上の選択であり、クラスの概念に基づいたものですHas_mは閉じています

矛盾した引用の謝罪;それがあなたに役立つなら、それなら素晴らしいですが、そうでなければただ紙を読んでください。これは複雑な(しかし説得力のある)アイデアです。

Chris Doneは、テンプレートHaskellを使用して Haskellでのダック入力 をOO Haskell論文(「Has」タイプを使用)とほぼ同じように提供しています。いくつかのインタラクティブセッション彼のサイトからのサンプル:

λ> flap ^. donald
*Flap flap flap*
λ> flap ^. chris
I'm flapping my arms!

fly :: (Has Flap duck) => duck -> IO ()
fly duck = do go; go; go where go = flap ^. duck

λ> fly donald
*Flap flap flap*
*Flap flap flap*
*Flap flap flap*

これには少しボイラープレート/異常な構文が必要であり、私は個人的には型クラスにこだわりたいと思います。しかし、この地域での現実的な作品を自由に公開してくれたことに対するChris Doneへの称賛。

45
Dan Burton

名前空間の問題に対処するリンクを追加すると思いました。 GHCのオーバーロードされたレコードフィールドOverloadedRecordFields拡張子 を使用してGHC 7.10に(そしておそらくすでにHEADに)入っているようです。

これにより、次のような構文が可能になります

data Person = Person { id :: Int, name :: String }
data Company { name :: String, employees :: [Person] }

companyNames :: Company -> [String]
companyNames c = name c : map name (employees c)
8
crockeea

[編集]この答えは、問題についての私のランダムな考えのほんの一部です。この答えよりも他の答えをお勧めします。その答えのために、他の人の作品を調べて参照するのに多くの時間を費やしたからです。

レコード構文

暗闇の中で少し試してみてください。「レイアウトに基づく」提案された構文は、非レコード構文のdata宣言によく似ています。解析が混乱する可能性があります(?)

--record
data Foo = Foo {i :: Int, s :: String} deriving (Show)
--non-record
data Foo = Foo Int String deriving (Show)
--new-record
data Foo = Foo i :: Int, s :: String deriving (Show)

--record
data LotsaInts = LI {a,b,c,i,j,k :: Int}
--new-record
data LostaInts = LI a,b,c,i,j,k :: Int

後者の場合、:: Intは正確に何に適用されますか?データ宣言全体?

(現在)レコード構文を使用した宣言は、構築および更新構文に似ています。これらの場合、レイアウトベースの構文は明確ではありません。これらの余分な=記号をどのように解析しますか?

let f1 = Foo {s = "foo1", i = 1}
let f2 = f1 {s = "foo2"}

let f1 = Foo s = "foo1", i = "foo2"
let f2 = f1 s = "foo2"

関数アプリケーションとは対照的に、f1 sがレコード更新であることをどのようにして知っていますか?

名前空間

クラスで定義されたidとPreludeのidを混在させたい場合はどうでしょうか。使用しているものをどのように指定しますか?修飾されたインポートまたはhidingキーワード、あるいはその両方よりも優れた方法を考えられますか?

import Prelude hiding (id)

data Foo = Foo {a,b,c,i,j,k :: Int, s :: String}
               deriving (Show)

id = i

ghci> :l data.hs
ghci> let foo = Foo 1 2 3 4 5 6 "foo"
ghci> id foo
4
ghci> Prelude.id f1
Foo {a = 1, b = 2, c = 3, i = 4, j = 5, k = 6, s = "foo"}

これらは素晴らしい答えではありませんが、私が得た最高のものです。私は個人的には、レコードの構文がthatであるとは思いません。名前空間/モジュールに関する改善の余地はあると思いますが、どうすれば改善できるかわかりません。

5
Dan Burton