[英]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]
的标准方法是使用mapM
或traverse
执行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:如果我们尝试以与concat
和combineByKey
相同的方式在 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.