繁体   English   中英

使用 Monad 进行函数映射

[英]Function mapping with Monads

我有以下代码:

type Mapper a k v = a -> [(k,v)]
type Reducer k v = k -> [v] -> [v]


mapReduce :: Ord k => Mapper a k v -> Reducer k v -> [a] -> [(k,[v])]
mapReduce m r = reduce r . shuffleKeys . concatMap (map listifyVal . m)
  where listifyVal (k,v) = (k,[v])
        shuffleKeys = Map.fromListWith (++)
        reduce r = Map.toList . Map.mapWithKey r

我得到了一个Monad类型

   type MapperM m a k v = a -> m [(k,v)]
   type ReducerM m k v = k -> [v] -> m [v]

我需要将现有代码转换为使用Monad

mapReduceM :: (Ord k, Monad m) => MapperM m a k v -> ReducerM m k v -> [a] -> m [(k,[v])]

我一直在转换表达式concatMap (map listifyVal . m)我写了这个辅助函数

listifyValM:: (Ord k, Monad m) => m(k,v) ->  m(k,[v])                        
listifyValM mkv = do
                    (k,v) <- mkv
                    return (k,[v])

并尝试

 mapReduceM :: (Ord k, Monad m) => MapperM m a k v -> ReducerM m k v -> [a] -> m [(k,[v])]
 mapReduceM m r input =  do      
                            let step0 =  (map listifyValM )                                                  
                            return []

但是我什至无法让这个(非常部分的)简单的事情作为初学者工作。 我得到:

  Could not deduce (Ord k0) arising from a use of `listifyValM'
  from the context: (Ord k, Monad m)
    bound by the type signature for:

我可能缺少关于mapping Monads一些基本Monads

从头开始编写mapReduceM可能比尝试机械转换mapReduce更容易。 你要:

mapReduceM :: (Monad m, Ord k) => MapperM m a k v -> ReducerM m k v -> [a] -> m [(k,[v])]
mapReduceM m r as = ...

因此,让我们假设以下类型的参数并尝试构建函数:

m :: a -> m [(k,v)]
r :: k -> [v] -> m [v]
as :: [a]

创建一个使用具体类型的骨架文件会很有帮助,这将允许我们在 GHCi 中键入检查表达式:

import Data.Map (Map)
import qualified Data.Map as Map

data A
data K = K deriving (Eq, Ord)
data V
data M a
instance Functor M
instance Applicative M
instance Monad M

m :: A -> M [(K,V)]
m = undefined
r :: K -> [V] -> M [V]
r = undefined
as :: [A]
as = undefined

显然,我们需要将as的元素传递给唯一可以使用它们的函数,即m 将一元函数a -> mb应用于列表[a]的标准方法是使用mapMtraverse执行traverse (在过去,前者用于 monads,后者用于 applicatives,但现在所有 monads 都是 applicatives,首选traverse 。)具体来说,加载这个骨架后,我们可以在 GHCi 中键入检查这个表达式:

> :t traverse m as
traverse m as :: M [[(K, V)]]

为了折叠列表列表,我们希望在 monad 的“下”应用concat 幸运的是,monad 是函子,所以fmap (或其同义词(<$>) )可以做到:

> :t concat <$> traverse m as
concat <$> traverse m as :: M [(K, V)]

现在,我们想通过公共键将其加载到地图中。 换句话说,我们想应用纯函数:

combineByKey :: [(K, V)] -> Map K [V]
combineByKey = Map.fromListWith (++) . map (\(k,v) -> (k,[v]))

在 monad 下:

> :t combineByKey . concat <$> traverse m as
combineByKey . concat <$> traverse m as :: M (Map K [V])

现在,我们想将减少r应用于地图。 这有点棘手。 如果我们尝试以与concatcombineByKey相同的方式在 monad 下应用mapWithKey ,我们会得到一个额外的不需要的 monad 层:

> :t Map.mapWithKey r . combineByKey . concat <$> traverse m as
Map.mapWithKey r . combineByKey . concat <$> traverse m as
  :: M (Map K (M [V]))
     ^---------^---------- two monad layers

在这里, do符号可以帮助我们完成整个过程:

do mymap <- combineByKey . concat <$> traverse m as
   ...

在这里, mymap被从 monad 中拉出来,所以有类型:

mymap :: Map K [V]
mymap = undefined

如果我们使用mapWithKey来应用 reducer:

> Map.mapWithKey r mymap
Map.mapWithKey r mymap :: Map K (M [V])

我们有一个一元元素的结构。 因为Map是可遍历的,我们可以使用sequence将 monad 拉到外面:

> sequence $ Map.mapWithKey r mymap
sequence $ Map.mapWithKey r mymap :: M (Map K [V])

几乎就是我们想要的mapReduceM返回值。 我们只需要将映射更改为 monad 下的列表:

> Map.toList <$> (sequence $ Map.mapWithKey r mymap)
Map.toList <$> (sequence $ Map.mapWithKey r mymap) :: M [(K, [V])]

最后,我们对mapReduceM的定义是完成的 do-block:

mapReduceM :: (Monad m, Ord k) => MapperM m a k v -> ReducerM m k v -> [a] -> m [(k,[v])]
mapReduceM m r as = do
  mymap <- combineByKey . concat <$> traverse m as
  Map.toList <$> (sequence $ Map.mapWithKey r mymap)
  where combineByKey :: (Ord k) => [(k, v)] -> Map k [v]
        combineByKey = Map.fromListWith (++) . map (\(k,v) -> (k,[v]))

这可以转换为无点形式,如下所示:

import Control.Monad

mapReduceM :: (Monad m, Ord k) => MapperM m a k v -> ReducerM m k v -> [a] -> m [(k,[v])]
mapReduceM m r =
  traverse m >=>                       -- apply the mapper
  pure . combineByKey . concat >=>     -- combine keys
  sequence . Map.mapWithKey r >=>      -- reduce
  pure . Map.toList                    -- convert to list

  where combineByKey :: (Ord k) => [(k, v)] -> Map k [v]
        combineByKey = Map.fromListWith (++) . map (\(k,v) -> (k,[v]))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM