【问题标题】:Haskell beginner question (defining pairs, similar to Http statuses)Haskell 初学者问题(定义对,类似于 Http 状态)
【发布时间】:2011-09-26 21:40:14
【问题描述】:

我是 Haskell 初学者,必须定义一系列已知状态,由 Int 和 String / ByteString 组成,类似于 HTTP 状态

我永远不必从状态消息中获取状态代码。但是,我必须获得给定状态代码的状态消息。

我查看了 Network.HTTP.Types,它们为每个可能的状态代码定义了不同的“变量”、“status200”、“status201”等(类似于下面的“FoodTwo.hs”)。

如果我刚刚定义了一个返回状态代码状态消息的函数,那么(性能?)有什么影响,如下面的“FoodOne.hs”所示?

最重要的是,在 C# 或 Java 等语言中,可能会声明一个静态字典,类似于 FoodThree.hs - 我有点怀疑这是 Haskell 方式吗?为什么?

-- FoodOne.hs
statusMessage :: Int -> String
statusMessage 30 = "BBQ ready"
statusMessage 40 = "Beverages served"
statusMessage rest = "Unknown Food Status"

-- FoodTwo.hs
data FoodStatus = FoodStatus {
    fstatusCode :: Int,
    fstatusMessage :: String
}

status30 = FoodStatus 30 "BBQ ready"
status40 = FoodStatus 40 "Beverages served"

-- FoodThree.hs
statusMessages = [(30,"BBQ ready"),(40,"Beverages served")]

【问题讨论】:

    标签: haskell tuples


    【解决方案1】:
    -- FoodOne.hs
    statusMessage :: Int -> String
    statusMessage 30 = "BBQ ready"
    statusMessage 40 = "Beverages served"
    statusMessage rest = "Unknown Food Status"
    

    大多数编译器会将其编译为线性搜索,因此它将具有线性运行时成本。

    -- FoodTwo.hs
    data FoodStatus = FoodStatus {
        fstatusCode :: Int,
        fstatusMessage :: String
    }
    
    status30 = FoodStatus 30 "BBQ ready"
    status40 = FoodStatus 40 "Beverages served"
    

    这种方法无法与其他方法相比:它不提供给定(动态已知的)Int 的状态查找。但是,对于静态已知的Ints,它是最快的:查找在编译时完成一次,因此无论有多少不同的常量,都具有恒定的运行时成本。

    -- FoodThree.hs
    statusMessages = [(30,"BBQ ready"),(40,"Beverages served")]
    

    在此列表中进行简单查找(例如通过内置的lookup 函数)将涉及线性搜索,因此具有线性运行时成本。

    -- FoodFour.hs
    import Data.Map as M
    statusMessages = fromList [(30, "BBQ ready"),(40,"Beverages served")]
    message n = fromMaybe "Unknown Food Status" (M.lookup n statusMessages)
    

    Data.Map 模块实现了平衡搜索树,每次查找在不同状态的数量上花费对数时间。您还可以考虑Data.IntMap,它也需要对数时间,但具有更好的常数。

    -- FoodFive.hs
    import Data.Array as A
    (statusLo, statusHi) = (30, 40)
    statusMessages = listArray (statusLo, statusHi)
        [ Just "BBQ ready"
        , Nothing
        , Nothing
        , {- ... -}
        , Just "Beverages served"
        ]
    message' n = guard (statusLo <= n && n <= statusHi) >> statusMessages ! n
    message = fromMaybe "Unknown Food Status" . message'
    

    Data.Array 模块实现了不可变数组,每次查找都需要固定时间。但是,如果您的数组是稀疏的,则这可能比其他方法具有更高的内存成本。

    这些都是所有 Haskell 方式。为选择在开发人员烦恼、速度和内存消耗之间进行适当权衡的语言,就像使用任何其他语言一样。

    【讨论】:

    • 这很有趣,我的印象是,一旦对表达式求值,它将被缓存在 Haskell 中,因此变体 1 之间的性能应该与变体 2 相似。另外,使用“status200” ,通过“statusMessage status200”访问状态消息与第一个“statusMessage 200”的相似度如何?
    • @kitsune 大多数编译器仅在程序员明确请求时才进行缓存——例如当他们通过 let-binding、where-binding 或顶级定义创建数据结构时——因为很难自动分析缓存的内存成本何时会得到回报。至于你的另一个问题,在FoodTwo.hs 中,fstatusMessage 函数本质上是一个指针解引用——它是一个常数时间操作。
    【解决方案2】:
    -- FoodThree.hs
    statusMessages = [(30,"BBQ ready"),(40,"Beverages served")]
    

    这可以很容易地在 Haskell 中使用:

    fromCode n = fromMaybe "No such error" $ find ((==) n . fst) statusMessages
    

    或者你可以使用Data.Map:

    import qualified Data.Map as Map
    import Data.Maybe (fromMaybe)
    
    codemap = Map.fromList statusMessages
    
    fromCode n = fromMaybe "No such error" $ Map.lookup n codemap
    

    【讨论】:

    • 您应该谈谈性能影响,因为这正是 kitsune 所问的问题。也就是说,与 Data.Map 相比,列表表示可能很慢。此外,问题不在于这是否可行,而在于这三种提议方式的利弊......
    【解决方案3】:

    会像下面这样的服务吗?

    data FoodStatus = BBQ_READY | BEVERAGES_SERVED | ...
    
    instance Show FoodStatus where ...
    
    toCode status = ...
    fromCode num = ...
    

    这几乎是 FoodOne.hs,但更抽象,因此更易于操作。您可以随意实现 fromCode,并根据需要优化性能。

    【讨论】:

      猜你喜欢
      • 2020-02-14
      • 1970-01-01
      • 2015-05-23
      • 1970-01-01
      • 1970-01-01
      • 2013-05-02
      • 2013-04-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多