簡體   English   中英

在Haskell中理解作為應用的函數

[英]Understanding Functions as Applicatives in Haskell

我最近一直在嘗試通過“向您學習Haskell”來學習Haskell,並且一直在努力理解作為Applicatives的功能。 我應該指出,使用其他類型的Applicatives(例如List)和Maybe我似乎已經足夠了解,可以有效地使用它們。

正如我在嘗試理解某些事物時通常會做的那樣,我會嘗試使用盡可能多的示例,並且一旦模式出現,事情就會變得有意義。 因此,我嘗試了一些示例。 隨附的注釋是我嘗試的幾個示例以及繪制的圖表以可視化正在發生的情況的圖表。

在此處輸入圖片說明

funct的定義似乎與結果無關,但是在我的測試中,我使用了具有以下定義的函數:

funct :: (Num a) => a -> a -> a -> a

在底部,我嘗試使用普通的數學符號顯示與圖中相同的內容。

因此,所有這些都很好,當我具有任意數量的參數的函數(盡管需要2個或更多)時,我可以理解模式,並將其應用於采用一個參數的函數。 但是從直覺上來說,這種模式對我來說沒有太大意義。

所以這是我有的具體問題:

理解我所看到的模式的直觀方式是什么,特別是如果我將Applicative視為容器(這就是我查看Maybe和列表的方式)的時候?

