Haskell:如何在数据中解开嵌套的可能

时间:2019-04-06 07:32:59

标签: haskell

新手在这里!

假设我有以下数据:

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代码是什么?

2 个答案:

答案 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"的情况。)