简体   繁体   English

约束类型类或实例的派生变量

[英]Constraining a derived variable of a type class or an instance

I'm writing a type class for my pipes library to define an abstract interface to Proxy -like types. 我正在为我的pipes库编写一个类型类来定义类似于Proxy的类型的抽象接口。 The type class looks something like: 类型类看起来像:

class ProxyC p where
    idT   :: (Monad m) => b' -> p a' a b' b m r
    (<-<) :: (Monad m)
          => (c' -> p b' b c' c m r)
          -> (b' -> p a' a b' b m r)
          -> (c' -> p a' a c' c m r)
    ... -- other methods

I'm also writing extensions for the Proxy type that are of the form: 我也在编写以下形式的Proxy类型的扩展:

instance (ProxyC p) => ProxyC (SomeExtension p) where ....

... and I'd like these instances to be able to impose an additional constraint that if m is a Monad then pa' ab' bm is a Monad for all a' , a , b' , and b . ...并且我希望这些实例能够施加一个额外的约束,即如果mMonad那么pa' ab' bm是所有a'ab'bMonad

However, I have no clue how to cleanly encode that as a constraint either for the ProxyC class or for the instances. 但是,我不知道如何干净地将其编码为ProxyC类或实例的约束。 The only solution I currently know of is to do something like encoding it in the method signatures of the class: 我目前所知道的唯一解决方案是在类的方法签名中对其进行编码:

    (<-<) :: (Monad m, Monad (p b' b c' c m), Monad (p a' a b' b m))
          => (c' -> p b' b c' c m r)
          -> (b' -> p a' a b' b m r)
          -> (c' -> p a' a c' c m r)

... but I was hoping there would be a simpler and more elegant solution. ...但我希望有一个更简单,更优雅的解决方案。

Edit : And not even that last solution works, since the compiler does not deduce that (Monad (SomeExtension pa' ab' bm)) implies (Monad (pa' ab' bm)) for a specific choice of variables, even when given the following instance: 编辑 :甚至最后一个解决方案都不起作用,因为编译器没有推断出(Monad (SomeExtension pa' ab' bm))暗示(Monad (pa' ab' bm)) (Monad (SomeExtension pa' ab' bm)) (Monad (pa' ab' bm))特定的变量选择,即使给定了以下实例:

instance (Monad (p a b m)) => Monad (SomeExtension p a b m) where ...

Edit #2 : The next solution I'm considering is just duplicating the methods for the Monad class within the ProxyC class: 编辑#2 :我正在考虑的下一个解决方案是复制ProxyC类中Monad类的方法:

class ProxyC p where
    return' :: (Monad m) => r -> p a' a b' b m r
    (!>=) :: (Monad m) => ...

... and then instantiating them with each ProxyC instance. ...然后用每个ProxyC实例实例化它们。 This seems okay for my purposes since the Monad methods only need to be used internally for extension writing and the original type still has a proper Monad instance for the downstream user. 这似乎可以用于我的目的,因为Monad方法只需要在内部用于扩展写入,并且原始类型仍然具有适合下游用户的Monad实例。 All this does is just expose the Monad methods to the instance writer. 所有这一切只是将Monad方法暴露给实例编写器。

a rather trivial way to do this is use a GADT to move the proof to the value level 一个相当简单的方法是使用GADT将证明移动到价值水平

data IsMonad m where
  IsMonad :: Monad m => IsMonad m 

class ProxyC p where
  getProxyMonad :: Monad m => IsMonad (p a' a b' b m)

you will need to explicitly open the dictionary wherever you need it 您需要在任何需要的地方明确打开字典

--help avoid type signatures
monadOf :: IsMonad m -> m a -> IsMonad m
monadOf = const

--later on
case getProxyMonad `monadOf` ... of
  IsMonad -> ...

the tactic of using GADTs to pass proofs of propositions is really very general. 使用GADT传递命题证明的策略非常普遍。 If you are okay using constraint kinds, and not just GADTs, you can instead use Edward Kmett's Data.Constraint package 如果你可以使用约束种类,而不仅仅是GADT,你可以使用Edward Kmett的Data.Constraint

class ProxyC p where
  getProxyMonad :: Monad m => Dict (Monad (p a' a b' b m))

which lets you defined 这让你定义

getProxyMonad' :: ProxyC p => (Monad m) :- (Monad (p a' a b' b m))
getProxyMonad' = Sub getProxyMonad

and then use a fancy infix operator to tell the compiler where to look for the monad instance 然后使用花哨的中缀运算符告诉编译器在哪里查找monad实例

 ... \\ getProxyMonad'

in fact, the :- entailment type forms a category (where the objects are constraints), and this category has lots of nice structure, which is to say it is pretty nice to do proofs with. 事实上, :- entailment类型形成一个类别(对象是约束),这个类别有很多很好的结构,也就是说用它做证明是相当不错的。

ps none of these snippets are tested. ps这些片段都没有经过测试。

edit: you could also combine the value level proofs with a newtype wrapper and not need to open GADTs all over the place 编辑:您还可以将值级别证明与newtype包装器结合使用,而不需要在整个地方打开GADT

newtype WrapP p a' a b' b m r = WrapP {unWrapP :: p a' a b' b m r}

instance ProxyC p => Monad (WrapP p) where
  return = case getProxyMonad of
                Dict -> WrapP . return
  (>>=) = case getProxyMonad of
               Dict -> \m f -> WrapP $ (unWrapP m) >>= (unWrapP . f)

instance ProxyC p => ProxyC (WrapP p) where
  ...

I suspect, but obviously have not tested, that this implementation will also be relatively efficient. 我怀疑,但显然没有测试过,这个实现也会相对有效。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM