Использование Proxy в TypeClass - PullRequest
0 голосов
/ 29 июня 2018

Я пытаюсь сделать что-то похожее на этот вопрос .

Я бы хотел определить класс типов

class Wrapper f where
    wrap :: a -> f a
    unwrap :: f a -> a
    name :: Proxy (f a) -> String

и затем определите

instance (IsString a, FromJSON a, Wrapper f) => FromJSON (f a) where
    parseJSON (String s) = wrap <$> pure (fromString $ unpack s)
    parseJSON invalid    = typeMismatch (name (Proxy :: Proxy (f a))) invalid

Но я получаю сообщение о том, что

Could not deduce (Wrapper f0) arising from a use of ‘name’                                               
      from the context: (IsString a, FromJSON a, Wrapper f)                                                    
        bound by the instance declaration at src/Model/Wrapper.hs:29:10-62                                     
      The type variable ‘f0’ is ambiguous

Мне не совсем понятно, почему это не работает и можно ли это как-то исправить

1 Ответ

0 голосов
/ 29 июня 2018

Сначала пара замечаний:

  1. Не определяйте такой экземпляр . Этот экземпляр будет соответствовать что угодно формы f a, независимо от того, действительно ли f находится в классе Wrapper. В частности, это также может конфликтовать со стандартными экземплярами, такими как FromJSON (Vector a), хотя Vector не может быть (хорошо себя ведущими) экземпляром Wrapper. Причина этого заключается в том, что система классов типов Haskell основана на предположении открытого мира : компилятор никогда не может предположить, что тип не принадлежит какому-либо классу, потому что кто-либо может, по крайней мере с технической точки зрения, добавить экземпляр позже.
  2. Я бы не советовал использовать Proxy в новом коде. Я всегда считал Proxy уродливым хаком, чуть менее уродливым, чем undefined :: T аргументы, которые обычно использовались для этого в старом коде на Haskell. В новом GHC проблема была исправлена ​​правильно -XAllowAmbiguousTypes с -XTypeApplications; они позволяют просто сделать подпись

    {-# LANGUAGE AllowAmbiguousTypes #-}
    class Wrapper f where
      ...
      name :: String
    

    и затем вместо name (Proxy :: Proxy (f a)) только запись name @f.

Теперь к актуальной проблеме: ваш код не работает, потому что переменные типа в стандартном Haskell всегда принадлежат только к сигнатуре / контексту одного типа, но не могут использоваться в коде, который его определяет. Итак, переменные типа не используют те же области имен , что и переменные-значения, поэтому, когда вы упоминаете Proxy (f a), компилятор «устраняет неоднозначность» переменных типа с f0 и a0. Это немного глупый недостаток Haskell98 и устраняется расширением -XScopedTypeVariables (вместе с ключевым словом aka forall). Следующее скомпилирует само по себе:

{-# LANGUAGE ScopedTypeVariables, UnicodeSyntax #-}
instance ∀ f a .  (IsString a, FromJSON a, Wrapper f) => FromJSON (f a) where
    parseJSON (String s) = wrap <$> pure (fromString $ unpack s)
    parseJSON invalid    = typeMismatch (name (Proxy :: Proxy (f a))) invalid

Как я уже сказал, такой экземпляр не должен быть определен. Я думаю, что вы на самом деле хотите что-то вроде

{-# LANGUAGE DataKinds, KindSignatures, TypeApplications #-}

import GHC.TypeLits (Symbol, KnownSymbol, symbolVal)

data Wrapper (n :: String) (a :: *)
   = Wrapper a
   | TypeMismatch String

instance ∀ a s . (IsString a, FromJSON a, KnownSymbol s)
                     => FromJSON (Wrapper s a) where
  parseJSON (String s) = Wrapper <$> pure (fromString $ unpack s)
  parseJSON invalid    = TypeMismatch $ symbolVal @s Proxy

Занятия не требуются.

...