2009-06-13 12 views
56

私はnewtypeがハスケルでdataと頻繁に比較されていることを知っていますが、私は技術的な問題よりもデザインの観点からこの比較を提起しています。型安全性に関するハスケル型と新型

imperitive/OO言語では、プリミティブ型を多用すると、プログラムの型安全性が低下し、誤って同じ型付き値の互換性が導入されます。 。例えば、多くのものはStringでもかまいませんが、コンパイラが静的に、名前であることを意味し、住所の中にある都市を意味するものを知ることができればいいでしょう。

それでは、Haskellのプログラマは、他のプリミティブな値に型の区別を与えるために、どれくらいの頻度でnewtypeを使用しますか? typeを使用すると、エイリアスが導入され、プログラムの読みやすさがより明確になりますが、誤って値の入れ替えが行われることはありません。私がhaskellを学ぶとき、私はタイプシステムが私が出会ったものほど強力であることに気づきます。したがって、私はこれが自然で一般的な行為だと思っていますが、私はこの点でnewtypeの使用に関する多くの議論を見ていません。

もちろん、多くのプログラマが別のことをしますが、これはすべてhaskellでよく見られますか?

+0

Hrm ...私は受け入れられるように複数の回答をマークすることはできません。私は何とかこの問題に関する異なる意見の合理的な表現を受け入れることを望んでいた... – StevenC

答えて

52

主な用途は、次のとおり

  1. タイプの別のインスタンスを定義します。
  2. ドキュメント。
  3. データ/形式正当性保証。

私はnewtypesを広範囲に使用しているアプリケーションに取り組んでいます。 Haskellのnewtypesは純粋にコンパイル時のコンセプトです。例えば。以下のアンラップでは、unFilename (Filename "x")は "x"と同じコードにコンパイルされます。ランタイムヒットは全くゼロです。 dataタイプがあります。これは上記の目標を達成するのに非常に良い方法です。

-- | A file name (not a file path). 
newtype Filename = Filename { unFilename :: String } 
    deriving (Show,Eq) 

私は誤ってこれをファイルパスとして扱いたくありません。これはファイルパスではありません。これは、データベースのどこかの概念ファイルの名前です。

アルゴリズムが適切なものを参照することは非常に重要です。これはnewtypesが役に立ちます。セキュリティのためにも非常に重要です。たとえば、Webアプリケーションへのファイルのアップロードを考えます。私は、これらのタイプがあります。今から私は一意のファイル名を生成することを

-- | Sanitize a filename for saving to upload directory. 
sanitizeFilename :: String   --^Arbitrary filename. 
       -> SanitizedFilename --^Sanitized filename. 
sanitizeFilename = SanitizedFilename . filter ok where 
    ok c = isDigit c || isLetter c || elem c "-_." 

-- | Generate a unique filename. 
uniqueFilename :: SanitizedFilename --^Sanitized filename. 
       -> IO UniqueFilename --^Unique filename. 

-- | A sanitized (safe) filename. 
newtype SanitizedFilename = 
    SanitizedFilename { unSafe :: String } deriving Show 

-- | Unique, sanitized filename. 
newtype UniqueFilename = 
    UniqueFilename { unUnique :: SanitizedFilename } deriving Show 

-- | An uploaded file. 
data File = File { 
    file_name  :: String   --^Uploaded file. 
    ,file_location :: UniqueFilename --^Saved location. 
    ,file_type  :: String   --^File type. 
    } deriving (Show) 

は私がアップロードされたファイルからファイル名をきれいにこの機能があるとし

任意のファイル名から一意のファイル名を生成することは危険です。最初にサニタイズする必要があります。同様に、固有のファイル名は拡張子によって常に安全です。私は今すぐディスクにファイルを保存し、私がしたい場合は、そのファイル名をデータベースに入れることができます。

しかし、ラップしたりラップしたりしなければならない場合もあります。長期的には、私は価値の不一致を避けるためにそれを価値があると考えています。 ViewPatternsは多少役立つ:

-- | Get the form fields for a form. 
formFields :: ConferenceId -> Controller [Field] 
formFields (unConferenceId -> cid) = getFields where 
    ... code using cid .. 

たぶん、あなたは、関数でそれをアンラップが問題であると言うだろう - あなたは間違って関数にcidを渡すと何?問題ではなく、会議IDを使用するすべての機能でConferenceIdタイプが使用されます。コンパイル時に強制される関数から関数レベルの契約システムの一種です。かなりいい。だから、私はできる限り頻繁に使用します。特に、大きなシステムではそうです。

+0

