在Haskell中从头开始定义monad

时间:2015-12-30 18:20:35

标签: haskell monads functor

在研究了Haskell中的monads之后 - 这个主题非常引人注目 - 我想知道我是否可以在不使用已经定义的类型类的情况下自己定义monad。

我没有让Monad成为Functor的实例,而只是想用它自己的fmap函数来定义monad本身(我还想更改一些函数名称,例如: 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)

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

2 个答案:

答案 0 :(得分:3)

一些小的修正:

  

monad可以由绑定运算符(>>=)和函数return定义,但也可以用returnjoin来定义,因为最后一个函数它可以用绑定运算符表示:join m = m >>= id

结论(“[a monad]也可以用returnjoin”来定义)是正确的,但前提(“自[join]可以表达在绑定运算符方面“)并不意味着它。相反,您必须证明您省略的运算符 - 绑定运算符 - 可以根据您拥有的内容 - returnjoin进行定义。一旦你尝试这样做,你就会明白为什么monad也是一个仿函数是如此重要:

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

函数fmap是必需的(并且是Haskell中Functor存在的基础),但也可以用return来定义,因为它也可以定义(我认为)如下:fmap f m = return . f

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

fmap f m = m >>= return . f

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

  

我知道这不如使用预定义的Monad类型类

那么有效

我认为没有理由相信这是先验的。选择(>>=)而不是join的原因并不是因为它更有效,而是因为在编程中使用monad时它是如此自然的操作。

至于你的实际代码,我觉得它有两点需要注意:

  1. 我强烈怀疑你对mapper的定义不符合你的想法。在第mapper id = id行中,左侧的模式id匹配所有传入的值,右侧的变量id使它们保持不变。行mapper (f ∘ g) = mapper f ∘ mapper g根本不是有效的Haskell。 (也许这两行旨在记录mapper所需法律而非实际代码?)

  2. join的形式提供mapper(>>=)的默认实现很奇怪 - 两者都是由于上述原因(所有三个都是基本的,必须定义)并且因为(>>=)不是类操作,因此用户无法以有意义的方式定义。

答案 1 :(得分:2)

好吧,这并不困难。我误以为实现一个自己的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)))