2012-07-20 20 views
1

如果必須將約束條件設置爲創建的任何圓的半徑必須大於零(半徑> 0)。怎麼做?如何將約束或驗證/條件驗證(對類型變量)?這裏:如何強制半徑> 0?

data Point = Point Float Float deriving (Show) 
data Radius = Radius Float deriving (Show) 
data Shape = Circle Point Radius deriving (Show) 
surface :: Shape -> Float 
surface (Circle _ (Radius r)) = pi * r^2 

如果方便的話,請再舉幾個例子說明如何在各種情況下設置約束/驗證。例如。數據電話可以有正則表達式或特定的一組起始號碼(區號或國家代碼等)。

回答

6

在數據類型的字段上實現驗證的最簡單方法不是從模塊中導出值構造函數,而是定義和導出相反的函數,在使用隱藏值構造函數實際構造和返回對象之前執行所需的檢查。

有兩種可能的方式來報告錯誤一個簡單的例子:

module MyModule 
(Radius -- we do not export value constructors 
, radius 
, radius' 
) where 

data Radius = Radius Float deriving (Show) 

radius :: Float -> Maybe Radius 
radius r | r > 0  = Just (Radius r) 
     | otherwise = Nothing 

radius' :: Float -> Radius 
radius' r | r > 0  = Radius r 
      | otherwise = error "negative radius" 

這樣一來,你的模塊的用戶將只能只能通過您個人定義的功能,以創造新的價值,而不是通過價值構造函數,使他們能夠跳過所有檢查。

+0

很好的例子,非常感謝! – DeTeam 2012-07-20 18:24:25

+0

@DeTeam:不客氣 – 2012-07-21 13:55:34

3

如果你想要比Riccardo的解決方案更有趣一些,你可以使用鏡頭作爲你的界面。您可以使用fclabels來執行此操作,雖然沒有辦法將外部構造函數的失敗與您嘗試驗證的內部值的失敗區分開來。

我也編寫了一個試驗性的lens lib,試圖以更結構化的方式來解決這個用例,但在這個階段我無法真正推薦它。

+0

根據程序員的生產力和應用程序代碼的維護情況,在改進建議的fclabels或鏡頭庫和Reccardo解決方案方面有什麼基本區別? – Optimight 2012-07-20 17:03:47

+0

@優化:鏡片非常有吸引力,當你需要編寫你的訪問器。所以如果出於某種原因,你發現你的圓圈類型嵌套在一個三角形或什麼的,你可以做'半徑。圓「以在該嵌套圓的半徑上產生透鏡。這與Riccardo所建議的只是更好的思路是一樣的,模仿額外的學習/複雜性/附加依賴性 – jberryman 2012-07-20 18:06:16