为什么我可以在没有显式或隐式定义的情况下使用 `>>=` 运算符?

6

我定义了一个类型X,其定义如下:

newtype X i o = X { runX :: Int -> i -> IO o }

我已经创建了一个实例,它包含了FunctorApplicativeMonad

instance Functor (X i) where
  fmap f a = X $ \ i o -> liftA f $ runX a i o

instance Applicative (X i) where
  pure x  = X $ \ _ _ -> return x
  a <*> b = X $ \ i o -> liftA2 (<*>) (runX a i o) (runX b i o)

instance Monad (X i) where
  return = pure
  a >> b = X $ \ i o -> runX a i o >> runX b i o

显然,我迄今为止无法为>>=提供定义,因此已将其排除在外。我预期编译时会出错,但实际上它只是发出了一个警告。好吧,它并不检查类的所有方法是否被定义了,但那么我肯定不能真正使用>>=。错了,我又错了一次。令我大为惊讶的是,GHCi很高兴地评估了let x = pure 5 >>= pureControl.Monad没有导出>>=的默认定义,我也肯定没有定义它,所以这怎么可能呢?


1
我无法使您的“Functor”实例通过类型检查。它抱怨无法将类型“IO(f0 a)”与“i0 -> IO o0”匹配。 - Tikhon Jelvis
1
代码不可编译(例如,在“fmap”定义中应该是“runX a i o”)。也许你在这里粘贴了错误的版本? - duplode
那对我仍然不起作用,但是去掉 liftA 就可以了。 - Tikhon Jelvis
不,你还有一个liftA2太多了 :P - Ørjan Johansen
1
我相信 GHC 会发出 警告。但可能需要使用 -Wall 选项进行编译。此外,如果您定义了一个类,可以使用 MINIMAL 来告诉编译器实例应该提供哪些方法。通过这种方式,您可以提供相互递归的默认实现,同时让编译器知道必须实现其中至少一个方法才能使事情正常工作。 - Bakuriu
显示剩余4条评论
1个回答

9

根据您更正的定义,如果我尝试定义并使用x,那么我会得到预期的运行时异常:

λ> let x = pure 5 >>= pure :: X Int Int
λ> runX x 5 5
*** Exception: foo.hs:12:10-20: No instance nor default method for class operation GHC.Base.>>=

有两个可能的原因导致您看不到它。

第一个原因是您只运行了let语句,但从未尝试过评估结果。由于Haskell是惰性的,let x = ...实际上并没有执行任何操作。只有当您真正尝试使用它时(例如使用runX),x才会被评估,这时您将遇到错误。

另一个可能性是使用了没有指定类型的let语句:

λ> let x = pure 5 >>= pure
λ> x
5

在这里,x 在使用的单子 m 中是多态的。为了打印出像这样的多态术语的有用信息,ghci 将 m 默认为 IO,这样可以正确地给出 5,但无法告诉您有关自定义单子的任何有用信息。

这也是我所期望的,但两者都不正确。我使用了高度类型限制的函数而不是这里的 pure,并尝试将 x 直接插入 GHCi 中。它成功地运行而没有错误。然而,我使用的 IO 实际上代表了创建 WX widget,当我尝试评估它时,我得到了一个 SegFault。你有什么想法,wxHaskell 中可能会导致这种情况发生? - Kwarrtz
@Kwarrtz:这就变成了一个不同的问题,我不确定我能提供多少帮助。我认为你最好单独提出这个问题,并提供更多与你情况相关的细节。 - Tikhon Jelvis

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接