Общее программирование в Haskell с SYB и специальным полиморфизмом

У меня есть класс, идентичный Show, и я хотел бы создать экземпляр этого класса для каждого типа кортежа. Обычно это делается путем написания отдельных экземпляров для каждого типа кортежа.

instance  (Show a, Show b) => Show (a,b)  where
  showsPrec _ (a,b) s = show_tuple [shows a, shows b] s

instance (Show a, Show b, Show c) => Show (a, b, c) where
  showsPrec _ (a,b,c) s = show_tuple [shows a, shows b, shows c] s

instance (Show a, Show b, Show c, Show d) => Show (a, b, c, d) where
  showsPrec _ (a,b,c,d) s = show_tuple [shows a, shows b, shows c, shows d] s
...

Запись одного экземпляра для каждого типа кортежа приводит к большому количеству шаблонов, и легко увидеть общий шаблон, общий для всех showPrec Реализации. Чтобы избежать такого рода шаблонов, я подумал, что мог бы использовать Data.Generics из Scrap вашего шаблонного шаблона и реализовать складывание над кортежами, например

showTuple = intercalate " " . gmapQ ("" `mkQ` show)

Но showTuple по какой-то причине не работает

> showTuple (1,2)
" "

Я думаю, что проблема в том, что show полиморфен, потому что, если я специализируюсь showTuple тогда это работает

showTupleInt = intercalate " " . gmapQ ("" `mkQ` (show :: Int -> String))
> showTupleInt (1::Int,2::Int)
"1 2"

Я проверил код gshow, который делает что-то похожее на то, что мне нужно, но я не могу понять, как это работает. Если я пытаюсь импортировать его код в GHCI, я получаю сообщение об ошибке:

> let gshows = (\t -> showChar '('
                      . (showString . showConstr . toConstr $ t)
                      . (foldr (.) id . gmapQ ((showChar ' ' .) . gshows) $ t)
                      . showChar ')'
                      ) `extQ` (shows :: String -> ShowS)
<interactive>:262:59:
Could not deduce (a ~ d)
from the context (Data a)
  bound by the inferred type of
           gshows :: Data a => a -> String -> String
  at <interactive>:(259,5)-(264,44)
or from (Data d)
  bound by a type expected by the context:
             Data d => d -> String -> String
  at <interactive>:262:33-65
  `a' is a rigid type variable bound by
      the inferred type of gshows :: Data a => a -> String -> String
      at <interactive>:259:5
  `d' is a rigid type variable bound by
      a type expected by the context: Data d => d -> String -> String
      at <interactive>:262:33
Expected type: d -> String -> String
  Actual type: a -> String -> String
In the second argument of `(.)', namely `gshows'
In the first argument of `gmapQ', namely
  `((showChar ' ' .) . gshows)'
In the second argument of `(.)', namely
  `gmapQ ((showChar ' ' .) . gshows)'

Итак, у меня есть два вопроса:

  1. Что случилось с showTuple и как я могу это исправить, чтобы он работал с кортежами любого размера
  2. как gshow работает, и почему, если я импортирую его код в GHCI, я получаю эту ошибку?

РЕДАКТИРОВАТЬ: я учусь Data.Generics и вообще SYM, поэтому я хотел бы использовать этот модуль. Я приму ответ, только если он использует только этот модуль. Благодарю.

3 ответа

Ты прав что showTuple не работает из-за полиморфизма show, Проблема в том, что mkQ хочет выбрать один конкретный тип:

mkQ :: (Typeable a, Typeable b) => r -> (b -> r) -> a -> r

b в сигнатуре типа должен быть один конкретный тип для каждого использования mkQ - без сигнатуры типа правила по умолчанию, вероятно, выбирают что-то (не уверен, что!), тогда как с сигнатурой типа это выбирает Int,

Ваш showTupleInt работает с кортежами любого размера, но, конечно, не с кортежами любого типа.

Проблема с определением gshows в GHCi действительно требуется подпись типа, чтобы иметь возможность проверять тип из-за рекурсивного использования gshows в своем собственном определении в типе, отличном от исходного вызова. Без сигнатуры типа средство проверки типов хочет определить gshows иметь точно такую ​​же реализацию переменной типа, что и использование gshows - который проявляется как Could not deduce (a ~ d) ошибка типа

