我认为这是半真半假。 Haskell 具有惊人的抽象能力,包括对命令式思想的抽象。例如,Haskell 没有内置的命令式 while 循环,但我们可以直接编写它,现在它可以了:
while :: (Monad m) => m Bool -> m () -> m ()
while cond action = do
c <- cond
if c
then action >> while cond action
else return ()
对于许多命令式语言来说,这种抽象级别是困难的。这可以在具有闭包的命令式语言中完成;例如。 Python 和 C#。
但 Haskell 还具有(非常独特的)能力,可以使用 Monad 类表征允许的副作用。例如,如果我们有一个函数:
foo :: (MonadWriter [String] m) => m Int
这可以是一个“命令式”函数,但我们知道它只能做两件事:
它不能打印到控制台或建立网络连接等。结合抽象能力,你可以编写作用于“任何产生流的计算”等的函数。
实际上,Haskell 的抽象能力使它成为一种非常优秀的命令式语言。
但是,错误的一半是语法。我发现 Haskell 非常冗长且难以在命令式风格中使用。下面是一个使用上述while 循环的命令式计算示例,它找到链表的最后一个元素:
lastElt :: [a] -> IO a
lastElt [] = fail "Empty list!!"
lastElt xs = do
lst <- newIORef xs
ret <- newIORef (head xs)
while (not . null <$> readIORef lst) $ do
(x:xs) <- readIORef lst
writeIORef lst xs
writeIORef ret x
readIORef ret
所有 IORef 垃圾、双重读取、必须绑定读取结果、fmapping (<$>) 以对内联计算的结果进行操作……这一切看起来都非常复杂。从函数式的角度来看,这很有意义,但命令式语言倾向于将这些细节中的大部分隐藏起来,以使它们更易于使用。
诚然,如果我们使用不同的while 风格的组合器,它可能会更简洁。但是,如果您将这种理念走得足够远(使用一组丰富的组合器来清楚地表达自己),那么您就会再次到达函数式编程。命令式 Haskell 只是不像精心设计的命令式语言那样“流动”,例如蟒蛇。
总之,Haskell 在语法上进行了改头换面,很可能是最好的命令式语言。但是,就整容的本质而言,它会用外表美丽和虚假的东西代替内在美丽和真实的东西。
编辑:对比lastElt这个python音译:
def last_elt(xs):
assert xs, "Empty list!!"
lst = xs
ret = xs.head
while lst:
ret = lst.head
lst = lst.tail
return ret
行数相同,但每行的噪音要少很多。
编辑 2
对于它的价值,这就是 Haskell 中的 纯 替代品的样子:
lastElt = return . last
就是这样。或者,如果你禁止我使用Prelude.last:
lastElt [] = fail "Unsafe lastElt called on empty list"
lastElt [x] = return x
lastElt (_:xs) = lastElt xs
或者,如果您希望它适用于任何 Foldable 数据结构并认识到您实际上并不需要 IO 来处理错误:
import Data.Foldable (Foldable, foldMap)
import Data.Monoid (Monoid(..), Last(..))
lastElt :: (Foldable t) => t a -> Maybe a
lastElt = getLast . foldMap (Last . Just)
以Map为例:
λ➔ let example = fromList [(10, "spam"), (50, "eggs"), (20, "ham")] :: Map Int String
λ➔ lastElt example
Just "eggs"
(.) 运算符是function composition。