我收集了大约十几种已定义类型的结构化内容(比如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>
答案 0 :(得分:3)
基于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)
但感觉很尴尬,因为它引入了一个中间值(
Map
components
)
我知道we couldn't persuade you last time around,但引入中间值确实没有错。这样做可以使代码更容易理解,使每个部分的工作更清晰,并重用。如果您不需要其他地方的components
地图(可能就是这种情况),并且不想为它做出顶级定义,只需将其放在where
子句中。
并复制那里的名字
这是一个烦恼,虽然相对较小。如果您的代码的用户无法访问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)