简体   繁体   English

不是Monad约束

[英]Not a Monad Constraint

Is it possible to define a instance constrain for "not a monad", in order to define two non-overlapping instances, one for monadic values, other for non-monadic values? 是否可以为“非monad”定义实例约束,以便定义两个非重叠实例,一个用于monadic值,另一个用于非monadic值?

A simplified example: 一个简化的例子:

{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE OverlappingInstances   #-}

class WhatIs a b | a -> b where
  whatIs :: a -> b

instance (Show a) => WhatIs a String where
  whatIs = show

instance (Monad m, Functor m, Show a) => WhatIs (m a) (m String) where
  whatIs x = fmap show x


main :: IO ()
main = do
  let x = 1 :: Int
  putStrLn "----------------"
  {-print $ whatIs (1::Int)-}
  print $ whatIs (Just x)
  putStrLn "--- End --------"

So, I use the FunctionalDependencies to avoid type annotations, but of course, the compiler complains with 因此,我使用FunctionalDependencies来避免类型注释,但当然,编译器抱怨

   Functional dependencies conflict between instance declarations:
     instance [overlap ok] Show a => WhatIs a String
       -- Defined at test.hs:10:10
     instance [overlap ok] (Monad m, Functor m, Show a) =>
                           WhatIs (m a) (m String)
       -- Defined at test.hs:13:10

Because a can assume the value ma , and thus a conflict arises. 因为a可以假定值ma ,因此产生冲突。

However, if I could replace the first instance with something like: 但是,如果我可以用以下内容替换第一个实例:

instance (NotAMonad a, Show a) => WhatIs a String where
  whatIs = show

This problem would not present itself. 这个问题不会出现。

So far I've found this very old email that seems to propose a somewhat related solution, but I'm not sure if there are new techniques to address this... 到目前为止,我发现这个非常旧的电子邮件似乎提出了一个有点相关的解决方案,但我不确定是否有新技术来解决这个问题...

I also found the constraints package, which I'm sure has useful functions for this case, but it is sorely lacking in (simple) examples. 我还发现了约束包,我确信它对这种情况有用,但是(简单)示例中却非常缺乏。

Any clues? 有线索吗?

Edit: after user2407038 correct answer. 编辑:在user2407038之后正确回答。

So, I tried user2407038 answer below, and indeed I managed to compile the provided example. 所以,我尝试下面的user2407038答案,确实我设法编译提供的示例。 The conclusion? 结论? I should not have simplified the example so much. 我不应该这么简化这个例子。 After some thinkering with my actual example, I was able to reduce it to this: 在考虑了我的实际例子后,我能够将其减少到这个:

{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeFamilies           #-}
{-# LANGUAGE UndecidableInstances   #-}

module IfThenElseStackExchange where

class IfThenElse a b c d | a b c -> d where
  ifThenElse :: a -> b -> c -> d

instance (Monad m) => IfThenElse (m Bool) (m b) (m b) (m b) where
  ifThenElse m t e  = do
    b <- m
    if b then t else e

instance (Monad m) => IfThenElse (m Bool) b b (m b) where
  ifThenElse ma t e = do
    a <- ma
    return $ if a then t else e

But I still getting the dreaded Functional dependencies conflict between instance declarations error. 但我仍然得到Functional dependencies conflict between instance declarations错误Functional dependencies conflict between instance declarations可怕的Functional dependencies conflict between instance declarations Why? 为什么? The part after the => (the instance head , as user2407038 promptly noted) is in fact quite different, thus it does not even qualify for OverlappingInstances, as the compiler can choose the most specific one. =>之后的部分( 实例头部 ,如用户2404038及时注明)实际上是完全不同的,因此它甚至不符合OverlappingInstances,因为编译器可以选择最具体的部分。

Then what? 那又怎样?

The error is, as always, hinted by the error message. 错误信息一如既往地暗示错误。 The abcd | abc -> d abcd | abc -> d abcd | abc -> d part is not being respected by the code above. abcd | abc -> d部分未受上述代码的尊重。 So I finally tried this instead: 所以我终于尝试了这个:

{-# LANGUAGE MultiParamTypeClasses  #-}
{-# LANGUAGE FlexibleInstances      #-}
{-# LANGUAGE FlexibleContexts       #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE TypeFamilies           #-}
{-# LANGUAGE UndecidableInstances   #-}

module IfThenElseStackExchange where

class IfThenElse a b c d | a b c -> d where
  ifThenElse :: a -> b -> c -> d

instance (Monad m, c ~ b) => IfThenElse (m Bool) (m b) (m b) (m c) where
  ifThenElse m t e  = do
    b <- m
    if b then t else e

instance (Monad m, c ~ b) => IfThenElse (m Bool) b b (m c) where
  ifThenElse ma t e = do
    a <- ma
    return $ if a then t else e

Et voilà! Etvoilà!

By using (mb) in the last parameter, I was trying to indicate that the final result has the same type as the second and third parameter. 通过在最后一个参数中使用(mb) ,我试图指出最终结果与第二个和第三个参数具有相同的类型。 But the problem seems to be that the FunctionalDependencies extension does not make the same kind of instance choosing on types as OverlappingInstances , and thus considers b and (mb) "the same" for its purposes. 但问题似乎是FunctionalDependencies扩展不会使类型上的实例选择为OverlappingInstances ,因此将b(mb)视为“相同”用于其目的。 Is this interpretation correct, or am I still missing something? 这种解释是正确的,还是我还缺少某些东西?

I can still 'tell' the compiler that c is of the same type as b using the constrain c ~ b , and thus reaching the intended result. 我仍然可以使用约束c ~ b “告诉”编译器cb类型相同,从而达到预期的结果。

After reading some more material about this, I highly recomend reading this article by Oleg where he generalizes his former solutions that both I and user2407038 linked. 在阅读了更多有关此内容的材料之后,我高度推荐阅读Oleg的这篇文章 ,他将他以前的解决方案概括为I和user2407038。 I found it quite accessible. 我发现它很容易接近。

If my interpretation of the FunctionalDependencies above is correct, and TypeFamilies being presented as a more flexible solution for the same problem domain, I wonder if could use them to solve this in another way. 如果我对上面的FunctionalDependencies的解释是正确的,并且TypeFamilies被呈现为同一问题域的更灵活的解决方案,我想知道是否可以使用它们以另一种方式解决这个问题。 Oleg solution mentioned above sure uses them, of course. 当然,上面提到的Oleg解决方案肯定会使用它们。

You don't need a NotAMonad class, or rather, WhatIs is exactly this class already. 你不需要NotAMonad类,或者更确切地说, WhatIs就是这个类。

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances,
    TypeFamilies, UndecidableInstances, IncoherentInstances #-}

class WhatIs a b | a -> b where
  whatIs :: a -> b

instance (Show a, b ~ String) => WhatIs a b where
  whatIs = show

instance (Monad m, Functor m, Show a) => WhatIs (m a) (m String) where
  whatIs x = fmap show x

You don't strictly need IncoherentInstances but if you want things like whatIs (1 :: Num a => a) to work you need it. 你并不严格需要IncoherentInstances但如果你想要whatIs (1 :: Num a => a)可以使用它,你需要它。

This probably isn't the best way of doing what you want but your use case isn't clear. 这可能不是做你想做的最好的方法,但你的用例并不清楚。

Edit: more explanation: First of all: these instances are not overlapping! 编辑:更多解释:首先:这些实例不重叠! I included the full list of language pragmas. 我列出了语言编译指示的完整列表。 The error you got said that "Functional dependencies conflict between instance declarations". 你得到的错误说“实例声明之间的功能依赖性冲突”。 Say you have the following: 说你有以下内容:

class C a b | a -> b

Supposed you have two instances of C : C ab , C cd (here a is not a rigid type variable; it is just any haskell type). 假设您有两个CC abC cd实例(这里的a不是刚性类型变量;它只是任何haskell类型)。 If a is an instantiation of c (or vice versa) then b must be an instantiation of d . 如果ac的实例化(反之亦然),那么b 必须d的实例化。 This general rule may be somewhat abstract so lets look at your code: 这个一般规则可能有点抽象,所以让我们来看看你的代码:

instance (Show a) => WhatIs a String where
instance (Monad m, Functor m, Show a) => WhatIs (m a) (m String) where

Since a is any type , you are declaring that 'for any type a, whatIs a == String'. 由于a任何类型 ,因此您声明'对于任何类型a,whatIs a == String'。 The second instance declares that 'for any type (ma), whatIs (ma) == (m String)'. 第二个实例声明'对于任何类型(ma),whatIs(ma)==(m String)'。 Obviously ma is an instantiation of a (any type is an instantiation of a free type variable) but String is never an instantiation of m String . 显然ma是的实例化a (任何类型是游离型变量的一个实例),但String是从来没有的实例化m String

Why does any of this matter? 为什么这有关系? When the compiler checks to see if fundeps conflict, it only looks at the instance head ; 当编译器检查fundeps是否冲突时, 它只查看实例头 ; that is, the portion to the right of => . 也就是说, =>右边的部分。 Therefore, 因此,

instance (Show a, b ~ String) => WhatIs a b where

is saying 'for any types a,b, whatIs a == b'. 正在说'对于任何类型a,b,what is a = = b'。 Obviously, since a and b are both free variables, they can be instantiated with any other type. 显然,由于ab都是自由变量,因此可以使用任何其他类型对它们进行实例化。 So if a == (m a0) you can freely say that b == (m String) . 所以如果a == (m a0)你可以自由地说b == (m String) The fact that b must be a string becomes known if and only if the first instance is the one that is matched . 当且仅当第一个实例是匹配的实例时b必须是字符串才变为已知的事实。

Since any types match a and b , WhatIs (IO ()) b matches the first instance. 由于任何类型都匹配ab ,因此WhatIs (IO ()) b匹配第一个实例。 The second instance is used because the compiler will try to match the instances in order of specificity. 使用第二个实例是因为编译器将尝试按特定顺序匹配实例。 You can find the 'rules' for determining specificity here. 您可以在此处找到用于确定特异性的“规则”。 . The simple explanation is that WhatIs ab matches more things so it is more general, and will be used later. 简单的解释是, WhatIs ab匹配更多的东西,因此它更通用,并将在以后使用。 (In fact, the instance C a0 a1 a2 .. an where a* is a distinct type variable, is the most general instance and will always be tried last) (实际上,实例C a0 a1 a2 .. an其中a*是一个不同的类型变量,是最常见的实例,并且总是最后尝试)

Edit: Here is the general solution to your problem. 编辑: 这是您的问题的一般解决方案。 With this code, whatIs = f_map show . 使用此代码, whatIs = f_map show

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

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