【问题标题】:Changing Haskell's Functor for Metaprogramming更改 Haskell 的元编程函子
【发布时间】:2015-04-12 09:53:51
【问题描述】:

我的范畴论知识不是很好。所以请多多包涵。

我一直在阅读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 使用真正的 Functor 而不仅仅是 Endofunctor,它会更适合元编程吗?

假设我们有以下内容(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 的一个实例?

【问题讨论】:

    标签: haskell metaprogramming dsl


    【解决方案1】:

    嗯,首先——

    ...如果它使用真正的 Functors 而不仅仅是 Endofunctors ...

    Haskell 仿函数真正的仿函数。只是,Functor 类不允许 任何 类通用仿函数,而只允许在 Hask 上作为内仿函数的特定仿函数。

    确实,非 endo-functor 很有趣;数学家一直在使用它们。虽然正如你所说,不可能有非 endofunctor monad,类似于 Applicative is 可能:该类实际上只是 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 类。这已在librariescouple 中进行了尝试。我也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 箭头等同于我们通常所说的(至少如果我们不这样做' t 考虑严格语义)。如果你走这条路,几乎所有东西都需要写成无点的,但是有syntax to pretend otherwise,你可以很好地合并函子甚至单子(如 Kleisli 箭头)。

    【讨论】:

    • 不应该是lam :: String -&gt; JsFunc () b -&gt; JsFunc a b吗?
    • haskell 的 Arrow 类型类是否必须更改为使用箭头 do-notation? 'arr' 会将任意 haskell 函数提升为无法转换为 JavaScript 的 JsFunc。
    • @clinux:对,我忘记了。在 Arrow 类中允许提升任意 Haskell 函数实际上是一个小技巧——该类是 IMOsupposed 来表示有针对性的幺半群类别,但这些需要大量的方法(@ 987654342@、const 等),可以使用arr 进行七合一滑动。这也是我不一样的地方in my categories library
    • 是的,没错。你可以不用 'arr' 用一个充满额外方法的拳头。你见过加罗吗? megacz.com/berkeley/garrows 。我在想,如果你可以用这种方式概括一个箭头,你也可以概括一个应用程序吗?每个第一个类型固定的箭头(不是正确的术语)都是 Applicative 的一个实例。是否存在 GApplicative?
    • 其实你对Applicative的定义就像我要找的广义的applicative?它只是让我读起来很困惑:p
    【解决方案2】:

    根据this mailing list post,应用函子在数学文献中被称为“弱对称松散单曲面函子”。 (简要,不仔细)查看lax monoidal 的 nlab 页面表明这个概念也由两个类别参数化,因此您可以通过一些工作将此参数仿函数类扩展为参数应用类。

    正如你所说,单子是内函子,所以它们不能被两个类别参数化。但是它们作为参数的一个类别不一定是 Hask。所以也可以给出一个参数化的 monad,比如

    class Functor c c f => Monad c f where
        return :: c a (f a)
        join :: c (f (f a)) (f a)
    

    然后我们可以使用一种标准技巧来定义绑定:

    (=<<) :: Monad c m => c a (m b) -> c (m a) (m b)
    (=<<) f = join . fmap f
    

    这里的(.)操作是Category类的组合,不是Prelude的函数组合。

    【讨论】:

    • 所以这意味着我可以让'c'成为 JsFunc,并说我有一个 JavaScript 前奏曲,它有一个 Maybe 类型('f' 变成了 Maybe)。这将允许我为代码生成器创建一个 javascript Maybe 类型的 monad 实例?我必须使用 Js 的 'lam' 而不是 haskell 的 lambda,但仍然很酷。
    【解决方案3】:

    我们可以让 c 是 JsFunc,d 是 Hask 而不是 Js

    好吧,不,我们不能完全做到,因为JsFunc 是不饱和类型的同义词,所以我们不能将变量 (c) 实例化到它。

    你可以创建一个新类型

    newtype JsCat a b = JsCat (Js (a -> b))
    

    然后像你说的那样创建一个Functor JsCat (-&gt;) Js

    然而,这并没有给你任何新的东西,因为如果我们扩展你想要的 fmap 的类型,它就变成了

    fmap :: JsFunc a b -> (->) (Js a) (Js b) -- or
    fmap :: Js (a -> b) -> Js a -> Js b
    

    这就是JsApply 实例。所以,当它已经适合现有的抽象时,我不确定为什么要将它变成函子。

    【讨论】:

    • Apply 类型类是 Functor(Haskell 的 Endofunctor)的子类,所以你真的不能让它成为 Apply 的实例。
    猜你喜欢
    • 2017-10-02
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 2011-11-16
    • 2015-05-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多