【问题标题】:Haskell: Understanding the bind and >> functionsHaskell:理解绑定和 >> 函数
【发布时间】:2019-07-26 10:17:18
【问题描述】:

我有以下 Haskell 表达式:

a = getLine >>= putStrLn . filter isDigit >> a

我无法理解上述表达式的工作原理。我知道>>= 函数接受一个单子值和一个函数(它接受一个正常值并返回一个单子值),并返回一个单子值。

我知道getLineputStrLn 有以下类型声明:

getLine :: IO String 
putStrLn :: String -> IO ()

所以下面的部分表达式:

a = getLine >>= putStrLn . filter isDigit

将返回IO ()。但是,函数>> 接受第一个单子值和第二个单子值并返回第二个单子值。

给定原始表达式,传递给>> 的第一个参数将是IO String 类型。第二个参数是a

我的问题是,a 的类型是什么,上面的表达式如何不断地接受用户输入并仅将输入的数字部分打印回屏幕?任何见解都值得赞赏。

【问题讨论】:

  • a :: IO t。是的,它是多态的。无限计算永远不会返回任何内容,因此它可以是任何类型。
  • 如果定义为a=a(无限递归),则类型为a :: forall b. b,即a可以是任何类型b。因为我们改为使用a = ioAction >> a,所以我们强制b 采用IO t 的形式,但t 可以是任何东西。所以我们得到a :: forall t. IO t。它仍然是无限递归,只是在每次递归调用时都会运行 IO 操作。从某种意义上说,这是可行的,因为>> 在执行其第一个参数/动作之前不会评估其第二个参数/动作。
  • 用括号括起来,你所拥有的是a = (getLine >>= (putStrLn . filter isDigit)) >> a。 (>>>>= 都是 infixl 1)。
  • 您说,“传递给>> 的第一个参数将是IO String 类型”。你认为传递给>> 的第一个参数是什么?为什么你认为它的类型为IO String? (这不是第一个参数的正确类型,所以如果您解释您的想法,我们可能会帮助您发现其中的错误。)

标签: haskell functional-programming monads io-monad


【解决方案1】:

注意:我按照@SamuelBarr 的建议将a 函数重命名为readPrintLoop,因为这样可以避免一些混淆。

我的问题是,readPrintLoop 的类型是什么,上面的表达式如何不断地接受用户输入并仅将输入的数字部分打印回屏幕?

readPrintLoop 的类型为:readPrintLoop :: <b>IO a</b>,所以它是 IOa 可以是任何类型,因为我们永远不会“返回”那个值,我们永远不会结束这个函数。

函数不断重复,因为readPrintLoop 是根据自身定义的。 readPrintLoop 定义为:

readPrintLoop :: IO a
readPrintLoop = getLine >>= putStrLn . filter isDigit >> readPrintLoop

因此我们在这里有无限递归,因为最终你会遇到a,因此用另一个getLine &gt;&gt;= putStrLn . filter isDigit &gt;&gt; a 替换它等等。

但是,函数&gt;&gt; 接受第一个单子值和第二个单子值并返回第二个单子值。

(&gt;&gt;) 等价于:

(>>) :: Monad m => m a -> m b -> m b
u >> v = u >>= (\_ -> v)

所以a的实现等价于:

readPrintLoop :: IO a
readPrintLoop = getLine >>= putStrLn . filter isDigit >>= \_ -> readPrintLoop

这里将传递下划线变量_ ()

【讨论】:

  • 可能值得澄清的是,名为a 的函数和类型变量a 是两个不同的东西。通过一些更明智的命名,我们有readPrintLoop :: IO areadPrintLoop = getLine &gt;&gt;= putStrLn . filter isDigit &gt;&gt; readPrintLoop
  • &gt;&gt;的定义有错误:m没有定义,应该是u
【解决方案2】:
a  =  getLine >>= putStrLn . filter isDigit

不是“表达的部分”。

      getLine >>= putStrLn . filter isDigit

是表达式的一部分。而且它不会“返回 IO ()”。它具有类型IO ()(您已经正确推断出(*))。它你所说的“一元价值”

给它起个名字,any name

ioAction :: IO ()
ioAction  =  getLine >>= (putStrLn . filter isDigit)

我们结束了

a  =  ioAction >> a 
----------------------------------
 (>>)     :: IO a -> IO b -> IO b
 ioAction :: IO ()
 a        ::         IO b
----------------------------------
 a        ::                 IO b

一切都进行类型检查。

a in 的语义

a  =  ((>>) ioAction) a

&gt;&gt;的语义定义。


(*)

---------------------------------------------------- 
    (>>=)                     :: m a -> (a -> m b) -> m b
    getLine                   :: IO String                   m a
    putStrLn                  ::    String -> IO ()          
    putStrLn . filter isDigit ::    String -> IO ()            a -> m b
----------------------------------------------------        ------------
 getLine >>= (putStrLn . filter isDigit)   :: IO ()          m        b

【讨论】:

    猜你喜欢
    • 2012-02-22
    • 2014-05-04
    • 1970-01-01
    • 1970-01-01
    • 2012-01-18
    • 1970-01-01
    • 2014-02-14
    • 2018-04-18
    • 2012-03-17
    相关资源
    最近更新 更多