一般任务是深入研究(非常复杂的)记录列表,报告它们包含的内容。这意味着在列表上发明大量过滤器和地图并进行报告。
简化问题看起来像这样:
{-# 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)]
为了尝试删除包装器类型的丑陋,我认为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包装类型?
我缺少什么(更一般地说) - 是否有更简单的方法来功能性地询问数据?
答案 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
或其变体作为参数传递。