在 Haskell 中,如果一个函数返回一个 "Maybe a" 类型,只是为了让它是安全和完全的,那么它又有什么用了呢?



因此,我必须定义一个安全版本的head函数,当[]作为参数传递时,它不会引发错误。这是:

safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x

但是现在,这个功能还有用吗?因为假设类型"a"是Int,那么你可以添加两个Int类型的对象,但不能添加两个"Maybe Int"类型的对象。

正如评论中提到的,您实际上可以添加两个Maybes。我只是想对此给出另一种观点。

是的,您不能直接将(+)应用于Maybe Int,但您可以将其升级到另一个能够自动执行此操作的功能。

要升级一元函数(如(+1)(,请编写fmap (+1) maybeInt(+1) <$> maybeInt。如果(+1)具有Int -> Int类型,则fmap (+1)表达式具有Maybe Int -> Maybe Int类型。

升级bin或更多ary函数在语法上要复杂一些:(+) <$> maybeInt <*> maybeIntliftA2 (+) maybeInt maybeInt。再次,在这里我们将(+) :: Int -> Int -> Int提升为liftA2 (+) :: Maybe Int -> Maybe Int -> Maybe Int

通过这种方式处理Maybe,可以在纯函数之外构建一个与Maybe一起工作的计算,并推迟对Nothing的检查。或者,如果您最终将其插入到另一个以Maybe为参数的函数中,甚至可以避免这种情况。

当然,您可以在任何Applicative上使用fmapliftA,而不仅仅是Maybe

"Just"就是这样一个函数。以下是如何使用其结果(用于ghci REPL(:

import Data.Foldable (sequenceA_)
let writeLn            = putStrLn . show
let supposedlyUnusable = writeLn <$> Just 0 
sequenceA_ supposedlyUnusable

它打印1,或者我们可以继续尝试另一个有趣的例子——使用Nothing案例

let supposedlyUnusable = writeLn <$> Nothing 
sequenceA_ supposedlyUnusable

它不会打印任何内容。

这是一个完整的程序,甚至适用于TraversableFoldable的其他实例,在这些实例中,您无法对Maybe值进行案例分析。<$>是使您可以将函数应用于Maybe或任何Functor中包含的任何内容的键,如果您有两个Maybe(或两个相同的Applicative(,则可以使用模式fn <$> applicative_a <*> applicative_b,它类似于fn a b,但其中ab包括Maybe值。

因此,我可以想到使用Maybe的几种剩余方法,所有这些都是用例分析:

let {fn (Just n) = Just $ 1 + n; fn Nothing  = Nothing}
fn v
-- but that was just a messy way of writing (1+) <$> v

let fn v = case v of {Just n -> Just $ 1 + n; Nothing -> Nothing}
-- and that's the same program with a different syntax

import Data.Maybe (fromMaybe)
fromMaybe someDefault v
-- and that extracted the `value` from `v` if we had `Just value` or else gave us `someDefault`

let {fn (Just n) = writeLn n; fn Nothing = putStrLn "No answer"}
-- this one extracts an action but also provides an action when there's nothing
-- it can be done using <$> and fromMaybe instead, but beginners tend to
-- find it easier because of the tutorials that resulted from the history
-- of the base library's development
let fn v = fromMaybe (putStrLn "No answer") (writeLn <$> v)

哦,哦!这个是整洁的:

import Control.Applicative
let v = Just 0 -- or Nothing, if you want
let errorcase = pure $ putStrLn "No answer"
let successcase = writeLn <$> v
sequenceA_ $ successcase <|> errorcase
-- that uses Alternative in which Maybe tries to give an answer preferring the earliest if it can

当然,我们也有经典的:

maybe (putStrLn "No answer") writeLn v

安全是有代价的。成本通常是额外的代码,以避免出现错误情况。Haskell为我们提供了在编译时而不是在运行时避免这种情况的方法。

让我用其他语言的例子来解释。虽然我不会说出任何语言的名字,但很明显我说的是哪种语言。请确保所有的语言都是很棒的,所以不要认为这是我在其他语言中找茬。

在某些语言中,您有指针,执行safeHead的方法是返回int指针或null指针。你必须取消引用指针才能得到值,当你取消引用空指针时,你会得到错误。为了避免这种情况,将需要额外的代码来检查空指针,并在它为空时执行一些操作。

在一些动态语言中,变量被分配为null。所以在上面的例子中,变量可以是int类型,也可以是null。如果在int中添加null,会发生什么?很可能是未定义的情况。再次,需要对null情况进行特殊处理。

在Haskell中,你也必须这样做,你必须用额外的代码来保护null的情况。那有什么区别呢?Haskell的区别在于在编译时而不是在运行时执行。*也就是说,当你有这种代码以及你对safeHeadp = safeHead xs + safeHead ys的定义时,代码会在编译时出错。如果键入Maybe Int,您将不得不为加法做更多的事情。您可以编写用于添加两个或多个Maybe Ints的函数,或者为Maybe Int创建newtype并重载+,或者做其他答案中提到的事情。

但无论你做什么,都要在单元测试之前完成。在它投入生产之前肯定要花很多时间。越早发现错误,成本越低。这就是类型安全Haskell的优势派上用场的地方。

* There could be mechanism in other languages to handle this at compile time.

相关内容

最新更新