<*>右邊的函數接受多個參數時(我主要使用右邊的(+3)(+5)函數(+5) ,該模式是什么?

為什么<*>右側的函數應用於左側函數的第二個自變量。 例如,如果右邊的函數是f()那么funct(a,b,c)變成funct (x, f(x), c)

為什么它對funct <*> (+3)無效,但對funct <*> (+)無效? 而且它確實適用於(\\ ab -> 3) <*> (+)

任何使我對這個概念有更好的直觀理解的解釋將不勝感激。 我讀過其他說明,例如在我提到的書中,它以((->)r)或類似模式解釋了函數,但是即使我知道在定義函數時如何使用->運算符,我也不確定在這種情況下理解它。

額外詳情:

我還想包括用來幫助我形成以上圖表的實際代碼。

首先,如上所示,我定義了funct:

funct :: (Num a) => a -> a -> a -> a

在整個過程中,我以各種方式完善了功能,以了解正在發生的事情。

接下來,我嘗試了以下代碼:

funct a b c = 6 
functMod =  funct <*> (+3)
functMod 2 3

毫不奇怪,結果是6

所以現在我嘗試像這樣直接返回每個參數:

funct a b c = a
functMod =  funct <*> (+3)
functMod 2 3 -- returns 2

funct a b c = b
functMod =  funct <*> (+3)
functMod 2 3 -- returns 5

funct a b c = c
functMod =  funct <*> (+3)
functMod 2 3 -- returns 3

由此,我可以確認第二張圖正在發生。 我也重復了這種模式以觀察第三張圖(這是第二次在頂部擴展的相同模式)。

如果將其定義替換為一些示例,通常可以了解函數在Haskell中的作用。 您已經有一些示例,所需的定義是(->) a <*> ,這是這樣的:

(f <*> g) x = f x (g x)

我不知道您是否會發現比僅使用幾次定義更好的直覺。

在您的第一個示例中,我們得到以下信息:

  (funct <*> (+3)) x
= funct x ((+3) x)
= funct x (x+3)

(由於沒有其他參數,我無法使用funct <*> (+3)進行任何操作,因此我將其應用於x在需要時隨時執行此操作。)

其余的:

  (funct <*> (+3) <*> (+5)) x
= (funct x (x+3) <*> (+5)) x
= funct x (x+3) x ((+5) x)
= funct x (x+3) x (x+5)

  (funct <*> (+)) x
= funct x ((+) x)
= funct x (x+)

請注意,你不能使用相同的funct與這兩個-第一它可以采取四個數字,但在第二,它需要采取一些和功能。

  ((\a b -> 3) <*> (+)) x
= (\a b -> 3) x (x+)
= (\b -> 3) (x+)
= 3

  (((\a b -> a + b) <*> (+)) x
= (\a b -> a + b) x (x+)
= x + (x+)
= type error

可以將函數monad作為容器查看。 請注意,對於每個參數類型,它實際上都是一個單獨的monad,因此我們可以選擇一個簡單的示例: Bool

type M a = Bool -> a

這相當於

data M' a = M' { resultForFalse :: a
               , resultForTrue :: a  }

實例可以定義

instance Functor M where            instance Functor M' where
  fmap f (M g) = M g'                 fmap f (M' gFalse gTrue) = M g'False g'True
   where g' False = f $ g False        where g'False = f $ gFalse
         g' True  = f $ g True               g'True  = f $ gTrue

ApplicativeMonad相似。

當然,對於具有多個可能值的參數類型,這種詳盡的案例列表定義將變得完全不切實際,但這始終是相同的原理。

但重要的是,實例始終是特定於某個特定參數的 因此, Bool -> IntBool -> String屬於同一個monad,但是Int -> IntChar -> Int不屬於同一個單子。 Int -> Double -> Int不屬於同一個單子的Int -> Int ,但是只有當你考慮Double -> Int為具有無關的不透明結果類型Int->單子。

因此,如果您正在考慮類似a -> a -> a -> a a-> a- a -> a -> a -> a那么這實際上不是關於應用程序/單子的問題,而是關於Haskell的一般問題。 因此,您不應該期望monad = container圖片可以帶您到任何地方。 要了解a -> a -> a -> a a- a -> a -> a -> a a- a -> a -> a -> a作為monad的成員,您需要挑選出您正在談論的箭頭; 在這種情況下,它只是最左邊的一個,即您在type M=(a->) monad中具有值M (a->a->a) a->a->a之間的箭頭不以任何方式參與單子動作。 如果它們在您的代碼中包含,則意味着您實際上是在混合多個monad。 在執行此操作之前,您應該了解單個monad的工作原理,因此請堅持僅使用單個功能箭頭的示例。

正如David Fletcher指出的 ,函數的(<*>)是:

(g <*> f) x = g x (f x)

有兩張直觀的(<*>)函數圖片,盡管不能完全阻止它令人目眩,但當您瀏覽使用它的代碼時,它們可能有助於保持平衡。 在接下來的幾段中,我將使用(+) <*> negate作為運行示例,因此您可能需要在GHCi中嘗試幾次,然后再繼續。

第一張圖片是(<*>)是將一個函數的結果應用於另一個函數的結果:

g <*> f = \x -> (g x) (f x)

例如, (+) <*> negate將參數傳遞給(+)negate ,分別給出一個函數和一個數字,然后將一個應用於另一個...

(+) <*> negate = \x -> (x +) (negate x)

...解釋了為什么其結果始終為0

第二張圖片是(<*>)作為函數組成的變體,其中該參數還用於確定將要組成的第二個函數

g <*> f = \x -> (g x . f) x

從這個角度來看, (+) <*> negate否定參數,然后將參數添加到結果中:

(+) <*> negate = \x -> ((x +) . negate) x

如果您有一個funct :: Num a => a -> a -> a -> a funct <*> (+3) funct :: Num a => a -> a -> a -> afunct <*> (+3)之所以起作用是因為:

  • 就第一張圖片而言: (+ 3) x是一個數字,因此您可以對其應用funct x ,最后得到funct x ((+ 3) x) ,該函數需要兩個參數。

  • 就第二張圖片而言: funct x是一個帶有數字的函數(類型為Num a => a -> a -> a ),因此您可以使用(+ 3) :: Num a => a -> a

另一方面,使用funct <*> (+) ,我們有:

  • 就第一張圖片而言: (+) x不是數字,而是Num a => a -> a函數,因此您無法對其應用funct x

  • 就第二張圖片而言: (+)的結果類型,被視為一個參數( (+) :: Num a => a -> (a -> a) Num a => a -> a (+) :: Num a => a -> (a -> a)Num a => a -> a ,為Num a => a -> a (而不是Num a => a ),因此您不能將它與funct x (期望使用Num a => a )。

對於可以將(+)作為(<*>)的第二個參數的東西的任意示例,請考慮以下函數iterate

iterate :: (a -> a) -> a -> [a]

給定一個函數和一個初始值, iterate通過重復應用該函數來生成無限列表。 如果我們將參數翻轉為iterate ,我們將得到:

flip iterate :: a -> (a -> a) -> [a]

鑒於funct <*> (+)的問題在於funct x不會使用Num a => a -> a函數,這似乎具有合適的類型。 確實:

GHCi> take 10 $ (flip iterate <*> (+)) 1
[1,2,3,4,5,6,7,8,9,10]

(在切向音符上,如果使用(=<<)而不是(<*>) ,則可以省略flip但是,這是另一回事了 。)


最后,兩個直觀的圖片都不適合應用樣式表達的常見用例,例如:

(+) <$> (^2) <*> (^3)

要在其中使用直觀的圖片,您必須考慮函數(.) (<$>)如何,這會使事情變得相當混亂。 將整個事情看作是提升后的應用程序會更容易:在此示例中,我們將(^ 2)和(^ 3)的結果相加。 等同於...

liftA2 (+) (^2) (^3)

...在某種程度上強調了這一點。 但是,就我個人而言,我覺得在此設置中編寫liftA2一個可能的缺點是,如果在同一表達式中正確應用結果函數,則最終會出現類似...

liftA2 (+) (^2) (^3) 5

...並且看到跟着三個參數的liftA2會使我的大腦傾斜。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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