markdown 来自卑微起点的Yoneda直觉

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了markdown 来自卑微起点的Yoneda直觉相关的知识,希望对你有一定的参考价值。

(previous [Yoneda blog](https://gist.github.com/Icelandjack/aecf49b75afcfcee9ead29d27cc234d5))
([reddit](https://www.reddit.com/r/haskell/comments/a0nyjj/yoneda_intuition_from_humble_beginnings/))
([twitter](https://twitter.com/Iceland_jack/status/1067173831708688384))

# Yoneda Intuition from Humble Beginnings

Let's explore the Yoneda lemma. You don't need to be an advanced Haskeller to understand this. In fact I claim you will understand the first section fine if you're comfortable with `map`/[`fmap`](https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Functor.html#v:fmap) and [`id`](https://hackage.haskell.org/package/base-4.11.1.0/docs/Data-Function.html#v:id).

I am not out to motivate it, but we will explore Yoneda at the level of terms and at the level of types.

> "[Yoneda lemma], arguably the most important result in category theory."
> 
> [*What You Needa Know about Yoneda*](https://www.cs.ox.ac.uk/jeremy.gibbons/publications/proyo.pdf)

(congratulations on not running away after "Yoneda lemma" or "category theory", what are you on)

## Yoneda; Term level

```haskell
[1..3]
```

Every list can be expressed as a (higher-order) function by viewing it through our Yoneda-tinted glasses :

```haskell
\f -> [f 1, f 2, f 3]
```

It's like the function `f` grabs a hold of each element. I'll call it `post` because it does "postprocessing".

The Yoneda lemma states that `[1..3]` and `\f -> [f 1, f 2, f 3]` are equivalent! (naturally isomorphic)

How do we go back? We pass it the identity function:

```haskell
  (\post -> [post 1, post 2, post 3]) id
= [id 1, id 2, id 3]
= [1..3]
```

The Yoneda form of `[1..3]` can be understood without `(f)map` but it is really `map` partially applied to its second argument:

```haskell
  \post -> [post 1, post 2, post 3]
= \post -> map post [1..3]
= flip map [1..3]
= (<$> [1..3])
```

This works for any `Functor`, the Yoneda forms of `Just True` and `Nothing` are

```haskell
\post -> Just (post True)
\_    -> Nothing
```

And the Yoneda form of [`Const "abc"`](https://hackage.haskell.org/package/base-4.12.0.0/docs/Data-Functor-Const.html#t:Const) doesn't touch the string:

```haskell
\_ -> Const "abc"
```

Recall that functions are Functors too (`instance Functor ((->) a)` where `fmap = (.)`), which is where our head starts to hurt: The Yoneda form now transforms a **function** to a higher-order function, how does that work?

Let's take the function `not` as an example, we pass the result of `not bool` to `post`:

```haskell
\post -> \bool -> post (not bool)
```

So we are really postcomposing `not` with our `post` function:

```haskell
\post -> post . not
\post -> fmap post not
```

or, doing case analysis on the Boolean, we are invoking `post` on the negated Boolean.

```haskell
\post -> \case    -- -XLambdaCase
  False -> post True
  True  -> post False
```

What happens when we pass in `id`? We get back `not`:

```haskell
\case
  False -> True
  True  -> False
```

## Yoneda; Type level

Our first example used `Functor []`

```haskell
list :: [Int]
list = [1..3]
```

So what is the type of the Yoneda form? (switching back from `post` to `f`)

```haskell
yoList :: (Int -> x) -> [x]
yoList f = [f 1, f 2, f 3]
```

Interesting. Is this what you expected? 

Before we had a list of `Int`s. Now we have a **polymorphic** higher-order function. The polymorphism is crucial for this to work, (warning: vague) think of it as grabbing every `Int` inside of `[Int]` and ripping them out, leaving a hole `x` behind (not to be confused with [typed holes](https://downloads.haskell.org/~ghc/8.6.2/docs/html/users_guide/glasgow_exts.html#typed-holes), this is a conceptual hole) (bonus points if you physically act this out every time you use Yoneda).

Imagine we have a magical Yoneda crossbow (no I don't know archercy, so this will be a funky). I think of `Yoneda [] Int` (Yoneda form of `[Int]`) like a cocked crossbow: we have "pulled" the `Int` out of it we get a very tightly-strung `(Int -> x)`, leaving a list of holes `[x]`.

To fire we load it with a *1)* type `@Int` and *2)* function (arrow) `id :: Int -> Int`
1. Remember that `yoList :: forall x. (Int -> x) -> [x]` actually takes `x` as an invisible argument. GHC usually solves it with unifiation but our first step is to instantiate `x` with a type: with crucial use of the `yoList` polymorphism we pick `Int` using [visible type applications](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#visible-type-application)

    ```haskell
    yoList @Int :: (Int -> Int) -> [Int]
    ```
2. The second step is passing `id :: Int -> Int` (..the arrow?) as an argument, and we get `yoList @Int id :: [Int]` back.

Let's look at the other cases,

```haskell
just :: Maybe Bool
just = Just True
```
becomes
```haskell
yoJust :: (Bool -> x) -> Maybe x
yoJust f = Just (f True)
```

`Nothing :: Maybe a` contains no values of type `a` but, but we can still follow the pattern:
```haskell
yoNothing :: (a -> x) -> Maybe x
yoNothing _ = Nothing
```

Similarly `constAbc` contains no values of `a` 
```haskell
constAbc :: Const String a
constAbc = Const "abc"
```
so 
```haskell
yoConstAbc :: (a -> x) -> Const String x
yoConstAbc _ = Const "abc"
```

You might ask, "what happens if we do want to change the `String`?". So far I have restricted myself to `Functor` which is very limiting. See if you can make sense of the type of `\f -> Const (f "abc")` or even
```haskell
yoPair :: (Int -> x) -> (x, x)
yoPair f = (f 1, f 2)
```

which is not a Haskell `Functor`. We can in fact play this game with arbitrary type parameters;

```haskell
pair :: (Int, Bool, Char)
pair = (10, False, 'X')

yoPair1 :: (Int -> x) -> (x, Bool, Char)
yoPair1 f = (f 10, False, 'X')

yoPair2 :: (Bool -> y) -> (Int, y, Char)
yoPair2 f = (10, f False, 'X')

yoPair123 :: (Int -> x) -> (Bool -> y) -> (Char -> z) -> (x, y, z)
yoPair123 f g h = (f 10, g False, h 'X')
```

etc. are you getting a feel for it yet? To go back, we pass `id`.

Functions get weird though, when we have a function `show @Int` the type is

```haskell
show @Int :: (->) Int String
```

and `flip fmap show` (`(<$> show)`) pulls the `String` back

```haskell
(<$> show @Int) :: (String -> x) -> (Int -> x)
```

But the same doesn't work for `Int`, up until now we have worked on **co**variant arguments but `String` appears **contra**variantly. To understand the difference compare the type of `fmap` and [`contramap`](https://hackage.haskell.org/package/contravariant-1.5/docs/Data-Functor-Contravariant.html#v:contramap): the only difference is that the input arrow is flipped:

```haskell
fmap      :: Functor       f => (a -> a') -> (f a -> f a')
contramap :: Contravariant f => (a <- a') -> (f a -> f a')
```

## Yoneda; Type level again
This is starting to feel ranty, I will discuss how to actually use [`Yoneda`](https://hackage.haskell.org/package/kan-extensions-5.2/docs/Data-Functor-Yoneda.html) later (for example to fuse `fmap`s or to derive right Kan extensions), but for now I leave you with the type of `fmap @F` (I'm jumping around with variable names):

```haskell
(a -> x) -> (F a -> F x)
```

What is the type of `flip (fmap @F)`?

```haskell
F a -> (a -> x) -> F x
```
or if you wish to be explicit
```haskell
forall a x. F a -> (a -> x) -> F x
```

It kind of looks like `F a` and `F x` are having a tug-o-war using `(a -> x)`. Of course that's not what is happening at all, perish the thought. But if we float the `forall x` past `F a`

```haskell
                 vvvvvvvvvvvvvvvvvvvvvvvvv
forall a. F a -> forall x. (a -> x) -> F x
                 ^^^^^^^^^^^^^^^^^^^^^^^^^
```

we got ourselves the type of our Yoneda forms. Because our `yo*` examples had to be polymorphic in the `x`

```haskell
yoList          :: forall   x. (Int    -> x) -> [x]
yoJust          :: forall   x. (Bool   -> x) -> Maybe x
yoNothing       :: forall a x. (a      -> x) -> Maybe x
yoConstAbc      :: forall a x. (a      -> x) -> Const String x
(<$> show @Int) :: forall   x. (String -> x) -> (Int -> x)
```

so with [`RankNTypes`](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/glasgow_exts.html#extension-RankNTypes) we can give Yoneda a name (I use a type synonym for simplicity, you will want to use a `newtype` like [`Data.Functor.Yoneda`](https://hackage.haskell.org/package/kan-extensions-5.2/docs/Data-Functor-Yoneda.html#t:Yoneda))

```
>> :set -XRankNTypes
>> type Yoneda f a = (forall xx. (a -> xx) -> f xx)
>>
>> :t (\f -> [f 1, f 2, f 3]) :: Yoneda [] Int
.. :: (Int -> xx) -> [xx]
>> :t (<$> [1..3]) :: Yoneda [] Int
.. :: (Int -> xx) -> [xx]
```

and now we 

```haskell
yoList          ::           Yoneda []             Int
yoJust          ::           Yoneda Maybe          Bool
yoNothing       :: forall a. Yoneda Maybe          a
yoConstAbc      :: forall a. Yoneda (Const String) a
(<$> show @Int) ::           Yoneda ((->) Int)     String
```

以上是关于markdown 来自卑微起点的Yoneda直觉的主要内容,如果未能解决你的问题,请参考以下文章

转:尹一丁:创新,别忽视领导者与员工们的直觉

U-net 与 FCN 背后的直觉用于语义分割

会用Microsoft的人,又卑微又软弱

卑微之尘

如何做到既主动又不显得那么卑微?

指针 (卑微醇醇初学指针的崩溃啊)