新手在这里!
假设我有以下数据:
data Supplier = Supplier {
nameS :: String,
country :: Maybe String
}
data Product = Product {
nameP :: String,
supplier :: Maybe Supplier
}
我需要一个返回Maybe产品所在国家(地区)的函数,如果链中没有任何内容,则返回“未知”。 我可以这样:
productCountry :: Maybe Product -> String
productCountry product =
case product of
Just p -> case supplier p of
Just s -> case country s of
Just c -> c
Nothing -> "unknown"
Nothing -> "unknown"
Nothing -> "unknown"
但这很尴尬。另一种方法是:
import Data.Maybe (fromMaybe)
productCountry2 :: Maybe Product -> String
productCountry2 product =
let countryMaybe = do
p <- product
s <- supplier p
c <- country s
return c
in fromMaybe "unknown" countryMaybe
我觉得必须有更好的方法来做,但是我找不到。
“ productCountry”的最佳惯用Haskell代码是什么?
答案 0 :(得分:3)
如果编写的第一个参数类型为Maybe Something
的函数,通常是一个好兆头,表明您不必要避免使用Maybe
的monad实例。
data Supplier = Supplier {
nameS :: String,
country :: Maybe String
}
data Product = Product {
nameP :: String,
supplier :: Maybe Supplier
}
productCountry :: Product -> Maybe String
productCountry p = supplier p >>= country
-- Or, using Kleisli composition (requires Control.Monad)
-- productCountry = supplier >=> country
如果发现自己有一个Maybe Product
的实例,请再次使用Maybe
单子将其馈送到productCountry
。
maybeProduct >>= productCountry -- Just "somelandia" or Nothing
此外,让任何呼叫 productCountry
都担心,如果他们返回Nothing
,是否需要占位符国家/地区名称。
答案 1 :(得分:1)
按照建议将我的一些评论收集为答案:
您的第二个代码段:
productCountry2 :: Maybe Product -> String
productCountry2 product =
let countryMaybe = do
p <- product
s <- supplier p
c <- country s
return c
in fromMaybe "unknown" countryMaybe
已经接近我所说的惯用的Haskell。它肯定比您的第一个代码片段好很多倍,其中使用多个显式case
语句来说明以下事实:在此过程中遇到的任何Nothing
结果都意味着立即失败。在第二个代码段中使用Monad
块的Maybe
的{{1}}实例正是在这里,以避免重复这种样板代码。 (实际上,可以争辩说这是所有Monad的真正用途,除了do
有点“神奇”。)
对于改善它,我只有2条建议。首先是从您的IO
块的结尾开始的
do
完全等同于
c <- country s
return c
这是因为所有country s
块只是使用do
运算符的语法糖-您的第一个代码段等效于>>=
。并且,这就是所谓的spreading,与第二个代码段中使用的country s >>= return
相同。
第二种可能的简化方法(尽管我会认为这更多是一个见解的问题),只是完全省略country s
块并使用“已终止”版本:
do
最后,按照@KABU的建议,您可以意识到productCountry2 :: Maybe Product -> String
productCountry2 product =
let countryMaybe = product >>= supplier >>= country
in fromMaybe "unknown" countryMaybe
表达式在这里并没有真正让您受益匪浅,只需将其写为:
let
(尽管我个人认为productCountry2 :: Maybe Product -> String
productCountry2 product = fromMaybe "unknown" $ product >>= supplier >>= country
表达式在这里还可以,但它有点冗长,并且并非绝对必要-但我不会批评它,因为它将Monadic表达式与分别使用let
提取结果并提供fromMaybe
(如果恰好是"unknown"
的情况。)