[英]Haskell : what is the sense in: instance Functor ((->) r)
我承認,我的問題可能源於缺乏知識並且相當模糊。 但我試圖理解,有一些疑問,無法解決。
所以 GHC.Base 有這樣的定義,它的意義是什么:
instance Functor ((->) r) where
fmap = (.)
從編程語言的角度來看:我們有真正的基礎構造(->),我認為基礎比任何東西都重要,但可能是術語,你將其描述為非常衍生的構造(實例 Functor)的一部分。 意義何在? (->) 是 (->)。 就 Haskell 中描述的 (->) 而言,函子有意義。 但反之亦然:(->) 有意義,而 Functor 在 Haskell 庫中正確描述。
從 lambda 微積分的角度來看: 2.1 如果從“常識”定義“(->) r”是 r 周圍的容器(我們稱之為“Any_f”),那么 function fmap 應該如何工作? fmap 應該把value 改成container,但是container-structure 不改,盡量寫。
fmap f (Any_f x) <=> Any_f (fx)
(是的,這是非類型化的 lambda 微積分)
2.2. 但是我們看看Functor((->) r)在Haskell中是怎么定義的:
instance Functor ((->) r) where
fmap = (.)
-- Or in other words (quotation marks intentionaly):
-- fmap f (Any_f x) = f (Any_f x)
-- fmap :: forall a, b, c => (b -> c) -> (a -> b) -> (a -> c)
所以:
“常識”(不改變容器結構)告訴我們寫:
fmap f (Any_f_as_container x) = Any_f_as_container (fx)
類型要求告訴我們寫:
fmap f (any_f_as_container x) = f (any_f_as_container x)
這不是說“instance Functor ((->) r)”沒有意義嗎? 如果不是——當它改變最外層的 function(容器本身,而不是容器值)時,它有什么意義?
我將嘗試說服您fmap = (.)
確實是使容器的形狀保持不變的東西,但將 function 應用於容器中的所有元素。 但在我們為(->)
做這件事之前,讓我們為一些更簡單的類型做這件事。 具體來說,讓我們針對作為具有特定數量元素的容器的類型執行此操作——即,恰好包含兩個元素的容器將是TwoF
,而沒有元素的容器將是ZeroF
。 像這樣:
data ZeroF a = ZeroF
data OneF a = OneF a
data TwoF a = TwoF a a
data ThreeF a = ThreeF a a a
這些的Functor
實例應該是什么樣的? 好吧, OneF
的那個看起來和你的問題完全一樣:
instance Functor OneF where
fmap f (OneF x) = OneF (f x)
其他的看起來非常相似——只是應用f
更多(或更少)次來說明元素更多(或更少)的事實。 它們都在這里,有一些創造性的空白來突出相似之處/模式:
instance Functor ZeroF where fmap f (ZeroF ) = ZeroF
instance Functor OneF where fmap f (OneF x0 ) = OneF (f x0)
instance Functor TwoF where fmap f (TwoF x0 x1 ) = TwoF (f x0) (f x1)
instance Functor ThreeF where fmap f (ThreeF x0 x1 x2) = ThreeF (f x0) (f x1) (f x2)
希望現在您同意這肯定具有您在問題中描述的Functor
實例的味道:保持容器的形狀相同,並將給定的 function f
應用於其中包含的每個元素。
所以那些是具有給定數量元素的容器。 現在,讓我們為這些容器編寫訪問器——即我們想要列表的(!!)
等價物,在給定數字的情況下,我們從容器中提取該字段。 由於ZeroF
中有零個元素,我們需要一個零值的索引類型; 而對於ThreeF
,我們需要一個具有三個值的索引類型。
data Zero
data One = One0
data Two = Two0 | Two1
data Three = Three0 | Three1 | Three2
索引函數的類型如下所示:
indexZero :: ZeroF a -> Zero -> a
indexOne :: OneF a -> One -> a
indexTwo :: TwoF a -> Two -> a
indexThree :: ThreeF a -> Three -> a
我不會全部實現它們——它們非常簡單——但這里有一個可以給你一個想法,以防它不是很明顯。
indexTwo (TwoF x0 x1) Two0 = x0
indexTwo (TwoF x0 x1) Two1 = x1
事實證明,索引函數有一個反函數——如果你給我一個 function,當給定一個索引時,它會產生一個值,然后我可以給你一個包含這些值的容器。 類型如下所示:
tabulateZero :: (Zero -> a) -> ZeroF a
tabulateOne :: (One -> a) -> OneF a
tabulateTwo :: (Two -> a) -> TwoF a
tabulateThree :: (Three -> a) -> ThreeF a
(你明白為什么這是逆的正確類型嗎?請注意,比如說, TwoF a -> Two -> a
與TwoF a -> (Two -> a)
是同一類型,)只是給你一個想法這些是如何實現的,以防它不是很明顯:我們只需將索引 function 應用於每個索引:
tabulateZero ix = ZeroF
tabulateOne ix = OneF (ix One0 )
tabulateTwo ix = TwoF (ix Two0 ) (ix Two1 )
tabulateThree ix = ThreeF (ix Three0) (ix Three1) (ix Three2)
證明tabulateX. indexX = id
tabulateX. indexX = id
和indexX. tabulateX = id
indexX. tabulateX = id
每個X
的 id,即制表實際上是索引的倒數。
好的,但現在稍等一下,看看我們剛剛做了什么:我們已經將function (如Two -> a
)變成了一個容器(如TwoF a
),反之亦然。 從道德上講,類型Two -> a
和TwoF a
是完全相同的東西。 因此,認為我們可以為Two -> a
實現fmap
似乎是合理的——例如,只需適當地轉換為TwoF a
並返回!
twomap :: (a -> b) -> (Two -> a) -> (Two -> b)
twomap f = indexTwo . fmap f . tabulateTwo
讓我們想象一下它在做什么。 我們將從任意索引 function 開始:
\case Two0 -> x0; Two1 -> x1
現在我們go通過流程:
\case Two0 -> x0; Two1 -> x1
tabulateTwo
TwoF x0 x1
fmap f
TwoF (f x0) (f x1)
indexTwo
\case Two0 -> f x0; Two1 -> f x1
由於f
在兩個分支中都應用,我們可以將其從case
中提取出來:
f . (\case Two0 -> x0; Two1 -> x1)
第二項正是我們開始時使用的索引 function。 換句話說,我們剛剛確定了twomap
的另一個更簡單的實現:
twomap f ix = f . ix
如果您對zeromap
、 onemap
和threemap
進行類似推理,您會發現它們實際上都具有相同的實現; 我們可以通過多態性對所有不同大小的容器統一執行此操作; 與其使用onemap
來更改One -> a
等,不如使用xmap
來更改x -> a
:
xmap :: (a -> b) -> (x -> a) -> (x -> b)
xmap f ix = f . ix
當然,我們不必命名f
和ix
:
xmap = (.)
...這是(x -> _)
的Functor
實例。
(->)
不僅僅是語法。 它是一個與任何其他運算符一樣的運算符,但在類型級別而不是術語級別。 它有一個種類Type -> Type -> Type
,這意味着如果你將它應用到一個單一的類型,你得到的不是一個類型,而是另一個種類Type -> Type
的“函數”。
Type -> Type
是所有函子的一種,因此有理由認為部分應用的(->)
運算符也可能是函子,這就是
instance Functor ((->) r) where
fmap = (.)
定義。 也就是說,將一個 function 映射到另一個 function 意味着組合這兩個函數。
作為“容器”,將 function(類型為r -> a
東西)視為包含類型a
的所有可能值,您可以通過將 function 應用於類型r
的參數來獲得這些值。 fmap
會將 function 應用於其他 function 返回的任何內容。 (或者在理論上,將它應用於另一個可以返回的每個值。)
所以答案是:函數可以表示為(a -> b)
或Map ab
- 對於 function 和可能的有限計數 arguments 這些實際上是兩個等價表示。
所以instance Functor (Map r)
是有意義的,它將像instance Functor ((->) r)
已經實現的那樣實現。
上面的答案已通過instance Functor ((,) r)
的實現得到證實。 是的,這與Map r
有點不同,但盡可能接近。
PS @chepner:我不能將你的回答標記為“最佳答案”,因為我不理解(並且幾乎不同意)一個句子中的一個詞:
(->) 不僅僅是語法。 它和其他操作員一樣
Function 不是“像任何其他”操作(我使用概念“構造”) function 是神奇的或底層編譯器構造,所有其他功能都基於它。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.