[英]How can I parameterise my Haskell functions?
我希望我的術語在這里是正確的 - 如果沒有,隨時編輯任何東西。
我正在使用Haskell作為幫助語言,同時撰寫關於組合博弈論的論文 - 也就是說,我的所有函數都只是兼顧數字並幫助我找到我正在研究的游戲的解決方案。
我為具體的,整體的“板尺寸”編寫了所有功能(想想chequerboard,5x5等)。 我想擴展到任何規模的研究板,因此通過包含Integer參數來修改所有相關功能。 例如 ,
type Size = Int
squares :: Size -> [Square]
squares size = [ (x,y) | x <- [1..size],
y <- [1..size]]
然而,這開始變得混亂。 通常我認為與大小無關的函數在訪問需要大小的函數時必須提供大小。
這很快導致這樣的行:
reaching size = (losing size) \\\ (setA size)
takeUDmir _ [] = []
takeUDmir size (x:xs) = [x] ++ takeUDmir size (xs \\\ mirUD size x)
rotate size s = concatMap (mirUD size) (mirLR size s)
(請注意,函數的內容確實無關緊要,我只是想表明它已經變得無用了。)
我非常有信心使用Haskell,並且通常使用函數式編程,但是我不知道如何去除所有這些size
引用,並且僅僅依賴於設置每個函數大小的其他東西,這需要它使用。
我想我可能正在尋找一個單子,但我不知道。
這是拉出Reader
monad的最佳時機 - 它抽象出一些全局可用的只讀配置數據的概念。
data Config = Config { size :: Int, ... }
type MyMonad = Reader Config
fun :: MyMonad Result
fun = funNeedingTheSize <$> asks size <*> pure arg1 <*> pure arg2
runMyMonad :: Config -> MyMonad a -> a
runMyMonad = flip runReader
我要在這里充實奧古斯都的建議。 如果您只需要定義一次size
,那么您可以在本地綁定中構建所有相關機制。 這有效地允許您在特定size
定義的上下文中創建許多函數,然后使用所有這些函數只需選擇一次size
。 它與RecordWildCards
特別好
{-# LANGUAGE RecordWildCards -#}
data Methods =
Methods { meth1 :: Int -> Int
, meth2 :: Int -> Int
...
}
mkMethods :: Int -- ^ size
-> Methods
mkMethods size =
Methods { meth1 = \i -> size + i
, meth2 = \i -> size - i
...
}
...
someFn :: Int -> Result
someFn size = ... meth1 ... meth2 ...
where Methods {..} = mkMethods size
這實際上是模擬ML模塊的有限方式。
Reader monad的替代方法是Implicit Parameters 。
如果您嘗試在遞歸調用中更改參數的值,語義可能會很微妙,這些通常會導致錯誤。
然而,它們確實具有避免需要以monadic風格重寫代碼的優勢,這是一種非常具有侵略性的改變,有損於可讀性。
我的觀點是,它們在像你這樣的情況下是完美的,它們只會在一系列調用的頂層設置為一種“配置”信息,而不會在該鏈中發生變化。
如果你有任何類型簽名,它們仍會侵入您的類型簽名,但如果您不提供簽名,它們會像類型類一樣自動推斷。
如果您在任何地方使用它們的size
上面的代碼片段將使用隱式參數看起來像這樣:
type Size = Int
squares :: (?size :: Size) => [Square]
squares = [ (x,y) | x <- [1..?size],
y <- [1..?size]]
reaching = losing \\\ setA
takeUDmir [] = []
takeUDmir (x:xs) = [x] ++ takeUDmir (xs \\\ mirUD x)
rotate s = concatMap mirUD (mirLR s)
可能在某些函數中,顯式傳遞值更合適 - 它取決於size
參數對特定函數的重要程度。
要實際“實例化”參數,請使用let
:
let ?size = 5 in squares
解決此問題的另一種方法是使用reflection
庫。 這絕對是一種復雜的方法,應該非常謹慎地使用。 FP Complete上的Austin Seipp 提供了一個教程資源, 用於實現它,但這可能非常多毛。
reflection
的簡化形式是Given
類型類。 要做到這一點,我們只是標注了功能與given
的地方,我們希望一些全球配置價值。
meth1 = 1 + given
meth2 = given - 1
像ImplicitParams
一樣,我們注意到我們在類型簽名中使用了Given
。
meth1 :: (Num a, Given a) => a
meth2 :: (Num a, Given a) => a
這允許我們靜態地消除它們並確保使用give
傳遞所有正確的配置信息
give :: a -> (Given a => r) -> r
因此,如果我們有一些someComplexCalculation :: Given Config => Result
我們可以通過提供Config
來解決這個問題。
give config someComplexCalculation :: Result
並且類型確保我們不會讓這個丟失的配置傳播到頂部。
在Reifies
/ reflect
/ reify
機械推廣此屬性,但在一些方便和簡單的虧損這樣做。 如果你必須傳遞多個重疊使用的配置,那么它是必需的。 Given
系統不可能知道哪個given
s應該被重疊區域下的傳遞配置替換, Reifies
使用s
參數解決這個問題很像ST
monad的s
參數。
然而, Reifies
的殺手級應用程序將運行時配置的類型類傳遞給表達式。 通常你不能覆蓋類型類,但是Reifies
提供了一種方法。 有關詳細信息,請參閱Austin Seipp的底部教程。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.