Вы можете увидеть это, поместив его в исходный файл с сигнатурой типа и без нее - с сигнатурой она хорошо проверяет тип, без нее вы получите ошибку, аналогичную той, которая была у вас, если вы впервые используете :set -XNoMonomorphismRestriction,

gshows работает вообще из-за типа gmapQ:

gmapQ :: Data a => (forall d. Data d => d -> u) -> a -> [u]

В отличие от mkQэтот параметр сам по себе полиморфен - обратите внимание на вложенные forall,

Хотя твой showTuple также использует gmapQ, слишком поздно - mkQ уже вызвал проблему, заставляя show работать только на одном типе.

Вы также не можете использовать show непосредственно с gmapQ непосредственно, потому что ограничения разные - gmapQ хочет что-то, что будет работать на любом экземпляре Data, в то время как show ограничен Show, gshows никогда не использует Show тип класс вообще, хотя он использует shows специализируется на String,

Трудно доказать отрицание в таком случае, но я вполне уверен, что вы не можете написать что-нибудь подобное showTuple который будет использовать Show класс полиморфно, используя только sybпотому что у него просто нет ничего, что могло бы "распознавать" типы, имеющие конкретный экземпляр. Вот почему syb-with-class существует.

Кроме того, если вы действительно хотите что-то, что просто работает на одном уровне структуры типа, то есть показывает кортеж любого размера, но использует что-то другое для элементов кортежа, то syb Возможно, это неправильное решение, поскольку оно предназначено для рекурсивной работы и поиска объектов на любом уровне структуры данных. Я считаю, что GHC.Generics решение является наилучшим для реализации showTuple,

Я больше знаком с GHC Generics, а не с SYB, поэтому я предлагаю решение, основанное на Generics. Хотя он не дает прямого ответа на ваш вопрос, я надеюсь, что он также может быть полезным.

{-# LANGUAGE TypeOperators, FlexibleContexts, DefaultSignatures #-}
import Data.Sequence
import GHC.Generics

class Strs' f where
    strings' :: f a -> Seq String

instance Strs' U1 where
    strings' U1 = empty

instance Show c => Strs' (K1 i c) where
    strings' (K1 a) = singleton $ show a

instance (Strs' a) => Strs' (M1 i c a) where
    strings' (M1 a) = strings' a

instance (Strs' f, Strs' g) => Strs' (f :*: g) where
    strings' (a :*: b) = strings' a >< strings' b

class Strs a where
    strings :: a -> Seq String
    default strings :: (Generic a, Strs' (Rep a)) => a -> Seq String
    strings = strings' . from

-- Since tuples have Generic instances, they're automatically derived using
-- the above default.
instance Strs () where
instance (Show a, Show b) => Strs (a, b) where
instance (Show a, Show b, Show c) => Strs (a, b, c) where

Вы можете использовать syb-with-class. Это предшествует -XConstraintKindsтак что вам нужно написать экземпляр SatКласс и получить класс данных, который выводит эта библиотека. Вот пример, который довольно близок к примеру showTuple, за исключением того, что я добавил немного {}:

{-# LANGUAGE FlexibleContexts, FlexibleInstances, MultiParamTypeClasses, TemplateHaskell, UndecidableInstances #-}
import Data.Generics.SYB.WithClass.Basics
import Data.Generics.SYB.WithClass.Instances
import Data.Generics.SYB.WithClass.Derive

data A a b c = A a b c deriving Show
data B a = B a deriving Show
data C a = C a deriving Show

derive [''A,''B,''C]

data ShowD a = ShowD { showD :: a -> String -> String }
instance (Show a) => Sat (ShowD a) where
    dict = ShowD shows

gshow x = case gfoldl ctx 
                (\ (s, f) x -> (s . ("{"++) . showD dict x . ("}"++) , f x))
                (\y -> (id ,y))
                x
        of (str,_) -> str ""
    where
        ctx :: Proxy ShowD
        ctx = undefined

x1 = A (B 'b') (C "abc") (B ())

{-
>>> gshow x1
"{B 'b'}{C \"abc\"}{B ()}"

>>> show x1
"A (B 'b') (C \"abc\") (B ())"
-}

Второй аргумент gfoldl получает возможность позвонить shows (B 'b'), shows (C "abc")а также shows (B ()) благодаря showD dict который получает shows функция с правильным типом.

Другие вопросы по тегам