Как выбрать значение постоянного типа на основе несвязанного типа? - PullRequest
0 голосов
/ 19 октября 2018

Хорошо, поэтому перед изменением, которое я хочу внести, я получил следующий упрощенный рабочий пример:

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.Другими словами, я хочу выразить, что если a равно C, то будет выбрано какое-то значение еще не упомянутого типа 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, которые имеют putStrLn и f, представляют определения полей в форме.f представляет решение о том, проверять или нет поле в зависимости от того, возвращает ли форма тип, который зависит от него.

Например, форма может возвращать тип DocSectionA или DocSectionB.Поле может иметь тип Text, и мы хотим выразить, что определенное поле должно проверяться только тогда, когда форма возвращает DocSectionA, а другое поле должно проверяться только тогда, когда форма возвращает DocSectionB.

Надеюсь, это поможет.

Ответы [ 2 ]

0 голосов
/ 19 октября 2018

Я не понимаю, для чего предназначен ваш T класс.Похоже, на самом деле он не используется интересным / актуальным способом.Но ваш f может быть реализован с использованием проверок равенства на TypeRep s.Например:

{-# 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" ветке f не только мы, но и средство проверки типов знает, что a ~ b.

0 голосов
/ 19 октября 2018

Вот как мы это делаем в настоящее время с неоднозначными типами и типами приложений.Неоднозначные типы позволяют иметь членов класса, которые не упоминают параметры класса (в противном случае вы можете использовать прокси).

Здесь c равно 0, если a ~ T, или 1, если * 1007.*:

{-# 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 (почему бы вам это сделать), вы можете использовать перекрывающиеся экземпляры (и руководство GHC является лучшим справочником о том, как они работают):

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

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

main = print (c @String)
...