[英]Why does join . (flip fmap) have type ((A -> B) -> A) -> (A -> B) -> B?
有些人在ghci玩弄着仿函數和monad,這讓我得到了一個我希望更好理解其類型和行為的價值。
\\x -> join . x
的類型\\x -> join . x
\\x -> join . x
是(Monad m) => (a -> m (mb)) -> (a -> mb)
和\\y -> y . (flip fmap)
的類型\\y -> y . (flip fmap)
\\y -> y . (flip fmap)
是(Functor f) => ((a -> b) -> fb) -> (fa -> c)
。
ghci版本8.2.2允許定義h = join . (flip fmap)
h = join . (flip fmap)
。
為什么
h
有類型((A -> B) -> A) -> (A -> B) -> B
?
特別是,為什么仿函數和monad約束消失了? 這真的是正確和預期的行為嗎? 作為后續行動,我還想問:
為什么評估整數
u
和v
h (\\f -> fu) (\\x -> x + v)
在每種情況下給出u + 2v
?
簡而言之 :由於類型推導,Haskell知道m
和f
實際上是部分實例化的箭頭。
好吧,讓我們做數學。 函數join . (flip fmap)
join . (flip fmap)
基本上是你給定的lambda表達式\\x -> join . x
\\x -> join . x
帶有參數(flip fmap)
,所以:
h = (\x -> join . x) (flip fmap)
現在lambda表達式有類型:
(\x -> join . x) :: Monad m => (a -> m (m b)) -> (a -> m b)
現在參數flip fmap
有類型:
flip fmap :: Functor f => f c -> ((c -> d) -> f d)
(我們這里使用c
和d
而不是a
和b
來避免兩種可能不同類型之間的混淆)。
這意味着flip fmap
的類型與lambda表達式的參數類型相同,因此我們知道:
Monad m => a -> m (m b)
~ Functor f => f c -> ((c -> d) -> f d)
---------------------------------------
a ~ f c, m (m b) ~ ((c -> d) -> f d)
所以我們現在知道a
與fc
具有相同的類型(這是波浪號的含義~
)。
但我們必須做一些額外的計算:
Monad m => m (m b)
~ Functor f => ((c -> d) -> f d)
--------------------------------
m ~ (->) (c -> d), m b ~ f d
因此我們知道m
與(->) (c -> d)
(基本上這是一個我們知道輸入類型的函數,這里(c -> d)
,輸出類型是m
的類型參數。
所以這意味着mb ~ (c -> d) -> b ~ fd
,所以這意味着f ~ (->) (c -> d)
和b ~ d
。 另外一個結果是,因為a ~ fc
,我們知道a ~ (c -> d) -> c
所以列出我們得到的東西:
f ~ m
m ~ (->) (c -> d)
b ~ d
a ~ (c -> d) -> c
所以我們現在可以“專門化”我們的lambda表達式和我們的flip fmap
函數的類型:
(\x -> join . x)
:: (((c -> d) -> c) -> (c -> d) -> (c -> d) -> d) -> ((c -> d) -> c) -> (c -> d) -> d
flip fmap
:: ((c -> d) -> c) -> (c -> d) -> (c -> d) -> d
和flip fmap
的類型現在完全匹配lambda表達式的參數類型。 所以(\\x -> join . x) (flip fmap)
的類型(\\x -> join . x) (flip fmap)
是lambda表達式類型的結果類型,即:
(\x -> join . x) (flip fmap)
:: ((c -> d) -> c) -> (c -> d) -> d
但是現在我們當然還沒有獲得這個功能的實現。 然而,我們已經向前邁進了一步。
既然我們現在知道m ~ (->) (c -> d)
,我們知道應該查找monad的箭頭實例 :
instance Monad ((->) r) where f >>= k = \\ r -> k (fr) r
因此對於給定函數f :: r -> a
,作為左操作數,以及函數k :: a -> (r -> b) ~ a -> r -> b
作為操作數,我們構造一個新的映射函數應用於f
的變量x
到k
應用於x
和x
。 因此,這是一種對輸入變量x
執行某種預處理的方法,然后在考慮預處理和原始視圖的情況下進行處理(這是人類讀者可以使用的解釋)。
現在join :: Monad m => m (ma) -> ma
實現為 :
join :: Monad m => m (ma) -> ma join x = x >>= id
所以對於(->) r
monad,這意味着我們將其實現為:
-- specialized for `m ~ (->) a
join f = \r -> id (f r) r
由於id :: a -> a
(標識函數)返回其參數,我們可以進一步簡化它:
-- specialized for `m ~ (->) a
join f = \r -> (f r) r
或清潔:
-- specialized for `m ~ (->) a
join f x = f x x
所以它基本上給出了一個函數f
,然后將該參數兩次應用於該函數。
此外,我們知道箭頭類型的Functor
實例定義為 :
instance Functor ((->) r) where fmap = (.)
因此它基本上用作函數結果的“后處理器”:我們構造一個新函數,用於使用給定函數進行后處理。
所以現在我們將這個函數專門用於給定的Functor
/ Monad
,我們可以將實現派生為:
-- alternative implementation
h = (.) (\f x -> f x x) (flip (.))
或者使用更多的lambda表達式:
h = \a -> (\f x -> f x x) ((flip (.)) a)
我們現在可以進一步專注於:
h = \a -> (\f x -> f x x) ((\y z -> z . y) a)
-- apply a in the lambda expression
h = \a -> (\f x -> f x x) (\z -> z . a)
-- apply (\z -> z . a) in the first lambda expression
h = \a -> (\x -> (\z -> z . a) x x)
-- cleaning syntax
h a = (\x -> (\z -> z . a) x x)
-- cleaning syntax
h a x = (\z -> z . a) x x
-- apply lambda expression
h a x = (x . a) x
-- remove the (.) part
h a x = x (a x)
所以h
基本上需要兩個參數: a
和x
,然后將其與執行功能的應用程序a
作為函數和x
為參數,輸出被傳遞給x
功能一次。
作為樣本用法,您使用:
h (\f -> f u) (\x -> x + v)
還是更好的:
h (\f -> f u) (+v)
所以我們可以這樣分析:
h (\f -> f u) (+v)
-> (+v) ((\f -> f u) (+v))
-> (+v) ((+v) u)
-> (+v) (u+v)
-> ((u+v)+v)
所以我們將u+v
添加到v
。
使用>>>
可以更輕松地排列類型:
a -> b >>>
b -> c ::
a -> c
在這里,我們有
join . flip fmap == flip fmap >>> join
flip fmap :: Functor f => f a -> ((a -> b) -> f b )
join :: Monad m => (m (m b)) -> m b
----------------------------------------------------------
flip fmap >>> join ::
(Functor f, Monad m) => f a -> m b , ((a -> b) ->) ~ m, f ~ m
::
(Functor f, Monad f) => f a -> f b , f ~ ((a -> b) ->)
:: ((a -> b) -> a) -> ((a -> b) -> b)
簡單,機械,世俗。
要看看會發生什么 ,組合子樣式定義通常是最容易給擺弄,
(join . flip fmap) f g x =
join (flip fmap f) g x = -- join f x = f x x
(`fmap` f) g g x = -- f `fmap` g = f . g
(g . f) g x
g (f g) x
所以我們畢竟不需要x
(或者我們呢?)。 函數的join
和fmap
定義在邊距中給出。 我們到了
(join . flip fmap) f g = g (f g) -- f :: (a -> b) -> a, g :: a -> b
-- f g :: a , g (f g) :: b
另一種方式是從類型開始,按照modus ponens的規則,
((a -> b) -> a) (a -> b) -- f g
---------------------------
(a -> b) a -- g (f g)
---------------------------------------
b
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.