2015-09-11 23 views
1

我收集了大约十几种我定义的类型的结构化东西(比如说Component),其中每个都可以用“名称”来标识,而且努力理清一个惯用的Haskell实例化和检索方法。这些东西在我的应用程序中经常使用,因此概念上它们是一组全局常量或常量表,理想情况下这些常量将被初始化并保存以供快速检索。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 

这显然已经处理了“常量”值作为常量的优势,但觉得尴尬,因为它引入了一个中间值(Mapcomponents)并在那里复制名称(在Component中并作为相应的键)。

无论如何,我觉得我正在解决这一切都是错误的,并且必须有更好的方法来建立一组索引结构化值,其中包括一堆常量和计算值。

回答

3

您的Map为基础的解决方案看起来很好。两个小的调整:首先,你应该做一个合格的进口Data.Map,以避免名称冲突:

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

第二import是那里只是为了更加方便。有了它,你不需要在类型签名中写入M.Map

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

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

但感觉别扭,因为它引入了一个中间值(Mapcomponents

我知道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) 
+0

优秀!你可以多说一点关于第二次导入的注释('import Data.Map(Map)')。另外我想我需要'M.fromList'。是我的lambda生成'组件'好吗?我确实“控制”了这一点,所以“名称”的重复不是这样的问题(正如您正确指出的那样)。是否有Haskell成语来命名这种“私人”或“内部”值? – orome

+1

@raxacoricofallapatorius(1)'import Data.Map(Map)'只是为了让类型签名少一点噪音。离开它是完全可以的;它只意味着你必须编写'components :: M.Map Name Component'。 (2)'M.fromList'的确如此。 (3)拉姆达很好。有一些无点的写法,例如'liftA2(,)name id'('liftA2'在'Control.Applicative'中)和'name &&& id'('(&&&)'在'Control.Arrow '),但在这种情况下,选择很大程度上是品味的问题(有些人会认为免费版本更清晰,而其他人会觉得它们混淆)。 – duplode

+1

@raxacoricofallapatorius(4)“是否有Haskell成语命名这样的”私有“或”内部“值? - 不是真的;只要将它们命名为其他名称即可。这也意味着即使对于内部价值也要有合理清晰的名称,以便人们更容易阅读您的实施。此外,对于具有局部作用域的函数(函数参数,where子句定义等),使用较短和较少的描述性名称通常是可以的,因为在大多数情况下,有可能在快速浏览。 – duplode