在Haskell中,如果一个函数返回一个“也许是”类型只是为了安全和全面,那么它又有什么用呢?

时间:2018-10-27 23:15:36

标签: haskell

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

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

但是现在,此功能仍然有用吗?因为假定类型“ a”是一个Int,则可以添加两个Int类型的对象,但是不能添加两个“ Maybe Int”类型的对象。

3 个答案:

答案 0 :(得分:8)

“仅”就是这样一种功能。这是您使用其结果(用于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(或两个相同的{{ 1}}),您可以使用Applicative模式,类似于fn <$> applicative_a <*> applicative_b,但其中fn a ba包含b值之类的内容。

因此,剩下了几种我可以想到的使用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

答案 1 :(得分:6)

正如评论中提到的那样,您实际上可以添加两个Maybe。我只想对此发表另一种观点。

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

要升级一元功能(如(+1)),请编写fmap (+1) maybeInt(+1) <$> maybeInt。如果(+1)的类型为Int -> Int,则fmap (+1)表达式的类型为Maybe Int -> Maybe Int

升级二进制或更多二进制函数的语法更加复杂:(+) <$> maybeInt <*> maybeIntliftA2 (+) maybeInt maybeInt。同样,在这里我们将(+) :: Int -> Int -> Int提升为liftA2 (+) :: Maybe Int -> Maybe Int -> Maybe Int

以这种方式处理Maybe可以让您建立一个与Maybe一起使用的函数,这些函数不包含纯函数,并且推迟对Nothing的检查。甚至避免将其插入到另一个以Maybe作为参数的函数中。

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

答案 2 :(得分:0)

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

让我用其他语言的示例进行解释。虽然我不会命名任何语言,但是很明显我在谈论哪种语言。请确保所有语言在其方式上都很棒,所以不要以为这是我发现其他语言的错。

在某些语言中,您拥有指针,而safeHead的处理方式是返回int指针或空指针。您将不得不取消引用指针以获取该值,而取消引用空指针时将获得错误。为了避免这种情况,将需要额外的代码来检查空指针,并在空指针时执行一些操作。

在某些动态语言中,您已将变量分配为null。因此,在上面的示例中,您的变量可以是int类型,也可以为null。如果将int添加为null会发生什么?最有可能是不确定的情况。再次需要对空情况进行特殊处理。

在Haskell中,您也必须做同样的事情,还必须用额外的代码来保护null情况。那有什么区别呢? Haskell的不同之处在于它是在编译时而不是在运行时进行的。*,即当您拥有这种代码以及您对safeHead p = safeHead xs + safeHead ys的定义时,该代码将在编译时给出错误。如果键入Maybe Int,您将需要做更多的事情来添加。您可以编写用于添加两个或多个Maybe Ints的函数,或者为Maybe Int和overload +创建newype或执行其他答案中提到的操作。

但是无论您做什么,都必须在单元测试之前进行。肯定要花很多时间才能投入生产。尽早发现错误的原因是成本降低。这就是类型安全的Haskell的优点派上用场的地方。

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