[英]Haskell: composing function with two floating arguments fails
我正在嘗試組合類型為(Floating a) => a -> a -> a
的函數與類型為(Floating a) => a -> a
的函數以獲得類型為(Floating a) => a -> a -> a
的函數(Floating a) => a -> a -> a
。 我有以下代碼:
test1 :: (Floating a) => a -> a -> a
test1 x y = x
test2 :: (Floating a) => a -> a
test2 x = x
testBoth :: (Floating a) => a -> a -> a
testBoth = test2 . test1
--testBoth x y = test2 (test1 x y)
但是,當我在 GHCI 中編譯它時,出現以下錯誤:
/path/test.hs:8:11:
Could not deduce (Floating (a -> a)) from the context (Floating a)
arising from a use of `test2'
at /path/test.hs:8:11-15
Possible fix:
add (Floating (a -> a)) to the context of
the type signature for `testBoth'
or add an instance declaration for (Floating (a -> a))
In the first argument of `(.)', namely `test2'
In the expression: test2 . test1
In the definition of `testBoth': testBoth = test2 . test1
Failed, modules loaded: none.
請注意, testBoth
的注釋版本testBoth
編譯。 奇怪的是,如果我從所有類型簽名中刪除(Floating a)
約束,或者如果我將test1
更改為只使用x
而不是x
和y
, testBoth
編譯。
我搜索了 StackOverflow、Haskell wiki、Google 等,但沒有發現任何關於與此特定情況相關的函數組合限制的信息。 有誰知道為什么會這樣?
\x y -> test2 (test1 x y)
== \x y -> test2 ((test1 x) y)
== \x y -> (test2 . (test1 x)) y
== \x -> test2 . (test1 x)
== \x -> (test2 .) (test1 x)
== \x -> ((test2 .) . test1) x
== (test2 .) . test1
這兩件事彼此不一樣。
test2 . test1
== \x -> (test2 . test1) x
== \x -> test2 (test1 x)
== \x y -> (test2 (test1 x)) y
== \x y -> test2 (test1 x) y
您的問題與Floating
無關,但事實上您想以不進行類型檢查的方式組合一個帶有兩個參數的函數和一個帶有一個參數的函數。 我會給你一個組合函數reverse . foldr (:) []
的例子reverse . foldr (:) []
reverse . foldr (:) []
。
reverse . foldr (:) []
reverse . foldr (:) []
具有[a] -> [a]
類型並按預期工作:它返回一個反向列表( foldr (:) []
本質上是列表的id
)。
然而reverse . foldr (:)
reverse . foldr (:)
不進行類型檢查。 為什么?
讓我們回顧一些類型:
reverse :: [a] -> [a]
foldr (:) :: [a] -> [a] -> [a]
foldr (:) [] :: [a] -> [a]
(.) :: (b -> c) -> (a -> b) -> a -> c
reverse . foldr (:) []
reverse . foldr (:) []
,因為(.)
實例化為:
(.) :: ([a] -> [a]) -> ([a] -> [a]) -> [a] -> [a]
換句話說,在(.)
類型注釋中:
a
變成[a]
b
變成[a]
c
變成[a]
所以reverse . foldr (:) []
reverse . foldr (:) []
的類型為[a] -> [a]
。
reverse . foldr (:)
reverse . foldr (:)
不進行類型檢查,因為:
foldr (:) :: [a] -> [a] -> [a]
作為(.)
的正確操作符,它將實例化其類型從a -> b
到[a] -> ([a] -> [a])
。 也就是說,在:
(b -> c) -> (a -> b) -> a -> c
a
將替換為[a]
b
將替換為[a] -> [a]
。 如果foldr (:)
類型是a -> b
, (. foldr (:))
的類型將是:
(b -> c) -> a -> c`
( foldr (:)
用作(.)
的右操作數)。
但是因為foldr (:)
類型是[a] -> ([a] -> [a])
, (. foldr (:))
的類型是:
(([a] -> [a]) -> c) -> [a] -> c
reverse . foldr (:)
reverse . foldr (:)
不進行類型檢查,因為reverse
的類型為[a] -> [a]
,而不是([a] -> [a]) -> c
!
當人們第一次在 Haskell 中學習函數組合時,他們了解到當函數體的最右邊有函數的最后一個參數時,你可以從參數和函數體中刪除它,替換或括號(或美元符號) ) 帶點。 換句話說,以下 4 個函數定義是等效的:
f a x xs = g ( h a ( i x xs))
f a x xs = g $ h a $ i x xs
f a x xs = g . h a . i x $ xs
f a x = g . h a . i x
所以人們有一種直覺說“我只是從主體和參數中刪除最右邊的局部變量”,但這種直覺是錯誤的,因為一旦你刪除了xs
,
f a x = g . h a . i x
f a = g . h a . i
不等價! 您應該了解函數組合何時進行類型檢查,何時不進行類型檢查。 如果上面的2個是等價的,那么就意味着下面的2個也是等價的:
f a x xs = g . h a . i x $ xs
f a x xs = g . h a . i $ x xs
這是沒有意義的,因為x
不是一個以xs
作為參數的函數。 x
是函數i
的參數, xs
是函數(ix)
的參數。
有一個技巧可以使具有2 個參數的函數無點。 那就是使用“owl”運算符:
f a x xs = g . h a . i x xs
f a = g . h a .: i
where (.:) = (.).(.)
以上兩個函數定義是等價的。 閱讀有關“owl”運算符的更多信息。
一旦你理解了函數、類型、部分應用和柯里化、函數組合和美元運算符,Haskell 編程就會變得更加簡單和直接。 要確定這些概念,請閱讀以下 StackOverflow 答案:
另請閱讀:
您的問題與Floating
沒有任何關系,盡管 typeclass 確實使您的錯誤更難以理解。 以下面的代碼為例:
test1 :: Int -> Char -> Int
test1 = undefined
test2 :: Int -> Int
test2 x = undefined
testBoth = test2 . test1
什么是testBoth的類型? 好吧,我們取(.) :: (b -> c) -> (a -> b) -> a -> c
並轉動曲柄得到:
b ~ Int
( test2
的參數與(.)
的第一個參數統一)c ~ Int
( test2
的結果與(.)
的第一個參數的結果統一)a ~ Int
( test1
參數 1 與(.)
參數 2 統一)b ~ Char -> Int
( test1
結果與(.)
參數 2 統一) 可是等等! 該類型變量 'b' (#4, Char -> Int
) 必須與test2
(#1, Int
) 的參數類型統一。 不好了!
你應該怎么做? 正確的解決方法是:
testBoth x = test2 . test1 x
還有其他方法,但我認為這是最易讀的。
編輯:那么試圖告訴你的錯誤是什么? 據說統一Floating a => a -> a
與Floating b => b
需要一個instance Floating (a -> a)
...雖然這是真的,但你真的不希望 GHC 嘗試將函數視為一個浮點數。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.