haskell - 如何在类 map 容器中将镜头定义为总和类型?

标签 haskell haskell-lens lenses

我可以手动定义所需的Lens':

type Key = String
type Val = Int
type Foo  = Map Key (Either Val Bool)

ll :: String -> Lens' Foo (Maybe Int)
ll k f m = f mv <&> \r -> case r of
  Nothing -> maybe m (const (Map.delete k m)) mv
  Just v' -> Map.insert k (Left v') m
  where mv = Map.lookup k m >>= maybeLeft
        maybeLeft (Left v') = Just v'
        maybeLeft (Right _) = Nothing

它的工作方式如下:

x, y :: Foo
x = Map.empty
y = Map.fromList [("foo", Right True)]

>>> x ^. ll "foo"
Nothing

>>> x & ll "foo" ?~ 1
fromList [("foo",Left 1)]

>>> (x & ll "foo" ?~ 1) ^. ll "foo"
Just 1

>>> (x & ll "foo" ?~ 1) ^. ll "bar"
Nothing

>>> x & ll "foo" ?~ 1 & ll "foo" .~ Nothing
fromList []

>>> y ^. ll "foo"
Nothing

>>> y & ll "foo" ?~ 1
fromList [("foo",Left 1)]

>>> y & ll "foo" .~ Nothing
fromList [("foo",Right True)]

我验证了定义是合法的:

-- Orphan instance is ok-ish in this case :)
instance (Ord k, Arbitrary k, Arbitrary v) => Arbitrary (Map k v) where
  arbitrary = Map.fromList <$> arbitrary

-- 1) You get back what you put in:
lensLaw1 :: Foo -> Key -> Maybe Val -> Property
lensLaw1 s k v = view (ll k) (set (ll k) v s) === v

-- 2) Putting back what you got doesn't change anything:
lensLaw2 :: Foo -> Key -> Property
lensLaw2 s k = set (ll k) (view (ll k) s) s === s

-- 3) Setting twice is the same as setting once:
lensLaw3 :: Foo -> Key -> Maybe Val -> Maybe Val -> Property
lensLaw3 s k v v' = set (ll k) v' (set (ll k) v s) === set (ll k) v' s

所以问题:ll 可以使用at 定义吗?和 _Left

