2015-09-11 34 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