在 Haskell 中从头开始定义 monad

Defining monad from scratch in Haskell

在学习了 Haskell 中的 monad 之后——这个主题对所有暗示的东西都非常引人注目——我想知道我是否可以在不使用已经定义的类型类的情况下自己定义一个 monad。

我不想让 Monad 成为 Functor 的实例,我只想定义一个 monad 本身,它有自己的 fmap 函数(我还想更改一些函数名称例如 return 并称之为 unit).

一个 monad 可以由绑定运算符 (>>=) 和函数 return 定义,但它也可以根据 returnjoin 来定义,因为这最后一个函数可以用绑定运算符来表示:join m = m >>= id。所以一个 monad 可以(技术上)根据 returnjoin 定义,没有别的。函数 fmap 是必需的(并且 Haskell 中存在 Functor 的基础),但也可以根据 return 来定义,因为它也可以定义(我认为)如下: fmap f m = m >>= return . f (编辑前写的,fmap f m = return . f;显然是错字).

但我知道这不会像使用预定义的 Monad 类型类那样有效,它只是为了更好地理解 Haskell 语言。

我怎样才能做到这一点?这是我现在对这个概念的描述,所以它不是有用的代码:

-- Just a sketch
infixr 9 ∘
(∘) :: (b -> c) -> (a -> b) -> a -> c
(∘) g f x = g (f x)
--(f ∘ g) x = f (g x)

-- My own 'fmap'
--mapper id  =  id
--mapper (f ∘ g) = mapper f ∘ mapper g


-- My monad
class MyMonadBase (m :: * -> *) where
    unit :: a -> m a   --return

    join :: m (m a) -> m a
    join = (>>= id)

    mapper f m = m >>= unit ∘ f


--Testing:
data Tree a = Leaf a | Branch (Tree a) (Tree a)

instance MyMonadBase Tree where
    unit = Leaf
    join (Leaf x) = x
    join (Branch l r)  = Branch (join l) (join r)

我在正确的轨道上(概念上)吗?

一些小的更正:

A monad may be defined by the bind operator (>>=) and the function return, but it also may be defined in terms of return and join since this last function it can be expressed in terms of the bind operator: join m = m >>= id.

结论(“[a monad] also may be defined in terms of return and join”)是正确的,但前提("since [join] can be expressed in terms of the bind operator")并不意味着它.相反,您必须证明您省略的运算符——绑定运算符——可以根据您拥有的东西来定义——returnjoin。一旦你尝试这样做,你就会明白为什么一个 monad 也是一个仿函数如此重要:

m >>= f = join (fmap f m)

The function fmap is required (and the base of existence of a Functor in Haskell), but also could be defined in terms of return, for it may be defined also (I think) as follows: fmap f m = return . f

这不是 fmap 的正确定义 -- 您确实应该怀疑,因为右侧没有提到 m!正确的版本是:

fmap f m = m >>= return . f

但是现在这是一个循环定义,因为上面我们计划用 fmap 来定义 (>>=)。所以如果你想让returnjoin成为Monad的基本操作,你确实需要单独实现fmap

I know this wouldn't be as efficient as using the predefined Monad typeclass

我看不出有任何理由先验地相信这一点。选择 (>>=) 而不是 join 的原因不是因为它更有效,而是因为在编程中使用 monad 时它是一种很自然的操作。

至于您的实际代码,我认为它看起来不错,但有两个注意事项:

  1. 我强烈怀疑您对 mapper 的定义与您认为的不一样。在行 mapper id = id 中,左侧的模式 id 匹配所有传入值,而右侧的变量 id returns 它们不变。 mapper (f ∘ g) = mapper f ∘ mapper g 行根本无效 Haskell。 (也许这两行是为了记录 mapper 所要求的法律而不是实际代码?)

  2. 根据 (>>=) 提供 joinmapper 的默认实现是很奇怪的——出于上述原因(这三个都是基本的和必须定义)并且因为 (>>=) 不是 class 操作,因此不能由用户以有意义的方式定义。

好吧,没那么难。我误以为实现自己的 monad 类型会非常复杂,但它只是应用了定义。

-- My monad
class MyMonad m where
    unit :: a -> m a
    join :: m (m a) -> m a
    mapf :: (a -> b) -> m a -> m b


--Testing MyMonad
data Tree a = Leaf a | Branch (Tree a) (Tree a) deriving (Show)

instance MyMonad Tree where
    unit = Leaf
    join (Leaf x) = x
    join (Branch l r) = Branch (join l) (join r)
    mapf f (Leaf x) = Leaf (f x)
    mapf f (Branch l r) = Branch (mapf f l) (mapf f r)

t = Branch (Branch (Leaf 1) (Leaf 3)) (Branch (Leaf 2) (Leaf 4))


-- My bind (just for completeness, not that I need it for this example)
(>>>) :: MyMonad m => m a -> (a -> m b) -> m b
xs >>> f = join (mapf f xs)


-- Testing my bind
extr :: Integer -> Tree Integer
extr x = Branch (Leaf (x^2)) (Leaf (2^x))

t >>> extr
--Branch (Branch (Branch (Leaf 1) (Leaf 2)) (Branch (Leaf 9) (Leaf 8))) 
--       (Branch (Branch (Leaf 4) (Leaf 4)) (Branch (Leaf 16) (Leaf 16)))