なぜ実存型は関数型プログラミングの悪い習慣と見なされているのですか(または、そうでないのですか)?


43

一貫してコードをリファクタリングして存在型への依存を排除​​するために使用できるテクニックは何ですか?通常、これらは、あなたのタイプの望ましくない構造を不適格にし、特定のタイプに関する最小限の知識で消費を許可するために使用されます(または私の理解もそうです)。

誰もがいくつかの利点を維持しているコードのこれらへの依存を削除するためのシンプルで一貫した方法を考え出しましたか?または、少なくとも、変更に対処するために重要なコードチャーンを必要とせずにそれらを削除できる抽象化をすり抜ける方法はありますか?

存在タイプの詳細については、こちらをご覧ください(「あえて」。)。


8
@RobertHarveyこの質問について初めて考えさせられたブログ投稿、Haskell Antipattern:Existential Typeclassを見つけました。また、Joey Adamsが言及している論文は、セクション3.1の実存を伴ういくつかの問題について説明しています。反対の議論がある場合は、それらを共有してください。
PetrPudlák13年

5
@PetrPudlák:アンチパターンは一般に存在型ではなく、より単純で簡単なもの(そしてHaskellでより良くサポートされているもの)が同じ仕事をする場合の特定の使用法に注意してください。
CAマッキャン

10
特定のブログ投稿の著者が意見を表明した理由を知りたい場合、おそらくあなたが尋ねるべき人物は著者です。
エリックリッパー

6
@Ptolemyそれは意見の問題です。Haskellを何年も使用しているうちに、強力な型システムを持たない関数型言語を使用することを想像することはできません。
ペトルプドラク

4
@Ptolemy:Haskellのマントラは「コンパイルすれば機能する」、Erlangsのマントラは「クラッシュさせる」です。動的タイピングは接着剤としては良いのですが、個人的には接着剤だけを使って物を作ることはしません。
デン

回答:


8

存在型は、関数型プログラミングでは実際には悪い習慣とは見なされていません。あなたをつまずかせているのは、実存性の最も一般的に引用されている用途の1つが実存型クラスアンチパターンであり、多くの人が悪い習慣だと信じていることです。

このパターンは、多くの場合、すべてが同じタイプクラスを実装する異種タイプの要素のリストをどのように作成するかという質問への回答として作成されます。たとえば、Showインスタンスを持つ値のリストが必要な場合があります。

{-# LANGUAGE ExistentialTypes #-}

class Shape s where
   area :: s -> Double

newtype Circle = Circle { radius :: Double }
instance Shape Circle where
   area (Circle r) = pi * r^2

newtype Square = Square { side :: Double }
    area (Square s) = s^2

data AnyShape = forall x. Shape x => AnyShape x
instance Shape AnyShape where
    area (AnyShape x) = area x

example :: [AnyShape]
example = [AnyShape (Circle 1.0), AnyShape (Square 1.0)]

このようなコードの問題は次のとおりです。

  1. で実行できる唯一の便利な操作AnyShapeは、その領域を取得することです。
  2. AnyShapeコンストラクターを使用して、いずれかの形状タイプをそのタイプに取り込む必要がありますAnyShape

結局のところ、このコードでは、この短いコードでは得られないものは実際には得られません。

class Shape s where
   area :: s -> Double

newtype Circle = Circle { radius :: Double }
instance Shape Circle where
   area (Circle r) = pi * r^2

newtype Square = Square { side :: Double }
    area (Square s) = s^2

example :: [Double]
example = [area (Circle 1.0), area (Square 1.0)]

マルチメソッドクラスの場合には、同様の効果は、一般にコードする、代わりに「メソッドのレコード」を使用してのような型クラスを使用することによって、より簡単に達成することができShape、あなたは、そのフィールドの「方法」であるレコードタイプ定義Shapeタイプ、円と正方形をShapes に変換する関数を作成します。


しかし、それは存在型が問題であることを意味しません!たとえば、Rustには、トレイトオブジェクトと呼ばれる機能があります。これは、トレイト(Rustのタイプクラスのバージョン)上の実在型としてよく説明されます。存在する型クラスがHaskellのアンチパターンである場合、それはRustが悪い解決策を選んだことを意味しますか?番号!Haskellの世界での動機は、実際には原則ではなく、構文と利便性にあります。

より数学的な方法はAnyShape、上からの型Double同型であることを指摘することです。それらの間には「ロスレス変換」があります(浮動小数点の精度を保つため)。

forward :: AnyShape -> Double
forward = area

backward :: Double -> AnyShape
backward x = AnyShape (Square (sqrt x))

厳密に言えば、どちらかを選択することでパワーを獲得したり失ったりすることはありません。つまり、選択は使いやすさやパフォーマンスなどの他の要因に基づいている必要があります。


また、実在型には、この異種リストの例以​​外にも他の用途があるので、それらを使用することをお勧めします。たとえば、HaskellのST型は、外部では純粋であるが内部ではメモリ変換操作を使用する関数を作成できるため、コンパイル時の安全性を保証するために、存在型に基づいた手法を使用します。

したがって、一般的な答えは、一般的な答えはないということです。存在タイプの使用はコンテキストでのみ判断できます。また、異なる言語によって提供される機能と構文に応じて回答が異なる場合があります。


それは同型ではありません。
ライアンライク

2

私はHaskellにあまり馴染みがないので、アカデミックではない機能的なC#開発者として質問の一般的な部分に答えようとします。

いくつかの読書をした後、それが判明しました:

  1. Javaワイルドカードは、存在タイプに似ています。

    例によるScalaの実存型とJavaのワイルドカードの違い

  2. ワイルドカードはC#に完全には実装されていません。一般的な差異はサポートされていますが、呼び出しサイトの差異はサポートされていません。

    C#ジェネリック:ワイルドカード

  3. 毎日この機能は必要ないかもしれませんが、そうすると感じるでしょう(たとえば、機能させるために追加の型を導入する必要があります)。

    C#ジェネリック制約のワイルドカード

この情報に基づいて、実在型/ワイルドカードは適切に実装されていれば有用であり、本質的に問題はありませんが、他の言語機能と同様に誤用される可能性があります。

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