(->)
的信息感到非常好奇。它说:data (->) a b -- Defined in `GHC.Prim`
到目前为止还不错,但当它说 - 时,情况变得非常有趣。
instance Monad ((->) r) -- Defined in `GHC.Base`
instance Functor ((->) r) -- Defined in `GHC.Base`
这意味着什么?为什么 GHC 将其定义为一个 Monad 和 Functor 的实例,适用于 (->)
?
(->)
的信息感到非常好奇。它说:data (->) a b -- Defined in `GHC.Prim`
到目前为止还不错,但当它说 - 时,情况变得非常有趣。
instance Monad ((->) r) -- Defined in `GHC.Base`
instance Functor ((->) r) -- Defined in `GHC.Base`
这意味着什么?为什么 GHC 将其定义为一个 Monad 和 Functor 的实例,适用于 (->)
?
一开始可能会有些困惑,但重要的概念是记住 (->)
不是一个单子或函子,但是 (->) r
是。Monad
和 Functor
类型都具有 * -> *
的种类,因此它们只期望一种类型参数。
这意味着 (->) r
的 fmap
看起来像:
fmap g func = \x -> g (func x)
也被称为
fmap g func = g . func
这只是普通的函数组合!当你对func
使用fmap g
时,你通过应用g
来改变输出类型。在这种情况下,如果func
的类型为a -> b
,那么g
必须具有类似b -> c
的类型。
Monad
实例则更加有趣。它允许您在函数应用“之前”使用其结果。帮助我理解的一个例子是:
f :: Double -> (Double,Double)
f = do
x1 <- (2*)
x2 <- (2+)
return (x1, x2)
> f 1.0
(2.0, 3.0)
这个函数会将隐式参数应用于绑定右侧的每个函数,对于f
传入1.0
,它会将值2 * 1.0
绑定到x1
,将2 + 1.0
绑定到x2
,然后返回(x1, x2)
。这使得将单个参数应用于多个子表达式变得非常容易。该函数等效于
f' x = (2 * x, 2 + x)
这有什么用处呢?一个常见的用法是Reader
Monad,它只是一个新的类型包装器,包装了(->) r
。 Reader
Monad 使得在整个应用程序中应用一个静态全局配置变得很容易。您可以编写类似以下的代码:
[Translated]:
这个有什么用处呢?其中一个常见的用途是 Reader
monad,它只是一个对 (->) r
的新型封装。 Reader
monad 使得在整个应用程序中应用一个静态的全局配置变得很容易。你可以编写如下代码:
myApp :: Reader Config ()
myApp = do
config <- ask
-- Use config here
return ()
然后使用 runReader myApp initialConfig
运行您的应用程序。 您可以轻松编写 Reader Config
单子中的操作,将它们组合在一起,并且所有这些操作都可以访问全局只读配置。 另外,还有一个伴随的 ReaderT
单子转换器,它允许您将其构建到转换器堆栈中,让您拥有非常复杂的应用程序,并轻松访问静态配置。
我认为如果Haskell总是允许类型运算符的部分,它会少一些困惑:
data a->b
instance Monad (r -> )
看起来更自然。
简单解释:我认为考虑特殊情况Monad (Bool -> )
非常有帮助,它基本上是一个两个元素的容器类型。它有两个元素。
\case
False -> elem1
True -> elem2
因此,您可以将函子实例与列表的实例视为相同:在“所有包含的元素”上进行映射。
适用和单子实例略有不同,明确“容器转换”可能会更有帮助:
data Pair a = Pair a a
instance Functor Pair where
fmap f (Pair a b) = Pair (f a) (f b)
instance Monad Pair where
return a = Pair a a
join (Pair (Pair a _) (Pair _ b))
= Pair a b
Applicative
/Monad
实例在不断的包装和压缩过程中是有意义的。作为一个容器,(r ->)
的大小是恒定的,因此强制要求Monad
和Applicative
实例反映这一点。 - J. Abrahamsonpure
/return
应用于[]
,它的规范定义是产生一个单例列表。然而,不可能有一个“单例”(Bool ->)
容器,因此这种直觉会让你感到困惑。大多数“容器”类型都是这样行为的,但像infinite stream这样的东西却不是这样——它总是必须有无限多的元素,所以pure
/return
只是使它们都成为相同的值。你对Applicative
/Monad
的直觉与我们在(r ->)
中看到的完全相同。 - J. Abrahamsonf
同构于某个源数据的某个函数(即 存在 r . forall a . f a ~ r -> a
)时,我们称其为可表示函子,可能会认为它是“固定大小”的(大小由所选的 r
确定),并且将具有类似于 (r ->)
的 Applicative
/Monad
实例。 - J. Abrahamsoninstance Functor ((->) r) where
fmap = (.)
instance Monad ((->) r) where
return = const
f >>= k = \ r -> k (f r) r
f :: Double -> Double
应该改为f :: Double -> (Double, Double)
。 - The Internet(->) a
单子的作用! - bheklilr(<||>) = liftM2 (||)
。现在你可以很容易地构建组合谓词,比如isAlpha <||> isLetter <||> (== 'c') :: Char -> Bool
这是一个非常傻的例子。对我来说,satisfy $ isAlpha <||> isLetter <||> (== 'c')
看起来比satisfy $ \c -> isAlpha c || isLetter c || c == 'c'
更好看。 - J. Abrahamson