简体   繁体   English

使用 Monad 进行函数映射

[英]Function mapping with Monads

I have the following code :我有以下代码:

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

I'm given a Monad types我得到了一个Monad类型

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

And I need to convert the existing code to use Monad我需要将现有代码转换为使用Monad

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

I'm stuck on converting the expression concatMap (map listifyVal . m) I wrote this helper function我一直在转换表达式concatMap (map listifyVal . m)我写了这个辅助函数

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

and tried并尝试

 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 []

But I can't get even this (very partial) simple thing to work as a starter.但是我什至无法让这个(非常部分的)简单的事情作为初学者工作。 I get :我得到:

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

I'm probably missing something basic regarding mapping over Monads .我可能缺少关于mapping Monads一些基本Monads

Writing mapReduceM from scratch may be easier than trying to mechanically convert mapReduce .从头开始编写mapReduceM可能比尝试机械转换mapReduce更容易。 You want:你要:

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

So, let's assume arguments of the following types and try to build the function:因此,让我们假设以下类型的参数并尝试构建函数:

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

It can be helpful to create a skeleton file that uses concrete types which will let us type check expressions in GHCi:创建一个使用具体类型的骨架文件会很有帮助,这将允许我们在 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

Obviously, we need to pass the elements of as to the only available function that can take them, namely m .显然,我们需要将as的元素传递给唯一可以使用它们的函数,即m The standard way of applying a monadic function a -> mb to a list [a] is to perform a traversal, using mapM or traverse .将一元函数a -> mb应用于列表[a]的标准方法是使用mapMtraverse执行traverse (In the old days, the former was for monads and the latter was for applicatives, but now that all monads are applicatives, traverse is preferred.) Specifically, with this skeleton loaded, we can type check this expression in GHCi: (在过去,前者用于 monads,后者用于 applicatives,但现在所有 monads 都是 applicatives,首选traverse 。)具体来说,加载这个骨架后,我们可以在 GHCi 中键入检查这个表达式:

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

To collapse the lists of lists, we want to apply concat "under" the monad.为了折叠列表列表,我们希望在 monad 的“下”应用concat Fortunately, monads are functors, so fmap (or its synonym (<$>) ) can do it:幸运的是,monad 是函子,所以fmap (或其同义词(<$>) )可以做到:

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

Now, we want to load this into a map by common keys.现在,我们想通过公共键将其加载到地图中。 In other word, we want to apply the pure function:换句话说,我们想应用纯函数:

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

under the monad:在 monad 下:

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

Now, we want to apply the reduction r to the map.现在,我们想将减少r应用于地图。 This is a little tricky.这有点棘手。 If we try to apply mapWithKey under the monad the same way we did with concat and combineByKey , we get an extra unwanted monad layer:如果我们尝试以与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

Here, do notation can help us work through the process:在这里, do符号可以帮助我们完成整个过程:

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

Here, mymap is pulled out of the monad and so has type:在这里, mymap被从 monad 中拉出来,所以有类型:

mymap :: Map K [V]
mymap = undefined

If we used mapWithKey to apply the reducer:如果我们使用mapWithKey来应用 reducer:

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

we have a structure of monadic elements.我们有一个一元元素的结构。 Because Map is traversable, we can pull the monad to the outside with sequence :因为Map是可遍历的,我们可以使用sequence将 monad 拉到外面:

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

This is almost the return value we wanted from mapReduceM .几乎就是我们想要的mapReduceM返回值。 We just need to change the map to a list under the monad:我们只需要将映射更改为 monad 下的列表:

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

In the end, our definition of mapReduceM is the completed do-block:最后,我们对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]))

This can be converted to a point-free form like so:这可以转换为无点形式,如下所示:

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