簡體   English   中英

Haskell:instance Functor ((->) r) 有什么意義

[英]Haskell : what is the sense in: instance Functor ((->) r)

我承認,我的問題可能源於缺乏知識並且相當模糊。 但我試圖理解,有一些疑問,無法解決。

所以 GHC.Base 有這樣的定義,它的意義是什么:

instance Functor ((->) r) where
    fmap = (.)
  1. 從編程語言的角度來看:我們有真正的基礎構造(->),我認為基礎比任何東西都重要,但可能是術語,你將其描述為非常衍生的構造(實例 Functor)的一部分。 意義何在? (->) 是 (->)。 就 Haskell 中描述的 (->) 而言,函子有意義。 但反之亦然:(->) 有意義,而 Functor 在 Haskell 庫中正確描述。

  2. 從 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 -> aTwoF 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 = idindexX. tabulateX = id indexX. tabulateX = id每個X的 id,即制表實際上是索引的倒數。

好的,但現在稍等一下,看看我們剛剛做了什么:我們已經將function (如Two -> a )變成了一個容器(如TwoF a ),反之亦然。 從道德上講,類型Two -> aTwoF 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

如果您對zeromaponemapthreemap進行類似推理,您會發現它們實際上都具有相同的實現; 我們可以通過多態性對所有不同大小的容器統一執行此操作; 與其使用onemap來更改One -> a等,不如使用xmap來更改x -> a

xmap :: (a -> b) -> (x -> a) -> (x -> b)
xmap f ix = f . ix

當然,我們不必命名fix

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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM