用于设置索引结构化值集合的Haskell习语

时间:2015-09-11 17:45:31

标签: haskell coding-style associative-array idioms idiomatic

我收集了大约十几种已定义类型的结构化内容(比如Component),每个内容都可以通过" name"来识别。努力理清一种惯用的Haskell实例化和检索它们的方式。这些东西在我的应用程序中经常使用,因此从概念上讲它们是一组全局常量或常量表,理想情况下它们将被初始化并保存以便快速检索。

我当前的方法,我感到不舒服,只是使用一个函数来计算"每个Component来自其名称。

data Component = Component {
                    someData :: !String,
                    otherData :: ![Int] 
                } deriving Show

component :: Name -> Component
component n = case n of
    -- about a dozen in the application
    "1"       -> Component "lasdkfj;alksdjfalkf" [1]
    "Q"       -> Component "nvjufhhqwe" [5,10,11]
    "other"   -> Component "ugugugu" []
    "A"       -> Component "alkkjsfkjaleifuhqiweufjc" []
    "B"       -> Component "randomletters" []
    "C"       -> Component "nothingimportant" [9,10]
    "b"       -> Component "uk" []
    "c"       -> Component "x" [4,2,7,9,0]
    ""        -> Component "ABC" []
    -- if not listed above, the Component is computed
    otherwise -> Component (someFunctionOf n) (someOtherFunctionOf n)

这对我来说并不合适。首先,Component的名称实际上是Component的一部分,但并未包含在该类型中。更重要的是,甚至可以计算常数值,实际上它们应该在某个表中进行初始化。

考虑到这一点,我也试过

type Name = String

import Data.Maybe
import Data.Map

data Component = Component {
                    name :: Name,
                    someDate :: String,
                    otherData :: [Int] 
                } deriving Show

components = fromList $ (\c -> (name c, c)) <$> [
    Component "1" "lasdkfj;alksdjfalkf" [1],
    Component "Q" "nvjufhhqwe" [5,10,11],
    Component "other" "ugugugu" [],
    Component "A" "alkkjsfkjaleifuhqiweufjc" [],
    Component "B" "randomletters" [],
    Component "C" "nothingimportant" [9,10],
    Component "b" "uk" [],
    Component "c" "x" [4,2,7,9,0],
    Component "" "ABC" []
    ]

component :: Name -> Component
component n | isNothing c = Component n (someFunctionOf n) (someOtherFunctionOf n) 
            | otherwise   = fromJust c  
        where c = Data.Map.lookup n components

这有利于明确处理常数&#34;值为常量,但感觉很尴尬,因为它引入了一个中间值(Map components)并在那里复制了名称(在Component中并作为相应的键)。

无论如何,我觉得我认为这一切都错了,并且必须有更好的方法来设置一组索引的结构化值,包括一堆常量和计算值。< / p>

1 个答案:

答案 0 :(得分:3)

基于Map的解决方案对我来说很好。两个小调整:首先,您应该对Data.Map进行合格导入,以避免名称冲突:

import qualified Data.Map as M
import Data.Map (Map)

第二个import只是为了方便起见。有了它,您无需在类型签名中编写M.Map

其次,isNothingisJust不是非常惯用的。使用maybefromMaybe或简单地对Maybe值进行模式匹配更为清晰。作为奖励,如果你这样做,你不需要使用fromJust(尽可能避免,因为它是部分的。)

component :: Name -> Component
component n = fromMaybe
    (Component n (someFunctionOf n) (someOtherFunctionOf n))
    (M.lookup n components)
  

但感觉很尴尬,因为它引入了一个中间值(Map components

我知道we couldn't persuade you last time around,但引入中间值确实没有错。这样做可以使代码更容易理解,使每个部分的工作更清晰,并重用。如果您不需要其他地方的components地图(可能就是这种情况),并且不想为它做出顶级定义,只需将其放在where子句中。

  

并复制那里的名字

这是一个烦恼,虽然相对较小。如果您的代码的用户无法访问components字典,则他们不能通过更改存储组件的名称来引入错误;只有你能这样做。尽管如此,如果你想最大限度地减少你可以引入错误的地方数量(这本身就是一个合法的目标),你可以将components的类型更改为......

components :: Map Name ComponentData

...其中ComponentDataComponent的原始无名称定义。 component函数(用户实际看到的函数)可以保留其当前类型:只需引入类似的内容......

giveNameToComponent :: Name -> ComponentData -> Component

...并将其定义更改为......

component :: Name -> Component
component n = fromMaybe
    (Component n (someFunctionOf n) (someOtherFunctionOf n))
    (giveNameToComponent n <$> M.lookup n components)

......或者等效但使用maybe

component :: Name -> Component
component n = maybe
    (Component n (someFunctionOf n) (someOtherFunctionOf n))
    (giveNameToComponent n)
    (M.lookup n components)