改变Haskell的编程器编程器

时间:2015-04-12 09:53:52

标签: haskell metaprogramming dsl

我对类别理论的了解并不是很好。所以请耐心等待。

我一直在阅读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的一个实例?

3 个答案:

答案 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}}个实例。所以,我不确定为什么你想把它变成一个仿函数,当它已经适合现有的抽象时。