如何根据不相关的类型选择常量类型的值?

How to chose a value of a constant type based on an unrelated type?

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

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 形式。这种形式可以 return 表示的不同类型 T。这些不同的类型使用表单中不同的字段子集。 g 中具有 putStrLnf 的行表示表单中字段的定义。 f 表示根据表单是否 returning 依赖于它的类型来决定是否验证字段。

例如,表单可能 return 类型 DocSectionADocSectionB。一个字段可能是 Text 类型,我们想表达一个特定的字段应该只在表单 returning a DocSectionA 时才被验证,而另一个字段只应该在 DocSectionA 时被验证表格是 returning a DocSectionB.

希望对您有所帮助。

下面是我们现在如何处理不明确的类型和类型应用程序。模棱两可的类型允许您拥有 class 不提及 class 参数的成员(否则您可以使用代理)。

此处 c 如果 a ~ T 则为 0,或 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 类型(虽然你为什么会这样),你可以使用重叠实例(GHC manual 是关于它们如何工作的最佳参考):

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

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

main = print (c @String)

我不明白你的 T class 是干什么用的。它似乎并没有真正以 interesting/relevant 的方式使用。但是您的 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 的新花式版本:虽然我们没有在上面使用它,但在 f"matched" 分支中,不仅我们而且类型检查器都知道a ~ b.