如何使用GADT包装类型签名

时间:2013-11-28 23:46:41

标签: haskell

背景

一般任务是深入研究(非常复杂的)记录列表,报告它们包含的内容。这意味着在列表上发明大量过滤器和地图并进行报告。

简化问题看起来像这样:

{-# LANGUAGE OverloadedStrings, DataKinds, ExistentialQuantification, GADTs #-}

import qualified Data.Map as Map
import Control.Monad (void)

data Rec = Rec
  { _rInt   :: Int
  , _rChar  :: Char
  } deriving (Show,Eq,Ord)

tRec = take 10 $ zipWith Rec [1..] ['a'..]

count :: (Ord a) => [b] -> (b -> a) -> (b -> Bool) -> Map.Map a Int
count list key pred' =
    let fn a = Map.insertWith (+) (key a) 1 in
    foldr fn Map.empty (filter pred' list)

report :: (Ord a, Show a) => [b] -> String -> (b -> a) -> (b -> Bool) -> IO ()
report list str key pred' = do
  let c = count list key pred'
  (putStrLn . (str ++) . show) c

例如:

λ: report tRec "count of characters with odd ints: " _rChar (odd . _rInt) 
count of characters with odd ints: fromList [('a',1),('c',1),('e',1),('g',1),('i',1)]

标准数据类型包装器

各种报告可以很好地捆绑(并准备好进一步重构)使用更高级的类型包装器,如下所示:

data Wrap = WrapInt Int | WrapChar Char deriving (Show, Eq, Ord)

demoWrap = void $ sequence $
  zipWith3
    (report tRec)
    ["count of all ints: ","count of characters with odd ints: "]
    [WrapInt . _rInt, WrapChar . _rChar]
    [const True, odd . _rInt]

给出了:

λ: demoWrap
count of all ints: fromList [(WrapInt 1,1),(WrapInt 2,1),(WrapInt 3,1),(WrapInt     4,1),(WrapInt 5,1),(WrapInt 6,1),(WrapInt 7,1),(WrapInt 8,1),(WrapInt 9,1),(WrapInt 10,1)]
count of characters with odd ints: fromList [(WrapChar 'a',1),(WrapChar 'c',1),(    WrapChar 'e',1),(WrapChar 'g',1),(WrapChar 'i',1)]

GADT解决方案

为了尝试删除包装器类型的丑陋,我认为ADT / GADT解决方案可能会有所帮助。

这是我的尝试:

-- GADTs attempt

data Useable where
  MkUseable :: (Show a, Eq a, Ord a) => a -> Useable

wrap :: (Show a, Eq a, Ord a) => a -> Useable
wrap = MkUseable

instance Show Useable where
  showsPrec p (MkUseable a) = showsPrec p a


-- this doesn't work
instance Eq Useable
--  where
--  (MkUseable a) == (MkUseable b) = a == b

instance Ord Useable
--  where
--  compare (MkUseable a) (MkUseable b) = compare a b

demoGADT = void $ sequence $
  zipWith3
    (report tRec)
    ["all ints:","odd chars:"]
    [wrap . _rInt, wrap . _rChar]
    [const True, odd . _rInt]

可用的Eq和Ord实例的编译器(非常正确)barfs具有可能不同的类型。但目的并不是要将Useable与不同的类型进行比较 - 更简单地包装任何(Show a,Ord a)类型,以便我可以将它们放在列表中。

所以有两个问题:

如何根据上述标准包装解决方案的精神使用GADT包装类型?

我缺少什么(更一般地说) - 是否有更简单的方法来功能性地询问数据?

3 个答案:

答案 0 :(得分:2)

您创建了一个存在类型,但这不是您想要的。

非存在性("透明")包装器如下所示:

data Useable a where
  MkUseable :: (Show a, Eq a, Ord a) => a -> Useable a

注意Useable类型如何通过其类型参数传递有关其内部信息的信息。

顺便说一句,您也可以使用普通(非GADT)语法定义相同的包装器:

data Useable a = (Show a, Eq a, Ord a) => Useable a

(仍需要像-XGADTs这样的语言扩展名

答案 1 :(得分:2)

这将需要更改原始函数,但使用GADT解决此问题的一种方法是包装整个键控函数而不是返回值。即。

data Key b where
    Key :: (Ord a, Show a) => (b -> a) -> Key b

count :: [b] -> Key b -> (b -> Bool) -> Map.Map a Int
count list (Key key) pred' =
    let fn a = Map.insertWith (+) (key a) 1 in
    foldr fn Map.empty (filter pred' list)

report :: [b] -> String -> Key b -> (b -> Bool) -> IO ()
report list str key pred' = do
  let c = count list key pred'
  (putStrLn . (str ++) . show) c

但是,现在问题是我们承诺从Map.Map a Int返回count,但我们不知道a可能是什么,因为它隐藏在Key中存在主义但是因为我们并不关心(至少在这个例子的范围内),我们可以将结果Map包装在隐藏密钥类型的另一个存在中。

{-# LANGUAGE StandaloneDeriving #-}

data CountMap where
    CountMap :: (Ord a, Show a) => Map.Map a Int -> CountMap

deriving instance Show CountMap

并相应地更改count

count :: [b] -> Key b -> (b -> Bool) -> CountMap
count list (Key key) pred' =
    let fn a = Map.insertWith (+) (key a) 1 in
    CountMap $ foldr fn Map.empty (filter pred' list)

现在我们可以做到

demoWrap = void $ sequence $
  zipWith3
    (report tRec)
    ["count of all ints: ","count of characters with odd ints: "]
    [Key _rInt, Key _rChar]
    [const True, odd . _rInt]

答案 2 :(得分:1)

当然可以完全动态并使用

import Data.Typeable

data Useable where
  MkUseable :: (Show a, Eq a, Ord a, Typeable a) => a -> Useable

instance Eq Useable where
  (MkUseable a) == (MkUseable b)
    | Just a' <- cast a  = a' == b
    | otherwise          = False

Ord也可以实施。但正如你可能马上说的那样,这不是很好。

我认为你不应该为demoGADT提供这样的类型。有了这样的多态Map类型,你就不会(没有Typeable)能够实际使用这些值,无论如何;在这里,你确实通过进入IO()完全抛弃了这些类型。所以你不妨做什么

demoNoGADT = void . sequence $ zipWith3 (\s f p -> f p s)
    ["all ints:", "odd chars:"]
    [r _rInt    , r _rChar    ]
    [const True ,odd . _rInt  ]
  where r :: (Ord a, Show a) => (Rec -> a) -> (Rec -> Bool) -> String -> IO ()
        r key pred' descript = report descript tRec key pred'

不需要GADT / exitentials。为了使这更加通用,您可能需要{-# LANGUAGE RankNTypes #-},以允许不同的报告功能:

demoRankNParam :: 
   ( forall a b . (Ord a, Show a) => [b] -> String -> (b -> a) -> (b -> Bool) -> IO () )
        -> IO ()
demoRankNParam report' = void . sequence $ zipWith3 (\s f p -> f p s)
    ["all ints:", "odd chars:"]
    [r _rInt    , r _rChar    ]
    [const True ,odd . _rInt  ]
  where r :: (Ord a, Show a) => (Rec -> a) -> (Rec -> Bool) -> String -> IO ()
        r key pred' descript = report' descript tRec key pred'

现在,您可以将report或其变体作为参数传递。