在 Haskell 中从头开始定义 monad
Defining monad from scratch in Haskell
在学习了 Haskell 中的 monad 之后——这个主题对所有暗示的东西都非常引人注目——我想知道我是否可以在不使用已经定义的类型类的情况下自己定义一个 monad。
我不想让 Monad
成为 Functor
的实例,我只想定义一个 monad 本身,它有自己的 fmap
函数(我还想更改一些函数名称例如 return
并称之为 unit
).
一个 monad 可以由绑定运算符 (>>=)
和函数 return
定义,但它也可以根据 return
和 join
来定义,因为这最后一个函数可以用绑定运算符来表示:join m = m >>= id
。所以一个 monad 可以(技术上)根据 return
和 join
定义,没有别的。函数 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")并不意味着它.相反,您必须证明您省略的运算符——绑定运算符——可以根据您拥有的东西来定义——return
和 join
。一旦你尝试这样做,你就会明白为什么一个 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
来定义 (>>=)
。所以如果你想让return
和join
成为Monad
的基本操作,你确实需要单独实现fmap
。
I know this wouldn't be as efficient as using the predefined Monad
typeclass
我看不出有任何理由先验地相信这一点。选择 (>>=)
而不是 join
的原因不是因为它更有效,而是因为在编程中使用 monad 时它是一种很自然的操作。
至于您的实际代码,我认为它看起来不错,但有两个注意事项:
我强烈怀疑您对 mapper
的定义与您认为的不一样。在行 mapper id = id
中,左侧的模式 id
匹配所有传入值,而右侧的变量 id
returns 它们不变。 mapper (f ∘ g) = mapper f ∘ mapper g
行根本无效 Haskell。 (也许这两行是为了记录 mapper
所要求的法律而不是实际代码?)
根据 (>>=)
提供 join
和 mapper
的默认实现是很奇怪的——出于上述原因(这三个都是基本的和必须定义)并且因为 (>>=)
不是 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)))
在学习了 Haskell 中的 monad 之后——这个主题对所有暗示的东西都非常引人注目——我想知道我是否可以在不使用已经定义的类型类的情况下自己定义一个 monad。
我不想让 Monad
成为 Functor
的实例,我只想定义一个 monad 本身,它有自己的 fmap
函数(我还想更改一些函数名称例如 return
并称之为 unit
).
一个 monad 可以由绑定运算符 (>>=)
和函数 return
定义,但它也可以根据 return
和 join
来定义,因为这最后一个函数可以用绑定运算符来表示:join m = m >>= id
。所以一个 monad 可以(技术上)根据 return
和 join
定义,没有别的。函数 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 functionreturn
, but it also may be defined in terms ofreturn
andjoin
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")并不意味着它.相反,您必须证明您省略的运算符——绑定运算符——可以根据您拥有的东西来定义——return
和 join
。一旦你尝试这样做,你就会明白为什么一个 monad 也是一个仿函数如此重要:
m >>= f = join (fmap f m)
The function
fmap
is required (and the base of existence of aFunctor
in Haskell), but also could be defined in terms ofreturn
, for it may be defined also (I think) as follows:fmap f m = return . f
这不是 fmap
的正确定义 -- 您确实应该怀疑,因为右侧没有提到 m
!正确的版本是:
fmap f m = m >>= return . f
但是现在这是一个循环定义,因为上面我们计划用 fmap
来定义 (>>=)
。所以如果你想让return
和join
成为Monad
的基本操作,你确实需要单独实现fmap
。
I know this wouldn't be as efficient as using the predefined
Monad
typeclass
我看不出有任何理由先验地相信这一点。选择 (>>=)
而不是 join
的原因不是因为它更有效,而是因为在编程中使用 monad 时它是一种很自然的操作。
至于您的实际代码,我认为它看起来不错,但有两个注意事项:
我强烈怀疑您对
mapper
的定义与您认为的不一样。在行mapper id = id
中,左侧的模式id
匹配所有传入值,而右侧的变量id
returns 它们不变。mapper (f ∘ g) = mapper f ∘ mapper g
行根本无效 Haskell。 (也许这两行是为了记录mapper
所要求的法律而不是实际代码?)根据
(>>=)
提供join
和mapper
的默认实现是很奇怪的——出于上述原因(这三个都是基本的和必须定义)并且因为(>>=)
不是 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)))