Haskell Overlapping/Incoherent Instances

后端 未结 2 1355
一个人的身影
一个人的身影 2021-02-20 01:11

I know this is code is a bit silly, but can someone explain why this isList [42] returns True whereas isList2 [42] prints False

相关标签:
2条回答
  • 2021-02-20 01:35

    The following code does the trick without requiring IncoherentInstances:

    {-# LANGUAGE FlexibleInstances #-}
    {-# LANGUAGE OverlappingInstances #-}
    
    class IsList a where
      isList :: a -> Bool
    
    instance IsList a where
      isList x = False
    
    instance IsList [a] where
      isList x = True
    
    isList2 :: (IsList a) => a -> Bool
    isList2 = isList
    
    main = do
      print (isList (42 :: Int))
      print (isList [42 :: Int])
      print (isList2 (42 :: Int))
      print (isList2 [42 :: Int])
    

    I'd recommend not using IncoherentInstances, it seems to cause a lot of trouble, as you can silently call different overloads depending on context quite easily.

    0 讨论(0)
  • 2021-02-20 01:54

    It's really quite simple. Let's ask GHCi what the type of isList2 is:

    ∀x. x ⊢ :t isList2
    isList2 :: a -> Bool
    

    This doesn't match the [a] instance (even though it could, via unification), but it does match the a instance immediately. Therefore, GHC selects the a instance, so isList2 returns False.

    This behavior is precisely what IncoherentInstances means. Actually, this is a rather nice demonstration of it.


    Hilariously, if you simply disable IncoherentInstances, we get exactly the opposite effect, and GHCi now says this:

    ∀x. x ⊢ :t isList2
    isList2 :: [Integer] -> Bool
    

    This happens because isList2 is a top-level binding not defined using function syntax, and thus subject to the Dreaded Monomorphism Restriction. So it gets specialized to the instance it's actually used with.

    Adding NoMonomorphismRestriction as well as disabling IncoherentInstances, we get this instead:

    ∀x. x ⊢ :t isList2
    isList2 :: IsList a => a -> Bool
    ∀x. x ⊢ isList2 'a'
    False
    ∀x. x ⊢ isList2 "a"
    True
    ∀x. x ⊢ isList2 undefined
    
    <interactive>:19:1:
        Overlapping instances for IsList a0 arising from a use of `isList2'
    

    Which is the expected overlapping behavior, with the instance chosen based on use and complaints if the choice is ambiguous.


    Regarding the edit to the question, I don't believe the desired result is possible without type annotations.

    The first option is to give isList2 a type signature, which prevents IncoherentInstances from selecting an instance too early.

    isList2 :: (IsList a) => a -> Bool
    isList2 = isList
    

    You'll probably need to do the same anywhere else isList is mentioned (even indirectly) without being applied to an argument.

    The second option is to disambiguate the numeric literals and disable IncoherentInstances.

    main = 
      print (isList (42 :: Integer)) >> 
      print (isList2 (42 :: Integer)) >> 
      print (isList [42]) >> 
      print (isList2 [42]) 
    

    In this case, there's enough information to pick a most-specific instance, so OverlappingInstances does its thing.

    0 讨论(0)
提交回复
热议问题