【问题标题】:Defining monad from scratch in Haskell在 Haskell 中从头开始定义 monad
【发布时间】:2015-12-30 18:20:34
【问题描述】:

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

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

monad 可以由绑定运算符(>>=) 和函数return 定义,但也可以用returnjoin 定义,因为最后一个函数可以用绑定运算符:join m = m >>= id。所以一个单子可以(技术上)用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)

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

【问题讨论】:

  • fmap f = return . f - 这不起作用。 return . f 的类型是 a -> m b,而不是 m a -> m bmapper 也不起作用:您不能在 id 上进行模式匹配,除非您将它们定义为 数据构造函数。 — 无论如何...我不太明白你在这里想要达到什么目的。从根本上说,monad 首先是一个函子。即使在技术上可以省略该要求,也不意味着这样做是有意义的。 Haskell98 做到了,很快就意识到这不是一个好主意。
  • (g <$>) = (return . g =<<),即return是不够的,还需要bind。 (k =<<) = join . (k <$>),所以你必须有(<$>)(即fmap)和join。参看。 stackoverflow.com/questions/34545818/…

标签: haskell monads functor


【解决方案1】:

一些小的更正:

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

结论(“[a monad] 也可以用returnjoin 来定义”)是正确的,但是前提(“since [join] 可以用绑定运算符来表示") 并不意味着它。相反,您必须表明您正在省略的运算符——绑定运算符——可以根据你所拥有的东西来定义——returnjoin。一旦你尝试这样做,你就会明白为什么 monad 也是 functor 如此重要:

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. (>>=) 方面提供joinmapper 的默认实现是很奇怪的——出于上述原因(这三个都是基本的,必须定义),并且因为(>>=) 是不是类操作,因此用户无法以有意义的方式定义。

【讨论】:

  • 对于许多仿函数,join . fmap f (f =<<)慢。
【解决方案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)))

【讨论】:

    猜你喜欢
    • 2023-03-18
    • 1970-01-01
    • 2013-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多