2010-09-23 137 views
5

這裏仍然是Haskell的新手。我只知道自己因錯誤的假設而陷入困境。如果我有以下功能...將列表元素作爲參數傳遞給curried函數

quadsum w x y z = w+x+y+z 

我想,可以採取一個列表,在指定的函數中使用的每個元素作爲參數一樣quadsum,並返回一個咖喱​​功能供以後使用的功能。

我一直想的東西的效果...

magicalFunctionMaker f [] = (f) 
magicalFunctionMaker f (x:xs) = magicalFunctionMaker (f x) xs 

具有能夠做到這一點,希望...

magicalFunctionMaker (quadsum) [4,3,2] 

獲得像咖喱功能.. :

(((quadsum 4) 3) 2) 

,或者,請致電:

magicalFunctionMaker (quadsum) [4,3,2,1] 

在...

((((quadsum 4) 3) 2) 1) 

得到的是這可能嗎?我有多誤導?

+0

快速瀏覽polyvariadic函數,但它可能會打擊你的思想:[http://stackoverflow.com/questions/3467279/how-to-create-a-polyvariadic-haskell-function] – fuz 2010-09-23 11:51:56

回答

3

Paul Johnson的回答幾乎涵蓋了它。只要做

quadsum 4 3 2 

和結果將是你想要的功能,類型Integer -> Integer

但有時這還不夠好。有時你會得到數字列表,你不知道列表有多長,你需要將這些元素應用到你的函數中。這有點困難。你不能這樣做:

magicalFunction2 f [] = f 
magicalFunction2 f (x1:x2:xs) = f x1 x2 

因爲結果有不同的類型。在第一種情況下,結果需要兩個參數,而在第二種情況下,它是一個完全應用的函數,因此不允許使用更多參數。在這種情況下,做的最好的事情是守住列表和你原來的功能,直到足夠的參數可供選擇:

type PAPFunc f a result = Either (f, [a]) result 

magicfunc f xs = Left (f,xs) 

apply (Left (f,xs)) ys = Left (f,xs++ys) 
apply p _    = p 

simp2 :: PAPFunc (a->a->b) a b -> PAPFunc (a->a->b) a b 
simp2 (Left (f,(x1:x2:xs))) = Right (f x1 x2) 
simp2 p = p 

現在你可以這樣做:

Main> let j = magicfunc (+) [] 
Main> let m = apply j [1] 
Main> let n = apply m [2,3] 

Main> either (const "unfinished") show $ simp2 m 
"unfinished" 
Main> either (const "unfinished") show $ simp2 n 
"3" 

你需要一個單獨的簡化函數用於每個元素,這個問題最容易由Template Haskell修復。

在Haskell中使用參數列表(相對於列表參數)往往非常尷尬,因爲多個結果都有不同的類型,並且對可變數量的不同類型參數的集合的支持很少。我見過的解決方案的三大類:

  1. 每個案例 明確代碼分開(迅速成爲了很多 工作)。

  2. 模板Haskell。

  3. 類型系統hackery。

我的回答大多是試圖讓1痛苦少交易。 2和3不適合心臟病。

編輯:事實證明,有關於這個問題的Hackage上有somepackages。使用「iteratee」:

import qualified Data.Iteratee as It 
import Control.Applicative 

magic4 f = f <$> It.head <*> It.head <*> It.head <*> It.head 

liftedQuadsum = magic4 quadsum 
-- liftedQuadsum is an iteratee, which is essentially an accumulating function 
-- for a list of data 

Main> p <- It.enumChunk (It.Chunk [1]) liftedQuadsum 
Main> It.run p 
*** Exception: EofException 
Main> q <- It.enumChunk (It.Chunk [2,3,4]) p 
Main> It.run q 
10 

但「iteratee」和「枚舉器」可能矯枉過正。

+0

謝謝。這有助於清理很多。 – Ishpeck 2010-09-23 18:46:47

+0

'在第一種情況下,結果需要兩個參數' - 如果這不是'需要一個參數' - '4 3 2'需要'1'? – 2017-12-29 16:36:02

1

不工作我想。 (((quadsum 4) 3) 2)具有不同的類型Intger -> Integer,例如具有Integer類型的((((quadsum 4) 3) 2) 1)

7

我認爲你誤解了Haskell類型系統。

首先,你的「quadsum」函數已經被curried了。您可以編寫「quadsum 4 3」並獲取期望2和1作爲額外參數的函數。當寫上「(((((quadsum 4)3)2)1)」的「quadsum 4 3 2 1」時。

在Haskell中,整數列表具有與「(4,3,2,1)」等整數或元組不同的類型。鑑於此,要了解你正在嘗試做什麼是相當困難的。如果你寫這個,應該發生什麼?

magicalFunctionMaker quadsum [5,4,3,2,1] 

您的「magicalFunctionMaker」看起來更像「foldl」,只不過您給foldl的函數只有兩個參數。所以你可以寫:

mySum = foldl (+) 0 

這將返回一個函數,該函數將列表和元素相加。

(順便說一句,一旦你神交此,瞭解與foldl和foldr相似的區別

編輯:

有重讀你的問題,我想你想得:

magicalFunctionMaker quadSum [4,3,2,1] :: Integer 
magicalFunctionMaker quadSum [4,3,2] :: Integer -> Integer 

這是不可能的,因爲magicalFunctionMaker的類型取決於列表參數的長度,這意味着動態類型化。正如有人所說,多變量函數可以做某些事情(儘管沒有列表參數),但這需要歸仁幾個毫克型的hackery。

+0

它不會意味着動態打字;這意味着*依賴打字*。 – 2015-10-08 21:07:14

2

你甚至不能「手動」列出的情況下對不同長度:

mf f [] = f 
mf f [x] = f x 
mf f [x,y] = f x y 

--Occurs check: cannot construct the infinite type: t = t1 -> t 
--Probable cause: `f' is applied to too many arguments 
--In the expression: f x 
--In the definition of `mf': mf f [x] = f x 

也就是說,MF不能把任意「元數」,你必須決定一個功能。出於同樣的原因,您不能將任意列表轉換爲元組:您不能說元組將擁有多少元素,但類型系統需要知道。

通過使用「forall」(參見http://www2.tcs.ifi.lmu.de/~abel/fomega/hm.html),可以通過將f限制爲遞歸類型a = a→a來解決這個問題。然而,我無法得到它的工作(看來我必須告訴Leksah在某處使用標誌-XRankNTypes),而對f的限制會讓你的功能變得毫無用處。

[編輯]

一下,最接近你想要的思考可能是某種減少功能。正如Paul指出的,這與foldl,foldr類似(但是下面的版本沒有「額外論證」並且與foldl相比具有同質類型。注意空列表的缺失基本情況)

mf :: (a → a → a) -> [a] -> a 
mf f [x] = x 
mf f (x:y:xs) = mf f ((f x y) : xs) 
3

我碰到了同樣的問題:我有一個像

someFunc :: Int -> Int -> Int -> Int 

功能我很樂意做的就是一個神奇的功能,使得例如

listApply :: [Int] -> (Int -> Int -> Int -> Int) -> Int 

這樣我可以說

listApply [1,2,3] someFunc 

本能地看來,約翰的回答同意,它應該是可能的o做一些類型系統魔術爲了做到這一點。有類似問題的解決方案涉及通過一系列明確的滾動和展開(例如,參見類型和編程語言的第20章或this thread中的第4篇文章)來顯式提供iso-recursive數據類型。

我在類型解決方案上砍了一段時間;感覺可能,但在決定嘗試模板Haskell之前,我並沒有完全理解它,而且事情變得更友好。

{-# LANGUAGE TemplateHaskell #-}  

import Language.Haskell.TH 
import Language.Haskell.TH.Syntax 

lApply :: [Int] -> String -> ExpQ 
lApply [] fn = return $ VarE (mkName fn) 
lApply (l:ls) fn = [| $(lApply ls fn) l |] 

(記得要使用的語言編譯或-XTemplateHaskell命令行開關。)

要使用這個,你叫lApply接頭內,像這樣:

> $(lApply [1,2] "+") 
3 

請注意,我有要使用包含我想調用的函數名稱的字符串:我無法直接將函數提升到ExpQ,但我可以引用其全局綁定。我可以看到這可能會讓人討厭。另外,由於我們遍歷列表的方式,參數必須在列表中以相反順序呈現。

還有一些其他的皺紋:爲了將其推廣到其他數據類型,這些類型必須在Lift類中具有相應的實例。例如,雙沒有實例,但你可以很容易地使一個:

instance Lift Double where 
     lift x = return $ LitE (RationalL (toRational x)) 

點燃的數據類型不具有DoubleL構造,但RationalL可以在它的地方使用,因爲它會以拼接的一般成員分數類。

如果您希望將此類功能與混合類型作爲參數一起使用,您將無法傳遞列表,因爲列表不能是混合類型。你可以使用元組來做到這一點,使用模板哈斯克爾實際上並不困難。在這種情況下,你需要創建一個函數來生成一個函數的AST,該函數接受一個元組中包含適當類型的元組,並將其映射到你想要的函數調用。或者,你可以將你的參數類型包裝在適當製作的ADT中,順便說一下,你也可以使用Template Haskell創建。這是作爲練習留給讀者:)

最後,所有的標準模板Haskell限制適用。例如,由於GHC階段限制,您無法從定義該模塊的模塊調用該函數。

模板Haskell是有趣和有趣的東西,但要完全誠實的iso-recursive數據類型解決方案可能是一個更高的性能,並且顯然不需要額外使用TH。我會回來後,如果/當我得到那個工作後發佈:)

1

我要編輯我的另一篇文章,但這是足夠大的自己。

下面是使用「類型魔法」進行處理的一種方法,但是我覺得它有點不太理想,因爲它需要特定於特定數量參數的函數的提升函數(詳見下文)。

讓我們首先定義一個遞歸的數據類型

data RecT a = RecR a 
      | RecC (a -> RecT a) 

所以RECT類型變量可以是隻是包着一個結果(RECR),也可以是一個不斷遞歸(RECC)。

現在,我們如何採取措施並將其轉換爲RecT a類型?

價值觀很簡單:

valRecT x = RecR x 

RECR x是類型RECT一個顯然。

如何使用一個參數,如id?

idRecT x = RecC $ \x -> RecR x 

RecC包裝一個函數,該函數接受一個變量並返回類型RecT a。表達

\x -> RecR x 

就是這樣一個功能,因爲當我們觀察到之前RECR x是類型RECT一個的。

更一般地,任何一個參數的功能,可以解除:

lift1RecT :: (a -> a) -> RecT a 
lift1RecT fn = RecC $ \a -> RecR $ fn a 

我們可以通過反覆包裝更深層嵌套函數概括這個調用裏面RECC:

lift2RecT :: (a -> a -> a) -> RecT a 
lift2RecT fn = RecC $ \b -> RecC $ \a -> RecR $ fn b a 

lift3RecT :: (a -> a -> a -> a) -> RecT a 
lift3RecT fn = RecC $ \c -> RecC $ \b -> RecC $ \a -> RecR $ fn c b a 

好了,我們已經完成了所有這些工作以將任意數量的參數的函數轉換爲單一類型RecT a。我們如何使用它?

我們可以很容易地寫下來的功能應用一層:

reduceRecT :: RecT a -> a -> RecT a 
reduceRecT (RecC fn) = fn 
reduceRecT _  = undefined 

換句話說,reduceRecT需要類型的類型RECT一個的參數和另一併返回一個新RECT一個這一直是降低一個級別。

我們也可以展開一個矩形內完成計算到結果:

unrollRecT :: RecT a -> a 
unrollRecT (RecR fn) = fn 
unrollRecT _  = undefined 

現在,我們已經準備好參數列表應用到的功能!

lApply :: [a] -> RecT a -> a 
lApply [] fn = unrollRecT fn 
lApply (l:ls) fn = lApply ls $ (reduceRecT fn) l 

讓我們先考慮一下基本情況:如果我們完成了計算,我們只是打開結果並返回它。在遞歸的情況下,我們將參數列表減1,然後通過將列表頭部應用於簡化的fn來轉換fn,從而產生新的RecT a。

讓這給一試:

lApply [2,5] $ lift2RecT (**) 
> 32.0 

所以,優點和這種方法的缺點是什麼?那麼,模板Haskell版本可以執行部分​​列表應用程序;這不是真正的這裏提出的isorecursive類型的解決方案(雖然我們原則上可以用一些醜陋來解決這個問題)。類型解決方案還有一個缺點:有更多的樣板代碼與其相關聯:我們需要一個listNRecT用於我們想要使用的所有N.最後,如果我們希望將其應用於混合變量類型的函數,那麼將其推廣到類比元組解決方案並不那麼容易。

當然,另一個有趣的可能性是通過使用Template Haskell來生成listNRecT函數來增強簡潔性;這消除了一些樣板,但從某種意義上來說,這兩種實現都有缺點。

相關問題