因此,我必须定义安全的head函数版本,当将[]作为参数传递时,该函数不会引发错误。在这里:
safeHead :: [a] -> Maybe a
safeHead [] = Nothing
safeHead (x:_) = Just x
但是现在,此功能仍然有用吗?因为假定类型“ a”是一个Int,则可以添加两个Int类型的对象,但是不能添加两个“ Maybe Int”类型的对象。
答案 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
不打印任何内容。
这是一个完整的程序,即使在其他Traversable
或Foldable
实例中也无法对Maybe
值进行案例分析的情况下,该程序仍然有效。 <$>
是使您可以将函数应用于Maybe
或任何Functor
中包含的内容的键,并且如果您有两个Maybe
(或两个相同的{{ 1}}),您可以使用Applicative
模式,类似于fn <$> applicative_a <*> applicative_b
,但其中fn a b
和a
包含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 <*> maybeInt
或liftA2 (+) maybeInt maybeInt
。同样,在这里我们将(+) :: Int -> Int -> Int
提升为liftA2 (+) :: Maybe Int -> Maybe Int -> Maybe Int
。
以这种方式处理Maybe
可以让您建立一个与Maybe
一起使用的函数,这些函数不包含纯函数,并且推迟对Nothing
的检查。甚至避免将其插入到另一个以Maybe
作为参数的函数中。
当然,您可以在任何fmap
上使用liftA
和Applicative
,而不仅仅是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.