如何基于不相关类型选择常量类型的值?

时间:2018-10-19 16:49:17

标签: haskell ghc

好的,所以在进行更改之前,我得到了以下简化的工作示例:

data D = D
data C = C

class T a where
  t :: a

instance T D where
  t = D

instance T C where
  t = C

g :: T a => IO a
g = do
  return t

main = (g :: IO D) >> return ()

所以问题是,在g内,我希望根据a选择不相关的a类型的值。换句话说,我想表达的是,如果aC,那么将选择尚未提及的类型e的某个值,如果不是,那么将选择另一个类型e的值将被选择。它基本上以任意类型相等为条件,例如伪代码if a ~ Bool then "foo" else "bar"。我这样尝试过(在此示例中,将String用于类型e

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE AllowAmbiguousTypes #-}
import Data.Proxy

class F sub1 sub2 where
  f :: Proxy (sub1, sub2) -> String

instance {-# OVERLAPPABLE #-} F a b where
  f _ = "did not match types"

instance {-# OVERLAPPING #-} F a a where
  f _ = "matched types"

data D = D
data C = C

class T a where
  t :: a

instance T D where
  t = D

instance T C where
  t = C

g :: forall a b. (T a, F b a) => IO a
g = do
  putStrLn $ f (Proxy :: Proxy (D, a))
  putStrLn $ f (Proxy :: Proxy (C, a))
  return t

main = (g :: IO D) >> return ()

我遇到以下错误:

y.hs:30:14: error:
    • Overlapping instances for F D a arising from a use of ‘f’
      Matching instances:
        instance [overlappable] F a b -- Defined at y.hs:10:31
        instance [overlapping] F a a -- Defined at y.hs:13:30
      (The choice depends on the instantiation of ‘a’
       To pick the first instance above, use IncoherentInstances
       when compiling the other instance declarations)
    • In the second argument of ‘($)’, namely
        ‘f (Proxy :: Proxy (D, a))’
      In a stmt of a 'do' block: putStrLn $ f (Proxy :: Proxy (D, a))
      In the expression:
        do putStrLn $ f (Proxy :: Proxy (D, a))
           putStrLn $ f (Proxy :: Proxy (C, a))
           return t
   |
30 |   putStrLn $ f (Proxy :: Proxy (D, a))
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^

y.hs:31:14: error:
    • Overlapping instances for F C a arising from a use of ‘f’
      Matching instances:
        instance [overlappable] F a b -- Defined at y.hs:10:31
        instance [overlapping] F a a -- Defined at y.hs:13:30
      (The choice depends on the instantiation of ‘a’
       To pick the first instance above, use IncoherentInstances
       when compiling the other instance declarations)
    • In the second argument of ‘($)’, namely
        ‘f (Proxy :: Proxy (C, a))’
      In a stmt of a 'do' block: putStrLn $ f (Proxy :: Proxy (C, a))
      In the expression:
        do putStrLn $ f (Proxy :: Proxy (D, a))
           putStrLn $ f (Proxy :: Proxy (C, a))
           return t
   |
31 |   putStrLn $ f (Proxy :: Proxy (C, a))
   |              ^^^^^^^^^^^^^^^^^^^^^^^^^

y.hs:34:9: error:
    • Overlapping instances for F b0 D arising from a use of ‘g’
      Matching instances:
        instance [overlappable] F a b -- Defined at y.hs:10:31
        instance [overlapping] F a a -- Defined at y.hs:13:30
      (The choice depends on the instantiation of ‘b0’
       To pick the first instance above, use IncoherentInstances
       when compiling the other instance declarations)
    • In the first argument of ‘(>>)’, namely ‘(g :: IO D)’
      In the expression: (g :: IO D) >> return ()
      In an equation for ‘main’: main = (g :: IO D) >> return ()
   |
34 | main = (g :: IO D) >> return ()
   |         ^

错误提示IncoherentInstances,但似乎并没有选择正确的实例。我还没有想出什么新尝试。

编辑:仅是为了查看会发生什么,我激活了IncoherentInstances,但它会导致相同的错误。

编辑2:我将解释该示例如何与我的实际情况结合起来。 g代表HTML表单。此表单可以返回由T表示的不同类型。这些不同的类型使用表单中字段的不同子集。 g中具有putStrLnf的行代表表单中字段的定义。 f表示根据表单是否返回依赖于该字段的类型来决定是否验证该字段。

例如,表单可能返回类型DocSectionADocSectionB。一个字段的类型可能为Text,我们要表达一个特定的字段应该仅在表单返回DocSectionA时才被验证,而另一个字段应该仅在表单返回{ {1}}。

我希望这会有所帮助。

2 个答案:

答案 0 :(得分:4)

我不明白您的T课程的用途。它似乎并未以有趣/相关的方式实际使用。但是您的f可以在TypeRep上使用相等性检查来实现。例如:

{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeApplications #-}

import Data.Type.Equality
import Type.Reflection

data C = C
data D = D

f :: forall a b. (Typeable a, Typeable b) => String
f = case testEquality (typeRep @a) (typeRep @b) of
    Just Refl -> "matched"
    _ -> "didn't match"

g :: forall a. Typeable a => IO ()
g = do
    putStrLn (f @C @a)
    putStrLn (f @D @a)

main = g @D

您当然可以通过通常的方式使用代理来避免ScopedTypeVariables和AllowAmbiguousTypes(如果这是您的首选方式)。我使用了Typeable的新奇花样版本:尽管我们上面没有使用它,但在"matched"的{​​{1}}分支中,不仅我们,而且类型检查器都知道f

答案 1 :(得分:1)

如今,这就是我们使用歧义类型和类型应用程序的方式。歧义类型允许您拥有不提及类参数的类成员(否则您可以使用代理)。

如果c,这里0a ~ T,如果1,则a ~ U

{-# LANGUAGE AllowAmbiguousTypes, TypeApplications #-}

data T
data U

class C a where
  c :: Int

instance C T where
  c = 0

instance C U where
  c = 1

main :: IO ()
main = print (c @T) >> print (c @U)

如果在a不是T的情况下真的要匹配 any 类型(为什么会这样),则可以使用重叠的实例(和{ {3}}是有关它们如何工作的最佳参考):

{-# LANGUAGE FlexibleInstances #-} -- in addition to the above

instance {-# OVERLAPPABLE #-} C a where
  c = 0

main = print (c @String)