【问题标题】:Generic programming in Haskell with SYB and ad-hoc polymorphism使用 SYB 和 ad-hoc 多态性在 Haskell 中进行泛型编程
【发布时间】:2013-12-27 18:51:10
【问题描述】:

我有一个与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 实现之间共享的通用模式。为了避免这种样板,我想我可以使用Scrap your boilerplate 中的Data.Generics 并实现一个折叠元组,比如

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,所以我想使用那个模块。只有当它仅使用该模块时,我才会接受答案。谢谢。

【问题讨论】:

    标签: haskell scrap-your-boilerplate


    【解决方案1】:

    我更熟悉 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
    

    【讨论】:

    • 我喜欢您使用GHC.Generics 的实现,但我不能接受它,因为它使用了另一个模块。我想使用Data.Generics
    • @mariop 没问题,我只是想投入我的 2 美分。希望其他更熟悉 SYB 的人能提供完整的答案。
    【解决方案2】:

    您可以使用syb-with-class。 它早于-XConstraintKinds,因此您需要编写Sat 的实例 类并派生该库派生的 Data 类。这是一个例子, 这非常接近 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 功能 使用正确的类型。

    【讨论】:

    • 谢谢@aavogt,你的提议很有趣。我不接受它只是因为它需要另一个模块。为此,我想避免在 SYM 之上使用另一个模块。如果您有仅使用 Data.Generics 的解决方案,那么我很乐意接受您的回答。
    • @mariop,我认为你可以用 SYB 做的最好的事情就是列出你知道在某一时刻有 Show 实例的类型。例如 mkQ "" (show :: Integer -&gt; String) `extQ` (show :: Int -&gt; String) `extQ` (show :: Double -&gt; String) :: GenericQ String 可以提供给 gmapQl 以显示这三种类型。这是非常有限的,因为您可能会忘记许多可能的类型([[[[(Int,Double,String)]]]] 有一个 Show 实例)或无法列出(因为它们尚未定义)。但也许extQ 对你来说已经足够了。
    【解决方案3】:

    你说得对,因为show 的多态性,showTuple 不起作用。问题是mkQ 想挑出一种特定的类型:

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

    类型签名中的 b 必须是每次使用 mkQ 的特定类型 - 如果没有类型签名,默认规则可能会选择一些东西(不确定是什么!),而使用类型签名它会选择Int.

    您的showTupleInt 确实适用于任何大小 的元组,但当然不适用于任何类型 的元组。

    在 GHCi 中定义 gshows 的问题在于,它确实需要类型签名才能进行类型检查,因为在其自己的定义中递归使用了与原始调用不同的类型 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 仅对一种类型起作用而造成了麻烦。

    您也不能直接将showgmapQ 一起使用,因为约束不同-gmapQ 想要的东西可以在Data 的任何实例上工作,而showShow 的约束. gshows 从未真正通用地使用 Show 类型类,尽管它确实使用了专用于 Stringshows

    在这种情况下很难证明是否定的,但我很确定你不能编写像 showTuple 这样的东西,它会使用 syb 多态地使用 Show 类,因为它只是没有任何东西可以“识别”具有特定实例的类型。这就是syb-with-class 存在的原因。

    另外,如果你真的想要一些只在类型结构的单一级别上工作的东西,即显示任何大小的元组但对元组的元素使用其他东西,那么syb 可以说是错误的解决方案,因为它设计用于递归操作并在数据结构的任何级别查找事物。我的观点是GHC.Generics 解决方案是实现showTuple 的最佳解决方案。

    【讨论】:

    • 你的解释很清楚,谢谢!好的,我不明白为什么它不起作用。我从您的回答中不明白的是,即使通过完全更改代码(删除gmapQ 可能吗?)但使用Data.Generics,是否有可能实现showTuple。如果您可以提供或解释为什么不可能,我会接受答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-02-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-28
    • 1970-01-01
    相关资源
    最近更新 更多