Subtraction of church numerals in haskell

后端 未结 3 1829
情歌与酒
情歌与酒 2020-12-29 06:06

I\'m attempting to implement church numerals in Haskell, but I\'ve hit a minor problem. Haskell complains of an infinite type with

Occurs check: cannot construct th

3条回答
  •  暗喜
    暗喜 (楼主)
    2020-12-29 06:43

    The problem is that predChurch is too polymorphic to be correctly inferred by Hindley-Milner type inference. For example, it is tempting to write:

    predChurch :: Church a -> Church a
    predChurch = \n -> \f -> \x -> n (\g -> \h -> h (g f)) (\u -> x) (\u -> u)
    

    but this type is not correct. A Church a takes as its first argument an a -> a, but you are passing n a two argument function, clearly a type error.

    The problem is that Church a does not correctly characterize a Church numeral. A Church numeral simply represents a number -- what on earth could that type parameter mean? For example:

    foo :: Church Int
    foo f x = f x `mod` 42
    

    That typechecks, but foo is most certainly not a Church numeral. We need to restrict the type. Church numerals need to work for any a, not just a specific a. The correct definition is:

    type Church = forall a. (a -> a) -> (a -> a)
    

    You need to have {-# LANGUAGE RankNTypes #-} at the top of the file to enable types like this.

    Now we can give the type signature we expect:

    predChurch :: Church -> Church
    -- same as before
    

    You must give a type signature here because higher-rank types are not inferrable by Hindley-Milner.

    However, when we go to implement subChurch another problem arises:

    Couldn't match expected type `Church'
           against inferred type `(a -> a) -> a -> a'
    

    I am not 100% sure why this happens, I think the forall is being too liberally unfolded by the typechecker. It doesn't surprise me though; higher rank types can be a bit brittle because of the difficulties they present to a compiler. Besides, we shouldn't be using a type for an abstraction, we should be using a newtype (which gives us more flexibility in definition, helps the compiler with typechecking, and marks the places where we use the implementation of the abstraction):

    newtype Church = Church { unChurch :: forall a. (a -> a) -> (a -> a) }
    

    And we have to modify predChurch to roll and unroll as necessary:

    predChurch = \n -> Church $ 
        \f -> \x -> unChurch n (\g -> \h -> h (g f)) (\u -> x) (\u -> u)
    

    Same with subChurch:

    subChurch = \m -> \n -> unChurch n predChurch m
    

    But we don't need type signatures anymore -- there is enough information in the roll/unroll to infer types again.

    I always recommend newtypes when creating a new abstraction. Regular type synonyms are pretty rare in my code.

提交回复
热议问题