【发布时间】:2014-12-09 09:44:48
【问题描述】:
我正在阅读 FP,我有两个基本问题:
FP 说函数应该接受一个输入并给出一个输出。那么我应该如何处理
void方法呢?它没有返回任何正确的东西?FP 说函数应该有一个 责任,那么我们如何处理方法内部的
log语句?这不违反规则吗?
想知道他们如何在 Scala、Haskell 中处理这些事情。 提前致谢。
【问题讨论】:
标签: function scala haskell functional-programming
我正在阅读 FP,我有两个基本问题:
FP 说函数应该接受一个输入并给出一个输出。那么我应该如何处理void 方法呢?它没有返回任何正确的东西?
FP 说函数应该有一个
责任,那么我们如何处理方法内部的log 语句?这不违反规则吗?
想知道他们如何在 Scala、Haskell 中处理这些事情。 提前致谢。
【问题讨论】:
标签: function scala haskell functional-programming
我假设您正在阅读一本名为“函数式编程”的书,尽管了解作者是谁也会有所帮助。无论如何,这些问题都比较容易回答,我会给出关于 Haskell 的答案,因为我不了解 Scala。
那么我应该如何处理 void 方法?它没有返回任何正确的东西?
在像 Haskell 这样的纯函数式语言中没有 void 方法。纯函数没有副作用,所以没有返回值的纯函数是没有意义的,比如
f :: Int -> ()
f x = let y = x * x + 3 in ()
不会进行任何计算,y 永远不会被计算,并且您提供的所有输入都将返回相同的值。但是,如果您有一个不纯的函数,例如写入文件或在屏幕上打印某些内容的函数,那么它必须存在于 monadic 上下文中。如果您还不了解 monad,请不要担心。它们需要一点时间来适应,但它们是一个非常强大和有用的抽象,可以使很多问题变得更容易。 monad 类似于IO,在 Haskell 中,它接受一个类型参数来指示可以存储在此上下文中的值。所以你可以有类似的东西
putStrLn :: String -> IO ()
或者
-- FYI: FilePath is an alias for String
writeFile :: FilePath -> String -> IO ()
这些都有副作用,由IO something 的返回值表示,() 某事表示该操作没有有意义的结果。例如,在 Python 3 中,print 函数返回 None,因为在将值打印到屏幕后返回没有任何意义。 () 也可以表示一元上下文具有有意义的值,例如在readFile 或getLine 中:
getLine :: IO String
readFile :: FilePath -> IO String
在编写 main 函数时,您可以执行类似的操作
main = do
putStrLn "Enter a filename:"
fname <- getLine -- fname has type String
writeFile fname "This text will be in a file"
contents <- readFile fname
putStrLn "I wrote the following text to the file:"
putStrLn contents
FP 说函数应该有单一职责,那么我们如何处理方法内部的日志语句呢?这不违反规则吗?
大多数函数不需要在其中进行日志记录。我知道这听起来很奇怪,但这是真的。在 Haskell 和大多数其他函数式语言中,您将编写许多小的、易于测试的函数,每个函数都执行一个步骤。在您的应用程序中有很多 1 或 2 行函数是很常见的。
当您确实需要进行日志记录时,比如说您正在构建一个 Web 服务器,您可以采用几种不同的方法。实际上有一个名为 Writer 的 monad,它可以让您在执行操作时聚合值。这些操作不必是不纯的做IO,它们可以是完全纯的。然而,一个可能用于 Web 服务器或大型应用程序的真正日志框架可能会附带它自己的框架。这样您就可以将日志记录设置到屏幕、文件、网络位置、电子邮件等。这个 monad 将包装 IO monad 以便它可以执行这些副作用。更高级的可能会使用一些更高级的库,如 monad 转换器或可扩展效果。这些让您可以将不同的单子“组合”在一起,以便您可以同时为两者使用实用程序。你可能会看到类似
type MyApp a = LogT IO a
-- log :: Monad m => LogLevel -> String -> LogT m ()
getConnection :: Socket -> MyApp Connection
getConnection sock = do
log DEBUG "Waiting for next connection"
conn <- liftIO $ acceptConnection sock
log INFO $ "Accepted connection from IP: " ++ show (connectionIP conn)
return conn
我不希望你完全理解这段代码,但我希望你能看到它混合了日志记录和网络操作。 liftIO 函数是 monad 转换器的常见函数,它将 IO 操作“转换”为包装 IO 的新 monad。
这听起来可能很令人困惑,如果您习惯于 Python、Java 或 C++ 之类的语言,一开始可能会感到困惑。我当然是!但是当我习惯了以这种不同的方式思考问题后,我希望我一直都在 OOP 语言中拥有这些结构。
【讨论】:
我可以从 Haskell 的角度来回答。
FP 说函数应该接受一个输入并给出一个输出。那么我应该如何处理 void 方法呢?它没有返回任何正确的东西?
因为这才是真正的功能!在数学中,每个函数都需要一些输入并给你一些输出。如果不提供任何输入,您就不能期望一些输出。 void 你在其他语言中看到的方法在数学上没有意义。但实际上其他语言中的void方法做的是某种IO操作,在Haskell中抽象为IOmonad。
我们如何处理方法内部的日志语句
您可以使用 monad 转换器堆栈并提升您的 IO 日志操作以在那里执行。事实上,writer monad 可以在没有任何IO 活动的情况下进行纯粹的日志操作。
【讨论】: