Why does this code using UndecidableInstances compile, then generate a runtime infinite loop?

前端 未结 2 1978
囚心锁ツ
囚心锁ツ 2021-02-05 03:37

When writing some code using UndecidableInstances earlier, I ran into something that I found very odd. I managed to unintentionally create some code that typechecks

2条回答
  •  逝去的感伤
    2021-02-05 03:56

    Here's how I mentally process these cases:

    class ConvertFoo a b where convertFoo :: a -> b
    instance (ConvertFoo a Foo, ConvertFoo Foo b) => ConvertFoo a b where
      convertFoo = ...
    
    evil :: Int -> String
    evil = convertFoo
    

    First, we start by computing the set of required instances.

    • evil directly requires ConvertFoo Int String (1).
    • Then, (1) requires ConvertFoo Int Foo (2) and ConvertFoo Foo String (3).
    • Then, (2) requires ConvertFoo Int Foo (we already counted this) and ConvertFoo Foo Foo (4).
    • Then (3) requires ConvertFoo Foo Foo (counted) and ConvertFoo Foo String (counted).
    • Then (4) requires ConvertFoo Foo Foo (counted) and ConvertFoo Foo Foo (counted).

    Hence, we reach a fixed point, which is a finite set of required instances. The compiler has no trouble with computing that set in finite time: just apply the instance definitions until no more constraint is needed.

    Then, we proceed to provide the code for those instances. Here it is.

    convertFoo_1 :: Int -> String
    convertFoo_1 = convertFoo_3 . convertFoo_2
    convertFoo_2 :: Int -> Foo
    convertFoo_2 = convertFoo_4 . convertFoo_2
    convertFoo_3 :: Foo -> String
    convertFoo_3 = convertFoo_3 . convertFoo_4
    convertFoo_4 :: Foo -> Foo
    convertFoo_4 = convertFoo_4 . convertFoo_4
    

    We get a bunch of mutually recursive instance definitions. These, in this case, will loop at runtime, but there's no reason to reject them at compile time.

提交回复
热议问题