如何在Haskell中按模块对函数进行参数化?

aga*_*gam 10 haskell haskell-backpack

这似乎是人为的,但是我似乎找不到以下答案:

说我有以下进口:

import qualified Data.Map as M
import qualified Data.HashMap.Lazy as HML
Run Code Online (Sandbox Code Playgroud)

现在,我有了一些函数(comp),该函数需要一些列表,执行某些操作,创建地图并返回它。

我的问题是我如何有两种调用方式,comp以便正确调用insertsize映射?

作为一名稻草人,我可以编写此函数的两个副本,一个引用M.insertM.size,而另一个引用HML.insertHML.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 ()
Run Code Online (Sandbox Code Playgroud)

编辑(2):这竟然是方式更有趣的比我预期的,感谢大家谁回答!

dan*_*iaz 6

这是使用模块签名和Mixins(又名Backpack)的方法

您将必须定义一个具有以下签名的库(它可以是内部库):

-- 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
Run Code Online (Sandbox Code Playgroud)

在同一个库中或在另一个库中,编写将签名当作普通模块导入的代码:

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 ()
Run Code Online (Sandbox Code Playgroud)

在另一个库中(必须是另一个库),编写一个与签名匹配的“实现”模块:

-- file Mappy.hs
{-# language ConstraintKinds #-}
module Mappy (C,insert,empty,size,Map) where

import Data.Map.Lazy

type C = Ord
Run Code Online (Sandbox Code Playgroud)

“签名匹配”仅基于名称和类型执行,实现模块不需要知道签名的存在。

然后,在要使用抽象代码的库或可执行文件中,将具有抽象代码的库和具有实现的库同时拉出:

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
Run Code Online (Sandbox Code Playgroud)

有时签名的名称和实现模块的名称不匹配,在这种情况下,必须使用Cabal文件的mixins部分。

编辑。创建HashMap实现被证明有些棘手,因为insert需要两个约束(EqHashable)而不是一个。我不得不诉诸“类同义词”的把戏。这是代码:

{-# 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
Run Code Online (Sandbox Code Playgroud)


Dan*_*ner 5

最简单的方法是根据您实际需要的操作而不是模块进行参数化。所以:

mapComp ::
  m ->
  (K -> V -> m -> m) ->
  (m -> Int) ->
  KVPairs -> IO ()
mapComp empty insert size kvpairs = do
  let m = foldr ins empty kvpairs where
        ins (k, v) t = insert k v t
  if size m /= length kvpairs
  then putStrLn $ "FAIL: " ++ show (size m) ++ ", " ++ show (length kvpairs)
  else pure ()
Run Code Online (Sandbox Code Playgroud)

然后可以将其称为,例如mapComp M.empty M.insert M.sizemapComp 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 ()
Run Code Online (Sandbox Code Playgroud)