これは信じられないほどクールなものChrisです。私はちょうど最初の練習のセットからReal World Haskellの第8練習2へのタイプクラス解決にこれを使用しました。大文字小文字を区別しないグロブマッチングを選択する方法を提供するよう求められます。ありがとうございました:) –

+0

最後の例のViewPatternは '(ConferenceID cid)'とどう違っていますか? – Dan

+2

私の場合は、任意の古い整数から任意の値を作成したくないため、コンストラクタをエクスポートしません。これはデータベースからのみ取得する必要があります。私は1つを安全にアンラップし、その整数を使うことができます。 –

10

タイプの区別にはnewtypeを使用することがかなり一般的だと思います。多くの場合、異なるタイプのクラスインスタンスを提供したり、実装を隠したりするだけでなく、偶発的な変換から保護するだけの理由もあります。

19

これは主に状況の問題だと思います。

パス名を考慮してください。便宜上、すべての文字列操作とリスト操作にアクセスする必要があるため、標準プレリュードには "type FilePath = String"があります。 "newtype FilePath = FilePath String"がある場合は、filePathLength、filePathMapなどが必要です。そうしないと、変換関数を永久に使用することになります。

一方、SQLクエリについて考えてみましょう。 SQLインジェクションは、共通のセキュリティホールなので、

newtype Query = Query String 

のようなものを持ってして、引用符をエスケープすることで、クエリ(またはクエリフラグメント)に文字列を変換します余分な機能を追加したり、空白を埋めることは理にかなっています同様の方法でテンプレートに追加します。そうすれば、誤って引用符エスケープ機能を使わずにユーザーパラメータをクエリに変換することはできません。

+0

ファイルパスの例に応じて、質問はあなたがやっているデザインの文脈の中で、どこにコントロールがないのですか。前の状況では、あなたのモジュールの消費者/関数/何でも、プリミティブを得るためのコードは見えません。後者の状況では、呼び出しの直前にプリミティブを取り戻すことは最悪です。 これはまさに私が尋ねた理由です。つまり、設計者がどのような設計選択肢について考えているのかを理解することです。確かに、私は利便性よりも安全に頼る人です。 – StevenC

+0

私が言ったように、私はdiffの感覚を得たかったので。あなたの答えはまだ価値があります。私はまだここでやっていない。 :) – StevenC

+3

私は、あなた自身のデザインプラクティスを検討していることを理解しています。実際の例をいくつか挙げたいと思います。 "newtype FilePath"と言うのはプログラマ時間です。変換関数は型チェッカーを幸せに保ち、実装を持たないためです。主なポイントは、あなたのnewtypeを繰り返し繰り返し出している場合、本当の余分な安全性はなく、難しい関数呼び出しのほんの一部です。 ライブラリを設計するときは、アプリケーションプログラマーの視点について考える必要があります。 –

14

X = Yの宣言では、typeはドキュメントです。 newtypeは型チェックです。これはnewtypedataを比較した理由です。

私はかなり頻繁にnewtypeを使っています。別のタイプと同じ方法で保存されているものが何か他のものと混同されないようにするためです。そのようにして、ちょっとだけ効率的な宣言として動作します。どちらか一方を選択する特別な理由はありません。 GHCのGeneralizedNewtypeDeriving拡張では、Numのようなクラスを自動的に派生させることができ、Intやその下にあるものと同じように、あなたの気温や円を加算したり引いたりすることができます。しかし、これに少し注意が必要です。典型的には、ある温度を別の温度で掛けることはありません!これらのものが使用される頻度の考え方については

は、私が今働いている1つの合理的に大規模なプロジェクトでは、私はdata約122用途、newtypeの39件の用途、およびtypeの96件の用途を有しています。

しかしtypeのもの96件の用途32が実際にあるので比率は、限り「単純な」タイプが関係しているように、それが示すよりも少し近いは、

type PlotDataGen t = PlotSeries t -> [String] 

ように、関数型のエイリアス最初に、それは実際には単純なX = Yエイリアスだけでなく、実際には関数型です。PlotDataGenは、PlotDataGen (Int,Double)などの新しいタイプを作成する別のタイプに適用するタイプコンストラクタです。 。このようなことをやり始めると、typeは単なるドキュメントではなく、実際にはデータレベルではなくタイプレベルで機能します。

newtypeがあります。再帰型定義が必要な場所など、typeは使用できませんが、これはかなり稀です。したがって、少なくともこの特定のプロジェクトでは、私の「プリミティブ」型定義の約40%がnewtypeであり、60%がtypeです。 newtypeの定義のいくつかは型であったが、あなたが言及した正確な理由で確実に変換された。

要するに、これは頻繁に使用されるイディオムです。 newtypesため

関連する問題