Joh*_*ler 13 monads haskell haskell-lens
上下文:这个问题具体参考Control.Lens
(撰写本文时的版本3.9.1)
我一直在使用镜头库,能够读取和写入结构的一块(或遍历的碎片)是非常好的.然后我讨论了是否可以使用镜头来对抗外部数据库.当然,我需要在执行中执行IO Monad
.所以概括:
题:
鉴于一个吸气剂(s -> m a)
和一个定位器(b -> s -> m t)
在哪里m
是Monad,是否可以构造Lens s t a b
镜头的Functor现在也包含在Monad中?是否仍然可以(.)
与其他"纯功能"镜头组合使用?
例:
我可以让Lens (MVar a) (MVar b) a b
使用readMVar
和withMVar
?
替代方案:
对于IO
monad中的容器是否有等效的Control.Lens,如MVar
或IORef
(或STDIN
)?
我一直在考虑这个想法,我称之为可变镜头.到目前为止,如果你从中受益,我还没有把它变成一个包,让我知道.
首先让我们回顾一下通用的van Laarhoven镜头(在我们稍后需要进行一些进口之后):
{-# LANGUAGE RankNTypes #-}
import qualified Data.ByteString as BS
import Data.Functor.Constant
import Data.Functor.Identity
import Data.Traversable (Traversable)
import qualified Data.Traversable as T
import Control.Monad
import Control.Monad.STM
import Control.Concurrent.STM.TVar
type Lens s t a b = forall f . (Functor f) => (a -> f b) -> (s -> f t)
type Lens' s a = Lens s s a a
Run Code Online (Sandbox Code Playgroud)
我们可以用"吸气剂"和"定型器"来制造这样的镜头
mkLens :: (s -> a) -> (s -> b -> t) -> Lens s t a b
mkLens g s f x = fmap (s x) (f (g x))
Run Code Online (Sandbox Code Playgroud)
然后从镜头中取出"吸气剂"/"定型器"
get :: Lens s t a b -> (s -> a)
get l = getConstant . l Constant
set :: Lens s t a b -> (s -> b -> t)
set l x v = runIdentity $ l (const $ Identity v) x
Run Code Online (Sandbox Code Playgroud)
例如,以下镜头访问一对中的第一个元素:
_1 :: Lens' (a, b) a
_1 = mkLens fst (\(x, y) x' -> (x', y))
-- or directly: _1 f (a,c) = (\b -> (b,c)) `fmap` f a
Run Code Online (Sandbox Code Playgroud)
现在可变透镜应该如何工作?获取某些容器的内容涉及一个monadic动作.设置一个值不会改变容器,它保持不变,就像一块可变的内存一样.因此,可变镜头的结果必须是monadic,而不是t
我们将拥有的返回型容器()
.而且,Functor
约束是不够的,因为我们需要将它与monadic计算交织.因此,我们需要Traversable
:
type MutableLensM m s a b
= forall f . (Traversable f) => (a -> f b) -> (s -> m (f ()))
type MutableLensM' m s a
= MutableLensM m s a a
Run Code Online (Sandbox Code Playgroud)
(Traversable
是monadic计算什么Functor
是纯计算).
我们再次创建辅助函数
mkLensM :: (Monad m) => (s -> m a) -> (s -> b -> m ())
-> MutableLensM m s a b
mkLensM g s f x = g x >>= T.mapM (s x) . f
mget :: (Monad m) => MutableLensM m s a b -> s -> m a
mget l s = liftM getConstant $ l Constant s
mset :: (Monad m) => MutableLensM m s a b -> s -> b -> m ()
mset l s v = liftM runIdentity $ l (const $ Identity v) s
Run Code Online (Sandbox Code Playgroud)
举个例子,让我们从TVar
内部创建一个可变镜头STM
:
alterTVar :: MutableLensM' STM (TVar a) a
alterTVar = mkLensM readTVar writeTVar
Run Code Online (Sandbox Code Playgroud)
这些透镜片面直接与组合的Lens
,例如
alterTVar . _1 :: MutableLensM' STM (TVar (a, b)) a
Run Code Online (Sandbox Code Playgroud)
笔记:
如果我们允许修改功能包括效果,可变透镜可以变得更强大:
type MutableLensM2 m s a b
= (Traversable f) => (a -> m (f b)) -> (s -> m (f ()))
type MutableLensM2' m s a
= MutableLensM2 m s a a
mkLensM2 :: (Monad m) => (s -> m a) -> (s -> b -> m ())
-> MutableLensM2 m s a b
mkLensM2 g s f x = g x >>= f >>= T.mapM (s x)
Run Code Online (Sandbox Code Playgroud)
但是,它有两个主要缺点:
Lens
.monadic镜片还有其他可能性.例如,我们可以创建一个monadic copy-on-write镜头来保留原始容器(就像那样Lens
),但是操作涉及一些monadic动作:
type LensCOW m s t a b
= forall f . (Traversable f) => (a -> f b) -> (s -> m (f t))
Run Code Online (Sandbox Code Playgroud)我已经制作了jLens - 一个用于可变镜头的Java库,但API当然远不如Haskell镜头那么好.
不,您不能将“镜头功能”限制为Monad。a的类型Lens
要求它与所有Functor
s 兼容:
type Lens s t a b = forall f. Functor f => (a -> f b) -> s -> f t
Run Code Online (Sandbox Code Playgroud)
这在英语中类似于:A Lens是一个函数,对于所有类型f
,其中f
a是a Functor
,取a (a -> f b)
并返回an s -> f t
。关键在于它必须为每个函数提供这种功能Functor
f
,而不仅仅是它们中某些恰好是Monad
s的子集。
编辑:
您可以制作一个Lens (MVar a) (MVar b) a b
,因为都不存在s
t
a
或b
受限制。那么,构造它的getter和setter上的类型是什么?(MVar a -> a)
我相信只能将getter的类型实现为该类型\_ -> undefined
,因为除了as之外,没有其他东西可以从MVar中提取值IO a
。设置器将是(MVar a -> b -> MVar b)
,我们也无法定义它,因为没有任何东西可以使MVar
as 成为除外IO (MVar b)
。
这表明,相反,我们可以改成类型Lens (MVar a) (IO (MVar b)) (IO a) b
。这将是一条有趣的途径,可以进一步使用一些我现在没有的实际代码和编译器。要将其与其他“纯功能性”镜头结合使用,我们可能需要某种举升机构将其举升为monad,例如liftLM :: (Monad m) => Lens s t a b -> Lens s (m t) (m a) b
。
编译的代码(第二次编辑):
为了能够使用Lens s t a b
as,Getter s a
我们必须有s ~ t
and a ~ b
。这限制了我们的有用的镜片类型解除对一些Monad
以最广泛的类型s
,并t
与最广泛的类型a
和b
。如果我们用b ~ a
可能的类型代替,我们会拥有Lens (MVar a) (IO (MVar a)) (IO a) a
,但是我们仍然需要MVar a ~ IO (MVar a)
和IO a ~ a
。我们将每种类型的类型进行选择Lens (IO (MVar a)) (IO (MVar a)) (IO a) (IO a)
,然后选择Control.Lens.Lens让我们编写为Lens' (IO (MVar a)) (IO a)
。按照这一思路,我们可以制作一个完整的系统,将“纯功能性”镜片与单价镜片组合在一起。提起“纯功能”镜头的操作,liftLensM
其类型为(Monad m) => Lens' s a -> LensF' m s a
,其中LensF' f s a ~ Lens' (f s) (f a)
。
{-# LANGUAGE RankNTypes, ScopedTypeVariables #-}
module Main (
main
) where
import Control.Lens
import Control.Concurrent.MVar
main = do
-- Using MVar
putStrLn "Ordinary MVar"
var <- newMVar 1
output var
swapMVar var 2
output var
-- Using mvarLens
putStrLn ""
putStrLn "MVar accessed through a LensF' IO"
value <- (return var) ^. mvarLens
putStrLn $ show value
set mvarLens (return 3) (return var)
output var
-- Debugging lens
putStrLn ""
putStrLn "MVar accessed through a LensF' IO that also debugs"
value <- readM (debug mvarLens) var
putStrLn $ show value
setM (debug mvarLens) 4 var
output var
-- Debugging crazy box lens
putStrLn ""
putStrLn "MVar accessed through a LensF' IO that also debugs through a Box that's been lifted to LensF' IO that also debugs"
value <- readM ((debug mvarLens) . (debug (liftLensM boxLens))) var
putStrLn $ show value
setM ((debug mvarLens) . (debug (liftLensM boxLens))) (Box 5) var
output var
where
output = \v -> (readMVar v) >>= (putStrLn . show)
-- Types to write higher lenses easily
type LensF f s t a b = Lens (f s) (f t) (f a) (f b)
type LensF' f s a = Lens' (f s) (f a)
type GetterF f s a = Getter (f s) (f a)
type SetterF f s t a b = Setter (f s) (f t) (f a) (f b)
-- Lenses for MVars
setMVar :: IO (MVar a) -> IO a -> IO (MVar a)
setMVar ioVar ioValue = do
var <- ioVar
value <- ioValue
swapMVar var value
return var
getMVar :: IO (MVar a) -> IO a
getMVar ioVar = do
var <- ioVar
readMVar var
-- (flip (>>=)) readMVar
mvarLens :: LensF' IO (MVar a) a
mvarLens = lens getMVar setMVar
-- Lift a Lens' to a Lens' on monadic values
liftLensM :: (Monad m) => Lens' s a -> LensF' m s a
liftLensM pureLens = lens getM setM
where
getM mS = do
s <- mS
return (s^.pureLens)
setM mS mValue = do
s <- mS
value <- mValue
return (set pureLens value s)
-- Output when a Lens' is used in IO
debug :: (Show a) => LensF' IO s a -> LensF' IO s a
debug l = lens debugGet debugSet
where
debugGet ioS = do
value <- ioS^.l
putStrLn $ show $ "Getting " ++ (show value)
return value
debugSet ioS ioValue = do
value <- ioValue
putStrLn $ show $ "Setting " ++ (show value)
set l (return value) ioS
-- Easier way to use lenses in a monad (if you don't like writing return for each argument)
readM :: (Monad m) => GetterF m s a -> s -> m a
readM l s = (return s) ^. l
setM :: (Monad m) => SetterF m s t a b -> b -> s -> m t
setM l b s = set l (return b) (return s)
-- Another example lens
newtype Boxed a = Box {
unBox :: a
} deriving Show
boxLens :: Lens' a (Boxed a)
boxLens = lens Box (\_ -> unBox)
Run Code Online (Sandbox Code Playgroud)
此代码产生以下输出:
Ordinary MVar
1
2
MVar accessed through a LensF' IO
2
3
MVar accessed through a LensF' IO that also debugs
"Getting 3"
3
"Setting 4"
4
MVar accessed through a LensF' IO that also debugs through a Box that's been lifted to LensF' IO that also debugs
"Getting 4"
"Getting Box {unBox = 4}"
Box {unBox = 4}
"Setting Box {unBox = 5}"
"Getting 4"
"Setting 5"
5
Run Code Online (Sandbox Code Playgroud)
有可能是一个更好的方式来写liftLensM
,而不诉诸使用lens
,(^.)
,set
和do
符号。通过提取getter和setter并调用lens
新的getter和setter来构建镜头似乎有些错误。
我无法弄清楚如何将镜头既用作吸气剂又用作吸气剂。readM (debug mvarLens)
并且setM (debug mvarLens)
都工作得很好,但像任何结构“让debugMVarLens =调试mvarLens”失去要么它可以作为一个事实Getter
,它可以作为一个事实Setter
,或者说知识Int
是一个实例show
,因此我可以使用debug
。我希望看到一种编写此部分的更好方法。
归档时间: |
|
查看次数: |
1510 次 |
最近记录: |