可组合透镜
Composable Lensing
我有以下 Haskell 类型
import Control.Lens
import Data.Map.Lens
import qualified Data.Map as M
type RepoStats = M.Map String (M.Map String RepoStat)
data RepoStat = RepoStat {
_full_name :: String,
_bug_count :: Maybe Int,
_commit_count :: Maybe Int
}
deriving (Generic, Show, Eq, Read)
makeLenses ''RepoStat
我希望能够抽象出嵌套的值检查并给出默认值:
makeStat name bug commit =
RepoStat
{ _full_name = name
, _bug_count = Just bug
, _commit_count = Just commit
}
getRepoStat :: String -> String -> (RepoStat -> Identity RepoStat) -> RepoStats -> Identity RepoStats
getRepoStat k1 k2 = at k1 . non (M.empty) . at k2 . non (makeStat k2 0 0)
fakeMap = M.empty :: RepoStats
setBug = fakeMap & (getRepoStat "a" "b") . bug_count ?~ 4
getBug = fakeMap ^. (getRepoStat "a" "b") . bug_count
setBug 可以编译并工作,但 getBug 不能。
test/Spec.hs:65:21: Couldn't match type ‘Identity RepoStats’ …
with ‘Const RepoStat RepoStats’
Expected type: Getting RepoStat RepoStats RepoStat
Actual type: (RepoStat -> Identity RepoStat)
-> RepoStats -> Identity RepoStats
In the second argument of ‘(^.)’, namely ‘(getRepoStat "a" "b")’
In the expression: fakeDb ^. (getRepoStat "a" "b")
Compilation failed.
能不能这样抽象出一个镜头?
确实如此。您为 getRepoStat
指定的类型签名是其类型签名作为 setter. If you want to be able to use it as a lens 的完全通用性,那么类型签名应该是
getRepoStat :: Functor f => String -> String -> (RepoStat -> f RepoStat) -> RepoStats -> f RepoStats
-- same definition
这可能也是getRepoStat
的推断类型。
只删除类型签名很有用:
getRepoStat k1 k2 = at k1 . non M.empty . at k2 . non (makeStat k2 0 0)
现在我们可以在 ghci 中使用 :t getRepoStat
查看推断类型。
有点乱,主要是因为at
超载了:
getRepoStat
:: (Functor f, At m, IxValue m ~ M.Map String RepoStat) =>
Index m -> String -> (RepoStat -> f RepoStat) -> m -> f m
我们可以认识到我们 return 一个 Lens'
我们也可以将镜头的上下文限制为 RepoStats
:
getRepoStat :: String -> String -> Lens' RepoStats RepoStat
getRepoStat k1 k2 = at k1 . non M.empty . at k2 . non (makeStat k2 0 0)
我有以下 Haskell 类型
import Control.Lens
import Data.Map.Lens
import qualified Data.Map as M
type RepoStats = M.Map String (M.Map String RepoStat)
data RepoStat = RepoStat {
_full_name :: String,
_bug_count :: Maybe Int,
_commit_count :: Maybe Int
}
deriving (Generic, Show, Eq, Read)
makeLenses ''RepoStat
我希望能够抽象出嵌套的值检查并给出默认值:
makeStat name bug commit =
RepoStat
{ _full_name = name
, _bug_count = Just bug
, _commit_count = Just commit
}
getRepoStat :: String -> String -> (RepoStat -> Identity RepoStat) -> RepoStats -> Identity RepoStats
getRepoStat k1 k2 = at k1 . non (M.empty) . at k2 . non (makeStat k2 0 0)
fakeMap = M.empty :: RepoStats
setBug = fakeMap & (getRepoStat "a" "b") . bug_count ?~ 4
getBug = fakeMap ^. (getRepoStat "a" "b") . bug_count
setBug 可以编译并工作,但 getBug 不能。
test/Spec.hs:65:21: Couldn't match type ‘Identity RepoStats’ …
with ‘Const RepoStat RepoStats’
Expected type: Getting RepoStat RepoStats RepoStat
Actual type: (RepoStat -> Identity RepoStat)
-> RepoStats -> Identity RepoStats
In the second argument of ‘(^.)’, namely ‘(getRepoStat "a" "b")’
In the expression: fakeDb ^. (getRepoStat "a" "b")
Compilation failed.
能不能这样抽象出一个镜头?
确实如此。您为 getRepoStat
指定的类型签名是其类型签名作为 setter. If you want to be able to use it as a lens 的完全通用性,那么类型签名应该是
getRepoStat :: Functor f => String -> String -> (RepoStat -> f RepoStat) -> RepoStats -> f RepoStats
-- same definition
这可能也是getRepoStat
的推断类型。
只删除类型签名很有用:
getRepoStat k1 k2 = at k1 . non M.empty . at k2 . non (makeStat k2 0 0)
现在我们可以在 ghci 中使用 :t getRepoStat
查看推断类型。
有点乱,主要是因为at
超载了:
getRepoStat
:: (Functor f, At m, IxValue m ~ M.Map String RepoStat) =>
Index m -> String -> (RepoStat -> f RepoStat) -> m -> f m
我们可以认识到我们 return 一个 Lens'
我们也可以将镜头的上下文限制为 RepoStats
:
getRepoStat :: String -> String -> Lens' RepoStats RepoStat
getRepoStat k1 k2 = at k1 . non M.empty . at k2 . non (makeStat k2 0 0)