我对类别理论的了解并不是很好。所以请耐心等待。
我一直在阅读Monads Made Difficult
并看到以下定义。
class (Category c, Category d) => Functor c d t where
fmap :: c a b -> d (t a) (t b)
从该网站我读到Haskell前奏中的Functor类型实际上是一个Endofunctor。 (上述类中的类别c和d都是Hask)
阅读完页面后,我很想知道。如果Haskell使用真正的Functors而不仅仅是Endofunctors,它会更适合元编程吗?
说我们有以下(Js代表Javascript)
data Js a where
litInt :: Integer -> Js Integer
litString :: String -> Js String
var :: String -> Js a
lam :: String -> Js b -> Js (a -> b)
let :: String -> Js a -> Js b -> Js b
ap :: JsFunc a b -> Js a -> Js b
type JsFunc a b = Js (a -> b)
现在回到上面的Functor定义
class (Category c, Category d) => Functor c d t where
fmap :: c a b -> d (t a) (t b)
我们可以将c设为JsFunc,d为Hask并且必须为Js
这将为我们提供Js的Functor。我无法使用Haskell的Endofunctor。
我找不到与此Functor相同方式定义的Apply或Applicative类型的任何内容。这些也可以这样做吗?
如果我错了,请纠正我。但Monad也不能这样做,因为它确实是Endofunctor的一个实例?
答案 0 :(得分:3)
根据this mailing list post,应用函子在数学文献中称为“弱对称松散幺正函子”。 (简要,不小心)查看lax monoidal的nlab页面表明这个概念也是由两个类别参数化的,所以你可以将这个参数仿函数类扩展到参数化的应用类,但有一些工作。
正如你所说,monad是endofunctors,所以它们不能通过两个类别进行参数化。但是他们作为参数的一个类别不必是Hask;所以也可以给出一个参数化的monad,比如
class Functor c c f => Monad c f where
return :: c a (f a)
join :: c (f (f a)) (f a)
然后我们可以使用一种标准技巧来定义bind:
(=<<) :: Monad c m => c a (m b) -> c (m a) (m b)
(=<<) f = join . fmap f
此处的(.)
操作是Category
类的合成,而不是Prelude
的合成。
答案 1 :(得分:3)
嗯,首先 -
...如果它使用真正的Functors而不仅仅是Endofunctors ......
Haskell仿函数是真正的仿函数。只有Functor
类不允许任何类通用函子,而只允许 Hask 中的特定元素作为endofunctor。
事实上,非内部函子很有趣;数学家一直使用它们。虽然如你所说,不能有非endofunctor monad,Applicative
的类似物是可能的:该类实际上只是monoidal functors的一个很好的Haskell接口,可以在不同类别之间定义。看起来像这样:
class (Functor r t f, Category r, Category t) => Monoidal r t f where
pureUnit :: () `t` f ()
fzipWith :: (a, b) `r` c -> (f a, f b) `t` f c
要在标准Applicative
类中方便地实际使用它,您需要的不仅仅是Monoidal
类。这已在couple libraries中进行了尝试。我也是wrote one。但是,嗯......它看起来并不美丽......
class (Functor f r t, Cartesian r, Cartesian t) => Monoidal f r t where
pureUnit :: UnitObject t `t` f (UnitObject r)
fzipWith :: (ObjectPair r a b, Object r c, ObjectPair t (f a) (f b), Object t (f c))
=> r (a, b) c -> t (f a, f b) (f c)
class (Monoidal f r t, Curry r, Curry t) => Applicative f r t where
-- ^ Note that this tends to make little sense for non-endofunctors.
-- Consider using 'constPure' instead.
pure :: (Object r a, Object t (f a)) => a `t` f a
(<*>) :: ( ObjectMorphism r a b
, ObjectMorphism t (f a) (f b), Object t (t (f a) (f b))
, ObjectPair r (r a b) a, ObjectPair t (f (r a b)) (f a)
, Object r a, Object r b )
=> f (r a b) `t` t (f a) (f b)
(<*>) = curry (fzipWith $ uncurry id)
我不知道这些框架是否允许您以实用的方式编写所需的应用程序JS代码。可能,但实际上我怀疑它会非常凌乱。还要注意,虽然你现在有应用程序浮动,但这并不意味着你实际上可以在Js代码中实际执行 Hask 应用程序通常所做的事情 - 相反,你实际上需要包装那些适用于Js
类型的变形金刚。
您可能需要考虑完全放弃“JS值”,而仅写入类别/箭头范例。即,转变定义:
data JsFunc a b where
litInt :: Integer -> JsFunc () Integer
litString :: String -> JsFunc () String
var :: String -> JsFunc () a
lam :: String -> JsFunc () b -> JsFunc a b
let :: String -> JsFunc () a -> JsFunc () b -> JsFunc () b
ap :: JsFunc a b -> JsFunc () a -> JsFunc () b
type Js = JsFunc ()
此处,我们使用()
作为JsFunc
类别的terminal object - 这使得Js a ≡ JsFunc () a
箭头等同于我们通常所说的值(至少如果我们不考虑严格语义)。如果你走这条路,几乎所有东西都需要无点写,但是syntax to pretend otherwise,你可以很好地整合仿函数甚至monad(如Kleisli箭头)。
答案 2 :(得分:2)
我们可以将c设为JsFunc,d为Hask并且必须为Js
嗯,我们不能完全准确,因为JsFunc
是一个不饱和类型的同义词,所以我们不能实例化一个变量(c
)。
您可以创建新类型
newtype JsCat a b = JsCat (Js (a -> b))
然后像你说的那样创建一个Functor JsCat (->) Js
。
然而,这并没有给你任何新的东西,因为如果我们扩展你想要的fmap
类型,它就会变成
fmap :: JsFunc a b -> (->) (Js a) (Js b) -- or
fmap :: Js (a -> b) -> Js a -> Js b
这是Apply
的{{1}}个实例。所以,我不确定为什么你想把它变成一个仿函数,当它已经适合现有的抽象时。