这似乎是人为的,但我似乎找不到以下答案:
说我有以下进口商品:
import qualified Data.Map as M
import qualified Data.HashMap.Lazy as HML
现在我有了一些函数(comp
),该函数可以获取一些列表,执行某些操作,创建地图并返回。
我的问题是我如何有两种调用comp
的方式,以便其对insert
和size
的调用(例如)正确映射?
作为一名稻草人,我可以编写此函数的两个副本,一个引用M.insert
和M.size
,而另一个引用HML.insert
和HML.size
...但是如何我应该“将模块作为参数传递”,否则以其他方式表明这一点?
谢谢!
编辑:为简化起见,这些是comp
的确切定义:
mapComp :: KVPairs -> IO ()
mapComp kvpairs = do
let init = M.empty
let m = foldr ins init kvpairs where
ins (k, v) t = M.insert k v t
if M.size m /= length kvpairs
then putStrLn $ "FAIL: " ++ show (M.size m) ++ ", " ++ show (length kvpairs)
else pure ()
hashmapComp :: KVPairs -> IO()
hashmapComp kvpairs = do
let init = HML.empty
let m = foldr ins init kvpairs where
ins (k, v) t = HML.insert k v t
if HML.size m /= length kvpairs
then putStrLn $ "Fail: " ++ show (HML.size m) ++ ", " ++ show (length kvpairs)
else pure ()
编辑(2):事实证明,这比我预期的要有趣得多,这要归功于所有人的回答!
答案 0 :(得分:6)
以下是使用模块签名和混合插件(也称为Backpack)的方法
您必须使用signature定义一个库(它可能是一个内部库):
-- file Mappy.hsig
signature Mappy where
class C k
data Map k v
empty :: Map k v
insert :: C k => k -> v -> Map k v -> Map k v
size :: Map k v -> Int
在同一个库中或另一个库中,编写将签名当作普通模块导入的代码:
module Stuff where
import qualified Mappy as M
type KVPairs k v = [(k,v)]
comp :: M.C k => KVPairs k v -> IO ()
comp kvpairs = do
let init = M.empty
let m = foldr ins init kvpairs where
ins (k, v) t = M.insert k v t
if M.size m /= length kvpairs
then putStrLn $ "FAIL: " ++ show (M.size m) ++ ", " ++ show (length kvpairs)
else pure ()
在另一个库(必须是另一个库)中,编写一个与签名匹配的“实现”模块:
-- file Mappy.hs
{-# language ConstraintKinds #-}
module Mappy (C,insert,empty,size,Map) where
import Data.Map.Lazy
type C = Ord
“签名匹配”仅基于名称和类型执行,实现模块不需要知道签名的存在。
然后,在要使用抽象代码的库或可执行文件中,将具有抽象代码的库和具有实现的库同时拉出:
executable somexe
main-is: Main.hs
build-depends: base ^>=4.11.1.0,
indeflib,
lazyimpl
default-language: Haskell2010
library indeflib
exposed-modules: Stuff
signatures: Mappy
build-depends: base ^>=4.11.1.0
hs-source-dirs: src
default-language: Haskell2010
library lazyimpl
exposed-modules: Mappy
build-depends: base ^>=4.11.1.0,
containers >= 0.5
hs-source-dirs: impl1
default-language: Haskell2010
有时签名的名称和实现模块的名称不匹配,在这种情况下,必须使用Cabal文件的mixins部分。
编辑。创建HashMap
实现有些棘手,因为insert
需要两个约束(Eq
和Hashable
)而不是一个约束。我不得不求助于"class synonym"技巧。这是代码:
{-# language ConstraintKinds, FlexibleInstances, UndecidableInstances #-}
module Mappy (C,insert,HM.empty,HM.size,Map) where
import Data.Hashable
import qualified Data.HashMap.Strict as HM
type C = EqHash
class (Eq q, Hashable q) => EqHash q -- class synonym trick
instance (Eq q, Hashable q) => EqHash q
insert :: EqHash k => k -> v -> Map k v -> Map k v
insert = HM.insert
type Map = HM.HashMap
答案 1 :(得分:5)
最简单的方法是根据实际需要的操作而不是模块进行参数化。所以:
mapComp M.empty M.insert M.size
然后您可以将其称为,例如mapComp HM.empty HM.insert HM.size
或data MapOps m = MapOps
{ empty :: m
, insert :: K -> V -> m -> m
, size :: m -> Int
}
mops = MapOps M.empty M.insert M.size
hmops = MapOps HM.empty HM.insert HM.size
mapComp :: MapOps m -> KVPairs -> IO ()
mapComp ops kvpairs = do
let m = foldr ins (empty ops) kvpairs where
ins (k, v) t = insert ops k v t
if size ops m /= length kvpairs
then putStrLn "Yikes!"
else pure ()
。作为一个小的附带好处,即使调用者喜欢的数据结构没有通过编写小型适配器并将其传递来提供名称和类型正确的模块,也可以使用此功能。
如果愿意,可以将它们合并为一条记录,以简化传递过程:
scale_fill_manual
答案 2 :(得分:1)
恐怕没有解决方法,就无法在Haskell中进行操作。主要问题是power2 <- power_factory(2)
会为comp
和M
的变体对相同的对象使用不同的类型,这在Haskell中是无法直接实现的。
您将需要让HML
使用数据或多态性来选择哪个选项。
作为基本思路,我将创建ADT以涵盖可能的选项,并使用布尔值确定模块:
comp
然后使用data SomeMap k v = M (M.Map k v) | HML (HML.HashMap k v)
f :: Bool -> IO ()
f shouldIUseM = do ...
中的case
表达式检查您的基础映射是foldr
还是M
。但是,我看不到使用这样的膨胀代码有什么好处,最好分别创建HML
和compM
。
另一种方法是创建将包装所有案例的类型类
compHML
然后手动为每个地图编写实例(或使用一些TemplateHaskell魔术,我认为这可能会有所帮助,但是这超出了我的技能)。它也需要一些膨胀代码,但是您将能够在使用的地图类型上参数化class SomeMap m where
empty :: m k v
insert :: k -> v -> m k v -> m k v
size :: m k v -> Int
:
comp
但是老实说,我会这样写这个函数:
comp :: SomeMap m => m -> IO ()
comp thisCouldBeEmptyInitMap = do ...
答案 3 :(得分:1)
我有点怀疑这是一个XY问题,所以这是我如何处理链接到的代码。您具有以下内容:
mapComp :: KVPairs -> IO ()
mapComp kvpairs = do
let init = M.empty
let m = foldr ins init kvpairs where
ins (k, v) t = M.insert k v t
if M.size m /= length kvpairs
then putStrLn $ "FAIL: " ++ show (M.size m) ++ ", " ++ show (length kvpairs)
else pure ()
hashmapComp :: KVPairs -> IO()
hashmapComp kvpairs = do
let init = HML.empty
let m = foldr ins init kvpairs where
ins (k, v) t = HML.insert k v t
if HML.size m /= length kvpairs
then putStrLn $ "Fail: " ++ show (HML.size m) ++ ", " ++ show (length kvpairs)
else pure ()
这有很多重复,通常不好。因此,我们将两个函数之间不同的位分解出来,并通过这些更改的位参数化新功能:
-- didn't try to compile this
comp :: mp k v -> (k -> v -> mp k v -> mp k v) -> (mp k v -> Int) -> KVPairs -> IO()
comp h_empty h_insert h_size kvpairs = do
let init = h_empty
let m = foldr ins init kvpairs where
ins (k, v) t = h_insert k v t
if h_size m /= length kvpairs
then putStrLn $ "Fail: " ++ show (h_size m) ++ ", " ++ show (length kvpairs)
else pure ()
如您所见,这是一个真正的机械过程。然后您致电comp M.empty M.insert M.size
。
如果您希望能够定义comp
,使其可以在尚未想到的地图类型上使用(或由用户指定),那么您可以必须针对抽象接口定义comp
。这是通过类型类完成的,如radrow的答案SomeMap
。
实际上,您已经注意到要使用的两个映射都实现了标准Foldable
和Monoid
,因此您已经可以做部分抽象了。
-- didn't try to compile this
comp :: (Foldable (mp k), Monoid (mp k v))=> (k -> v -> mp k v -> mp k v) -> KVPairs -> IO()
comp h_insert kvpairs = do
let init = mempty -- ...also why not just use `mempty` directly below:
let m = foldr ins init kvpairs where
ins (k, v) t = h_insert k v t
if length m /= length kvpairs
then putStrLn $ "Fail: " ++ show (length m) ++ ", " ++ show (length kvpairs)
else pure ()
正如评论中提到的那样,我认为backpack是(将会是)获取我认为您要的东西的方法,即参数化的 modules 。我对此了解不多,对我来说不清楚它能解决什么用例,即您不想使用上面描述的更传统的方法(也许我会阅读Wiki页面)。