簡體   English   中英

組成功能組合:(。)。(。)如何工作?

[英]Composing function composition: How does (.).(.) work?

(.)接受兩個函數,它們接受一個值並返回一個值:

(.) :: (b -> c) -> (a -> b) -> a -> c

因為(.)兩個參數,我覺得(.).(.)應該是無效的,但它完全沒問題:

(.).(.) :: (b -> c) -> (a -> a1 -> b) -> a -> a1 -> c

這里發生了什么? 我意識到這個問題措辭嚴厲......所有的功能都只是因為討論而采取了一個論點。 也許更好的方式來說它是類型不匹配。

讓我們首先使用typechecker進行機械校樣。 我將描述一種直觀的思考方式。

我想將(.)應用於(.) ,然后我將(.)應用於結果。 第一個應用程序幫助我們定義一些變量的等價物。

((.) :: (b -> c) -> (a -> b) -> a -> c) 
      ((.) :: (b' -> c') -> (a' -> b') -> a' -> c') 
      ((.) :: (b'' -> c'') -> (a'' -> b'') -> a'' -> c'')

let b = (b' -> c') 
    c = (a' -> b') -> a' -> c'

((.) (.) :: (a -> b) -> a -> c) 
      ((.) :: (b'' -> c'') -> (a'' -> b'') -> a'' -> c'')

然后我們開始第二次,但很快陷入困境......

let a = (b'' -> c'')

這是關鍵:我們想讓let b = (a'' -> b'') -> a'' -> c'' ,但我們已經定義了b ,所以我們必須嘗試統一 ---以匹配我們最好的兩個定義。 幸運的是,他們確實匹配

UNIFY b = (b' -> c') =:= (a'' -> b'') -> a'' -> c''
which implies 
      b' = a'' -> b''
      c' = a'' -> c''

通過這些定義/統一,我們可以繼續申請

((.) (.) (.) :: (b'' -> c'') -> (a' -> b') -> (a' -> c'))

然后擴大

((.) (.) (.) :: (b'' -> c'') -> (a' -> a'' -> b'') -> (a' -> a'' -> c''))

並清理它

substitute b'' -> b
           c'' -> c
           a'  -> a
           a'' -> a1

(.).(.) :: (b -> c) -> (a -> a1 -> b) -> (a -> a1 -> c)

說實話,這有點違反直覺。


這是直覺。 首先來看看fmap

fmap :: (a -> b) -> (f a -> f b)

它將一個功能“提升”為一個Functor 我們可以反復應用它

fmap.fmap.fmap :: (Functor f, Functor g, Functor h) 
               => (a -> b) -> (f (g (h a)) -> f (g (h b)))

允許我們將函數提升到更深層次的Functors層。

事實證明,數據類型(r ->)是一個Functor

instance Functor ((->) r) where
   fmap = (.)

這應該看起來很熟悉。 這意味着fmap.fmap轉換為(.).(.) 因此, (.).(.)只是讓我們轉換(r ->) Functor的更深層次的參數類型。 (r ->) Functor實際上是Reader Monad ,因此分層Reader就像擁有多種獨立的全局不可變狀態。

或者喜歡有多個不受fmap影響的輸入參數。 類似於在(> 1)arity函數的“只是結果”上組成新的延續函數。


最后值得注意的是,如果你認為這些東西很有意思,它就形成了在控制中衍生鏡片的核心直覺。 鏡頭

讓我們暫時忽略類型,只使用lambda演算。

  • Desugar中綴符號:
    (.) (.) (.)

  • ETA-擴大:
    (\\ ab -> (.) ab) (\\ cd -> (.) cd) (\\ ef -> (.) ef)

  • 內聯(.)的定義:
    (\\ abx -> a (bx)) (\\ cdy -> c (dy)) (\\ efz -> e (fz))

  • 替換a
    (\\ bx -> (\\ cdy -> c (dy)) (bx)) (\\ efz -> e (fz))

  • 替代品b
    (\\ x -> (\\ cdy -> c (dy)) ((\\ efz -> e (fz)) x))

  • 替代e
    (\\ x -> (\\ cdy -> c (dy)) (\\ fz -> x (fz)))

  • 替代c
    (\\ x -> (\\ dy -> (\\ fz -> x (fz)) (dy)))

  • 替代f
    (\\ x -> (\\ dy -> (\\ z -> x (dyz))))

  • Resugar lambda表示法:
    \\ xdyz -> x (dyz)

如果你問GHCi,你會發現它有預期的類型。 為什么? 因為函數箭頭是右關聯的以支持currying:類型(b -> c) -> (a -> b) -> a -> c實際上意味着(b -> c) -> ((a -> b) -> (a -> c)) 同時,類型變量b可以代表任何類型, 包括函數類型 看到連接?

以下是同一現象的一個更簡單的例子:

id :: a -> a
id x = x

id的類型表示id應該采用一個參數。 事實上,我們可以用一個參數來稱呼它:

> id "hello" 
"hello"

但事實證明我們也可以用兩個參數來稱呼它:

> id not True
False

甚至:

> id id "hello"
"hello"

到底是怎么回事? 理解id not True的關鍵是先看看id not 顯然,這是允許的,因為它將id應用於一個參數。 not Bool -> Bool的類型Bool -> Bool ,所以我們知道來自id類型的a應該是Bool -> Bool ,所以我們知道這種id的出現有類型:

id :: (Bool -> Bool) -> (Bool -> Bool)

或者,括號較少:

id :: (Bool -> Bool) -> Bool -> Bool

所以這種id的出現實際上需要兩個參數

同樣的推理也適用於id id "hello"(.) . (.) (.) . (.)

