1

以下是 MonadState 中 Lenses 的一系列示例/练习(由 Edward Kmett 编写),基于 Petr Pudlak 对我之前的问题的解决方案。

除了演示镜头的一些用途和功能之外,这些示例还显示了理解 GHCi 生成的类型签名是多么困难。有希望在未来会有所改善吗?

{-# LANGUAGE TemplateHaskell, RankNTypes #-}

import Control.Lens
import Control.Monad.State

---------- Example by Petr Pudlak   ----------
-- | An example of a universal function that modifies any lens.
-- It reads a string and appends it to the existing value.
modif :: Lens' a String -> StateT a IO ()
modif l = do
    s <- lift getLine
    l %= (++ s)

-----------------------------------------------

以下评论类型签名是由 GHCi 生成的。另一个是改编自彼得的。就个人而言,我比 GHCi 生产的那些更难理解,我想知道:为什么 GHCi 不生产那些简化的?

-------------------------------------------

-- modif2
  -- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
     -- (Int -> p a b) -> Setting p s s a b -> t IO ()
modif2 :: (Int -> Int -> Int) -> Lens' a Int -> StateT a IO ()     
modif2 f l = do
    s<- lift getLine
    l %= f (read s :: Int)

---------------------------------------

-- modif3
  -- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
     -- (String -> p a b) -> Setting p s s a b -> t IO ()
modif3 :: (String -> Int -> Int) -> Lens' a Int -> StateT a IO ()     
modif3 f l = do
    s <- lift getLine
    l %= f s
-- :t modif3 (\n -> (+) (read n :: Int)) == Lens' a Int -> StateT a IO ()

---------------------------------------

-- modif4 
  -- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
     -- (t1 -> p a b) -> (String -> t1) -> Setting p s s a b -> t IO ()
modif4 :: (Bool -> Bool -> Bool) -> (String -> Bool) -> Lens' a Bool -> StateT a IO ()
modif4 f f2 l = do
    s <- lift getLine
    l %= f (f2 s)
-- :t modif4 (&&) (\s -> read s :: Bool) == Lens' a Bool -> StateT a IO ()

---------------------------------------
-- modif5
  -- :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
     -- (t1 -> p a b) -> (String -> t1) -> Setting p s s a b -> t IO ()
modif5 :: (b -> b -> b) -> (String -> b) -> Lens' a b -> StateT a IO ()
modif5 f f2 l = do
    s<- lift getLine
    l %= f (f2 s)
-- :t modif5 (&&) (\s -> read s :: Bool) == Lens' a Bool -> StateT a IO ()

---------------------------------------

-- modif6
  -- :: (Profunctor p, MonadState s m) =>
     -- (t -> p a b) -> (t1 -> t) -> t1 -> Setting p s s a b -> m ()
modif6 :: (b -> b -> b) -> (c -> b) -> c -> Lens' a b -> StateT a IO ()
modif6 f f2 x l = do
    l %= f (f2 x)
-- :t modif6 (&&) (\s -> read s :: Bool) "True" ==  MonadState s m => Setting (->) s s Bool Bool -> m ()
-- :t modif6 (&&) (\s -> read s :: Bool) "True" 

---------------------------------------

-- modif7
  -- :: (Profunctor p, MonadState s IO) =>
     -- (t -> p a b) -> (String -> t) -> Setting p s s a b -> IO ()
modif7 :: (b -> b -> b) -> (String -> b) -> Lens' a b -> StateT a IO ()
modif7 f f2 l = do
    s <- lift getLine
    l %= f (f2 s)
-- :t modif7 (&&) (\s -> read s :: Bool) == 
-- :t modif7 (+) (\s -> read s :: Int) == 

---------------------------------------

p7a :: StateT Int IO ()
p7a = do
  get
  modif7 (+) (\s -> read s :: Int) id

test7a = execStateT p7a 10  -- if input 30 then result 40

---------------------------------------

p7b :: StateT Bool IO ()
p7b = do
  get
  modif7 (||) (\s -> read s :: Bool) id

test7b = execStateT p7b False  -- if input "True" then result "True"

---------------------------------------

data Test = Test { _first :: Int
                 , _second :: Bool
                 }
    deriving Show

$(makeLenses ''Test)

dataTest :: Test
dataTest = Test  { _first = 1, _second = False }

monadTest :: StateT Test IO String
monadTest = do
  get
  lift . putStrLn $ "1) modify \"first\" (Int requested)"
  lift . putStrLn $ "2) modify \"second\" (Bool requested)"
  answ <- lift getLine
  case answ of
    "1" -> do lift . putStr $ "> Write an Int: "
              modif7 (+) (\s -> read s :: Int) first
    "2" -> do lift . putStr $ "> Write a Bool: "
              modif7 (||) (\s -> read s :: Bool) second
    _   -> error "Wrong choice!"
  return answ

testMonadTest :: IO Test  
testMonadTest = execStateT monadTest dataTest
4

2 回答 2

5

作为 ML 传统中的一个家族,Haskell 是专门设计的,因此每个顶级绑定都有一个最通用的类​​型,而 Haskell 实现可以并且必须推断出这个最通用的类​​型。这确保您可以在尽可能多的地方重用绑定。在某种程度上,这意味着类型推断永远不会出错,因为无论您想到什么类型,类型推断都会找出相同的类型或更通用的类型。

为什么 GHCi 不产生那些简化的?

它会找出更通用的类型。例如,您提到 GHC 为某些代码找出以下类型:

modif2 :: (Profunctor p, MonadTrans t, MonadState s (t IO)) =>
  (Int -> p a b) -> Setting p s s a b -> t IO ()

这是一个非常通用的类型,因为每次使用时modif2,我都可以选择不同的 profunctors p、monad transformerst和 states s。所以modif2是非常可重用的。您更喜欢这种类型的签名:

modif2 :: (Int -> Int -> Int) -> Lens' a Int -> StateT a IO ()     

我同意这更具可读性,但也不那么通用:在这里,您决定p必须是->并且t必须是StateT,并且作为 的用户modif2,我无法更改它。

有希望在未来会有所改善吗?

我确信 Haskell 将继续强制要求大多数通用类型作为类型推断的结果。我可以想象,除了最通用的 type 之外,ghci 或第三方工具可以向您展示示例实例化。在这种情况下,最好以某种方式声明它->是一个典型的profunctor。不过,我不知道这方面的任何工作,所以没有太大希望,不。

于 2014-05-02T19:50:22.963 回答
4

让我们看一下您的第一个示例:

modif :: Lens' a String -> StateT a IO ()
modif l = do
  s <- lift getLine
  l %= (++ s)

这种类型很简单,但它也有一个缺点:你只能使用你的函数传递一个Lens. Iso当你有are a时,你不能使用你的函数Traversal即使这很有意义!鉴于 GHCi 推断的更一般的类型,您可以例如编写以下内容:

modif _Just :: StateT (Maybe String) IO ()

仅当该状态为 aJust

modif traverse :: StateT [String] IO ()

这会将读取的值附加到列表中的所有元素。这对于您提供的简单类型是不可能的,因为_Justandtraverse不是镜头,而只是Traversals.

于 2014-05-02T20:05:52.977 回答