也许使用某种 prismToLens::Prism' a b -> Lens' (Maybe a) (Maybe b),您可以在 k 处执行。 prismToLens _Left。但我不确定 prismToLens 是否有意义? Hoogle 对 lens 没有太大帮助:(

EDIT 似乎第三定律并不总是成立。如果将 Key 更改为 Bool,很容易找到反例。然而在我的应用程序中 Map 实际上是依赖的,即求和分支依赖于键,所以 Lens 法则应该成立(如果我访问 foo,我知道它应该是 Left 或根本不存在)。

最佳答案

现在我选择:

prismToLens :: Prism' a b -> Lens' (Maybe a) (Maybe b)
prismToLens p = lens getter setter
  where getter s = s >>= (^? p)
        setter _ b = (p#) <$> b

所以我可以像这样定义 ll:

ll' :: Key -> Lens' Foo (Maybe Val)
ll' k = at k . prismToLens _Left

与问题中定义的“镜头”相反,第二定律不成立:

-- 2) Putting back what you got doesn't change anything:
-- Doesn't hold
-- >>> quickCheck $ lensLaw2' (Map.fromList [(True,Right False)]) True
-- fromList [] /= fromList [(True,Right False)]
lensLaw2' :: Foo -> Key -> Property
lensLaw2' s k = set (ll' k) (view (ll' k) s) s === s

但是原来的第三定律不成立:

-- 3) Setting twice is the same as setting once:
-- Doesn't hold
-- >>> quickCheck $ lensLaw3 (Map.fromList [(False, Right False)]) False (Just 0) Nothing
-- fromList [] /= fromList [(True,Right False)]
lensLaw3 :: Foo -> Key -> Maybe Val -> Maybe Val -> Property
lensLaw3 s k v v' = set (ll k) v' (set (ll k) v s) === set (ll k) v' s

正如问题中所说,因为我有依赖映射,所以没关系。当访问一些键 k 时,不应该有 Right 值,如果我希望有 Left 的话。考虑到这一点,使用 prismToLens 实际上更好。不过,仍在寻找更好的名称。


记起来之后non ,我更改了答案以使用:

prismToIso :: Prism' a b -> Iso' (Maybe a) (Maybe b)
prismToIso p = iso t f
  where t a = a >>=  (^? p)
        f b = (p#) <$> b -- no unused param as in `prismToLens`!

类似于 mapping .法则属性的行为与 prismToLens 相同。这就产生了新的问题:prismToIsoprismToLens 哪个更好或更差。为什么?


完整的可运行示例:

{-# LANGUAGE RankNTypes #-}
module Lens where

import Control.Applicative
import Control.Lens
import Data.Map as Map
import Test.QuickCheck

type Key = Bool
type Val = Int
type Foo  = Map Key (Either Val Bool)

ll :: Key -> Lens' Foo (Maybe Val)
ll k f m = f mv <&> \r -> case r of
  Nothing -> maybe m (const (Map.delete k m)) mv
  Just v' -> Map.insert k (Left v') m
  where mv = Map.lookup k m >>= maybeLeft
        maybeLeft (Left v') = Just v'
        maybeLeft (Right _) = Nothing

prismToLens :: Prism' a b -> Lens' (Maybe a) (Maybe b)
prismToLens p = lens getter setter
  where getter s = s >>= (^? p)
        setter _ b = (p#) <$> b

ll' :: Key -> Lens' Foo (Maybe Val)
ll' k = at k . prismToLens _Left

x, y :: Foo
x = Map.empty
y = Map.fromList [(True, Right True)]

{-
>>> x ^. ll "foo"
Nothing

>>> x & ll "foo" ?~ 1
fromList [("foo",Left 1)]

>>> (x & ll "foo" ?~ 1) ^. ll "foo"
Just 1

>>> (x & ll "foo" ?~ 1) ^. ll "bar"
Nothing

>>> x & ll "foo" ?~ 1 & ll "foo" .~ Nothing
fromList []

>>> y ^. ll "foo"
Nothing

>>> y & ll "foo" ?~ 1
fromList [("foo",Left 1)]

>>> y & ll "foo" .~ Nothing
fromList [("foo",Right True)]
-}

-- Orphan instance is ok-ish in this case :)
instance (Ord k, Arbitrary k, Arbitrary v) => Arbitrary (Map k v) where
  arbitrary = Map.fromList <$> arbitrary
  shrink = Prelude.map Map.fromList . shrink . Map.toList

-- 1) You get back what you put in:
lensLaw1 :: Foo -> Key -> Maybe Val -> Property
lensLaw1 s k v = view (ll k) (set (ll k) v s) === v

-- 2) Putting back what you got doesn't change anything:
lensLaw2 :: Foo -> Key -> Property
lensLaw2 s k = set (ll k) (view (ll k) s) s === s

-- 3) Setting twice is the same as setting once:
-- Doesn't hold
-- >>> quickCheck $ lensLaw3 (Map.fromList [(False, Right False)]) False (Just 0) Nothing
-- fromList [] /= fromList [(True,Right False)]
lensLaw3 :: Foo -> Key -> Maybe Val -> Maybe Val -> Property
lensLaw3 s k v v' = set (ll k) v' (set (ll k) v s) === set (ll k) v' s

-- Using prismToLens defined "lens"

-- 1) You get back what you put in:
lensLaw1' :: Foo -> Key -> Maybe Val -> Property
lensLaw1' s k v = view (ll' k) (set (ll' k) v s) === v

-- 2) Putting back what you got doesn't change anything:
-- Doesn't hold
-- >>> quickCheck $ lensLaw2' (Map.fromList [(True,Right False)]) True
-- fromList [] /= fromList [(True,Right False)]
lensLaw2' :: Foo -> Key -> Property
lensLaw2' s k = set (ll' k) (view (ll' k) s) s === s

-- 3) Setting twice is the same as setting once:
lensLaw3' :: Foo -> Key -> Maybe Val -> Maybe Val -> Property
lensLaw3' s k v v' = set (ll' k) v' (set (ll' k) v s) === set (ll' k) v' s

关于haskell - 如何在类 map 容器中将镜头定义为总和类型?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28427769/

相关文章:

haskell - 通过镜头过滤树中的内部元素

haskell - 如何获得具有重载字段名称的经典镜头?

Haskell Control.镜头移动棱镜

haskell - 由于 gcc 路径,Cabal 无法在 Mac OS X Lion 上运行

haskell - 压缩遍历

haskell - 变形与镜头有何关系?

haskell - 在状态计算中使用 Lens.Family.LensLike' 作为 setter 和 getter

haskell - INLINE Pragma 与类型类相结合

haskell - 为什么这需要显式类型?

sorting - 为什么简约的 Haskell 快速排序不是 "true"快速排序?