Haskell 用于设置索引结构化值集合的习惯用法
Haskell idiom for setting up a collection of indexed structured values
我收集了大约十几个我定义的类型的结构化事物(比如 Component
),每个都可以用 "name" 标识,并且正在努力排序出一种惯用的 Haskell 方式来实例化和检索它们。这些东西在我的应用程序中经常使用,因此从概念上讲,它们是一组全局常量或一组 table 常量,理想情况下会被初始化并保存以供快速检索。
我不喜欢table 我目前的方法,只是使用一个函数来 "compute" 每个 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
的一部分,但不包含在类型中。更重要的是,即使是常量值也会被计算出来,而实际上它们应该只在 table.
中初始化
考虑到这一点,我也尝试过
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
这样做的好处是可以清楚地将 "constant" 值视为常量,但感觉很尴尬,因为它引入了一个中间值(Map
components
)并在那里复制了名称(在Component
中作为对应的key)。
无论如何,我觉得我做的这一切都是错误的,并且必须有更好的方法来设置一组索引的结构化值,其中包括一堆常量和计算值。
你基于 Map
的解决方案对我来说看起来不错。两个小调整:首先,您应该对 Data.Map
进行合格的导入,以避免名称冲突:
import qualified Data.Map as M
import Data.Map (Map)
第二个 import
只是为了方便起见。有了它,你就不需要在类型签名中写M.Map
。
其次,isNothing
和 isJust
不是很地道。使用 maybe
、fromMaybe
或简单地对 Maybe
值进行模式匹配会更清楚。作为奖励,如果你这样做,你不需要使用 fromJust
(尽可能避免使用,因为它是部分的)。
component :: Name -> Component
component n = fromMaybe
(Component n (someFunctionOf n) (someOtherFunctionOf n))
(M.lookup n components)
but feels awkward, since it introduces an intermediate value (the Map
components
)
我知道,但引入中间值确实没有错。这样做可以让代码更容易理解,让每个部分的作用更清晰,并且更容易重用。如果您在其他任何地方都不需要 components
映射(很可能是这种情况)并且不想为它做一个顶级定义,只需将它放在 where
子句中即可。
and duplicates the name there
这是一个烦恼,虽然是一个相对较小的烦恼。如果您的代码的用户无法访问 components
字典,他们就无法通过更改存储组件的名称来引入错误;只有你可以这样做。尽管如此,如果您想尽量减少可能引入错误的地方的数量(这本身就是一个合理的目标),您可以将 components
的类型更改为...
components :: Map Name ComponentData
... 其中 ComponentData
是您对 Component
的原始无名定义。 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)
我收集了大约十几个我定义的类型的结构化事物(比如 Component
),每个都可以用 "name" 标识,并且正在努力排序出一种惯用的 Haskell 方式来实例化和检索它们。这些东西在我的应用程序中经常使用,因此从概念上讲,它们是一组全局常量或一组 table 常量,理想情况下会被初始化并保存以供快速检索。
我不喜欢table 我目前的方法,只是使用一个函数来 "compute" 每个 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
的一部分,但不包含在类型中。更重要的是,即使是常量值也会被计算出来,而实际上它们应该只在 table.
考虑到这一点,我也尝试过
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
这样做的好处是可以清楚地将 "constant" 值视为常量,但感觉很尴尬,因为它引入了一个中间值(Map
components
)并在那里复制了名称(在Component
中作为对应的key)。
无论如何,我觉得我做的这一切都是错误的,并且必须有更好的方法来设置一组索引的结构化值,其中包括一堆常量和计算值。
你基于 Map
的解决方案对我来说看起来不错。两个小调整:首先,您应该对 Data.Map
进行合格的导入,以避免名称冲突:
import qualified Data.Map as M
import Data.Map (Map)
第二个 import
只是为了方便起见。有了它,你就不需要在类型签名中写M.Map
。
其次,isNothing
和 isJust
不是很地道。使用 maybe
、fromMaybe
或简单地对 Maybe
值进行模式匹配会更清楚。作为奖励,如果你这样做,你不需要使用 fromJust
(尽可能避免使用,因为它是部分的)。
component :: Name -> Component
component n = fromMaybe
(Component n (someFunctionOf n) (someOtherFunctionOf n))
(M.lookup n components)
but feels awkward, since it introduces an intermediate value (the
Map
components
)
我知道components
映射(很可能是这种情况)并且不想为它做一个顶级定义,只需将它放在 where
子句中即可。
and duplicates the name there
这是一个烦恼,虽然是一个相对较小的烦恼。如果您的代码的用户无法访问 components
字典,他们就无法通过更改存储组件的名称来引入错误;只有你可以这样做。尽管如此,如果您想尽量减少可能引入错误的地方的数量(这本身就是一个合理的目标),您可以将 components
的类型更改为...
components :: Map Name ComponentData
... 其中 ComponentData
是您对 Component
的原始无名定义。 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)