3

次の場合にジェネリック型を返す関数を haskell で作成することは可能ですか。

  • この関数の本体では、結果として 2 つの別々のタイプを出力しますA(Bいくつかの計算に基づく)
  • タイプAB共通点がありますtype class C

サンプルコードを見てみましょう。型チェッカーは、このコードが正しいことを確認できる必要があります。関数はortest型のインスタンスを出力するため、結果に対して実行できます。ABf

data A = A 
data B = B

class C a where
    f :: a -> Int

instance C A where
    f x = 2

instance C B where
    f x = 3

-- This function fails to compile:
-- I want something like:
-- test :: C a => Int -> a
test x = if x < 1  
        then A
        else B

main = do
    print $ f $ test 0
    print $ f $ test 1

これがアンチパターンか何かのように見えることはわかっていますが、私は Haskell の機能をテストするのが大好きなので、特に型システムに関しては、その答えを知りたいと思っています。

4

1 に答える 1

3

ここでの問題は、Haskell の型変数が普遍的に量化されていることです。

これは、次のように読み取れることを意味します。

forall a. C a => Int -> a

これは、呼び出し元が の具体的な型を選択することを意味しaます。呼び出し先ではありません。

これを修正するには、「存在する」変数と呼ばれるものが必要です。全称量化変数は「forall」と読み、存在量化変数は「there exists」と読みます。

これを行う方法の簡単なスケッチ

{-# LANGUAGE ExistentialQuantification, GADTs #-}

-- Use existentials 
data Box = forall a. C a => Box a

-- Use GADTs
data Box' where
  Box' :: C a => a -> Box'

instance C Box where
    f (Box a) = f a

test :: Int -> Box
test x = if x < 1  
    then Box A
    else Box B

これの本質は、具体的なCインスタンスをこのデータ型の背後に隠しBox、普遍的に量化された型変数の代わりにそれを渡すことです。

Boxまたは、単純な継続を使用して、Rank2Typesの型を回避できます。

test :: Int -> (forall a. C a => a -> r) -> r
test x c = if x < 1 then c A else c B
main = do
  test 1 (print . f)
  test 0 (print . f)

存在変数の面白い癖、GHC でこのコードを試してください

fun = foo
  where (Box foo) = test 1
于 2013-07-28T18:40:20.027 に答える