這是一個巧妙的案例,我認為首先掌握更一般的案例更簡單,然后考慮具體案例。 那么讓我們考慮一下仿函數。 我們知道仿函數提供了一種在結構上映射函數的方法 -

class Functor f where
  fmap :: (a -> b) -> f a -> f b

但是如果我們有兩層仿函數呢? 例如,列表列表? 在這種情況下,我們可以使用兩層fmap

>>> let xs = [[1,2,3], [4,5,6]]
>>> fmap (fmap (+10)) xs
[[11,12,13],[14,15,16]]

但是這種模式f (gx)是完全一樣的(f . g) x ,所以我們可以寫

>>> (fmap . fmap) (+10) xs
[[11,12,13],[14,15,16]]

什么是fmap . fmap的類型fmap . fmap fmap . fmap

>>> :t fmap.fmap
  :: (Functor g, Functor f) => (a -> b) -> f (g a) -> f (g b)

我們看到它根據我們的需要映射了兩層仿函數。 但是現在記住(->) r是一個仿函數(來自r的函數類型,您可能更喜歡將其讀作(r ->) )並且它的仿函數實例是

instance Functor ((->) r) where
  fmap f g = f . g

對於一個函數, fmap只是函數組合! 當我們fmap兩個fmap我們映射函數functor的兩個級別。 我們最初有一些類型(->) s ((->) ra) ,這相當於s -> r -> a ,我們最終得到類型為s -> r -> b ,所以類型(.).(.)必須是

(.).(.) :: (a -> b) -> (s -> r -> a) -> (s -> r -> b)

它接受第一個函數,並用它來轉換第二個(雙參數)函數的輸出。 例如,函數((.).(.)) show (+)是兩個參數的函數,首先將其參數加在一起,然后使用show將結果轉換為String

>>> ((.).(.)) show (+) 11 22
"33"

例如,考慮更長的fmapfmap了自然的概括

fmap.fmap.fmap ::
  (Functor f, Functor g, Functor h) => (a -> b) -> f (g (h a)) -> f (g (h b))

它映射三層仿函數,相當於用三個參數的函數組合:

(.).(.).(.) :: (a -> b) -> (r -> s -> t -> a) -> (r -> s -> t -> b)

例如

>>> import Data.Map
>>> ((.).(.).(.)) show insert 1 True empty
"fromList [(1,True)]"

它將值True插入到帶有鍵1的空映射中,然后將輸出轉換為帶有show的字符串。


這些函數通常很有用,因此您有時會將它們定義為

(.:) :: (a -> b) -> (r -> s -> a) -> (r -> s -> b)
(.:) = (.).(.)

這樣你就可以寫了

>>> let f = show .: (+)
>>> f 10 20
"30"

當然,可以給出一個更簡單,有點的(.:)定義

(.:) :: (a -> b) -> (r -> s -> a) -> (r -> s -> b)
(f .: g) x y = f (g x y)

這可能有助於神秘化(.).(.)

你是對的, (.)只有兩個參數。 你似乎只對haskell的語法感到困惑。 在表達式(.).(.) ,它實際上是中間的點,它將另外兩個點作為參數,就像在表達式100 + 200 ,可以寫成(+) 100 200

(.).(.) === (number the dots)
(1.)2.(3.) === (rewrite using just syntax rules)
(2.)(1.)(3.) === (unnumber and put spaces)
(.) (.) (.) ===

並且應該更清楚地從(.) (.) (.)得到第一個(.)將第二個(.)和第三個(.)作為它的參數。

是的,這是由於currying。 (.)因為Haskell中的所有函數只接受一個參數。 你正在編寫的是對每個相應的組合(.)的第一次部分調用,它采用了它的第一個參數(組合的第一個函數)。

(首先閱讀關於函數組合,$運算符和無點樣式的答案 。)

想象一下,你有一個簡單的功能:它加起來2個數字然后否定結果。 我們稱之為foo

foo a b = negate (a + b)

現在讓我們一步一步地讓它無點,看看我們最終得到了什么:

foo a b = negate $ a + b
foo a b = negate $ (+) a b
foo a b = negate $ (+) a $ b
foo a b = negate . (+) a $ b
foo a   = negate . (+) a -- f x = g x is equivalent to f = g
foo a   = (.) negate ((+) a) -- any infix operator is just a function
foo a   = (negate.) ((+) a) -- (2+) is the same as ((+) 2)
foo a   = (negate.) $ (+) a
foo a   = (negate.) . (+) $ a
foo     = (negate.) . (+)
foo     = ((.) negate) . (+)
foo     = (.) ((.) negate) (+) -- move dot in the middle in prefix position
foo     = ((.) ((.) negate)) (+) -- add extra parentheses

現在讓我們更仔細地分析表達式(.) ((.) negate) 它是(.)函數的部分應用,其第一個參數是((.) negate) 我們可以進一步改造它嗎? 我們可以:

(.) ((.) negate)
(.) . (.) $ negate -- because f (f x) is the same as (f . f) x
(.)(.)(.) $ negate
((.)(.)(.)) negate

(.).(.)相當於(.)(.)(.) ,因為在第1個表達式中,中間的點可以在前綴位置移動並用括號括起來,這就產生了第二個表達式。

現在我們可以重寫我們的foo函數:

foo = ((.).(.)) negate (+)
foo = ((.)(.)(.)) negate (+) -- same as previous one
foo = negate .: (+)
  where (.:) = (.).(.)

現在你知道(.).(.)相當於(\\fgxy -> f (gxy))

(\f g x y -> f (g x y)) negate (+) 2 3 -- returns -5
((.).(.)) negate (+) 2 3 -- returns -5

暫無
暫無

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

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