Associated Parameter Restriction using Functional Dependency

前端 未结 3 772
走了就别回头了
走了就别回头了 2021-01-21 21:54

The function f below, for a given type \'a\', takes a parameter of type \'c\'. For different types \'a\', \'c\' is restricted in different ways. Concretely, when \'a\' is any In

3条回答
  •  被撕碎了的回忆
    2021-01-21 22:38

    OK, this one's been nagging at me. given the wide variety of instances, let's go the whole hog and get rid of any relationship between the source and target type other than the presence of an instance:

    {-# LANGUAGE OverlappingInstances, FlexibleInstances,TypeSynonymInstances,MultiParamTypeClasses #-}
    
    class Foo a b where f :: a -> b
    

    Now we can match up pairs of types with an f between them however we like, for example:

    instance Foo Int Int where f = (+1)
    instance Foo Int Integer where f = toInteger.((7::Int) -)
    instance Foo Integer Int where f = fromInteger.(^ (2::Integer))
    instance Foo Integer Integer where f = (*100)
    instance Foo Char Char where f = id
    instance Foo Char String where f = (:[])  -- requires TypeSynonymInstances
    instance (Foo a b,Functor f) => Foo (f a) (f b) where f = fmap f -- requires FlexibleInstances
    instance Foo Float Int where f = round
    instance Foo Integer Char where f n = head $ show n
    

    This does mean a lot of explicit type annotation to avoid No instance for... and Ambiguous type error messages. For example, you can't do main = print (f 6), but you can do main = print (f (6::Int)::Int)

    You could list all of the instances with the standard types that you want, which could lead to an awful lot of repetition, our you could light the blue touchpaper and do:

    instance Integral i => Foo Double i where f = round -- requires FlexibleInstances
    instance Real r => Foo Integer r where f = fromInteger -- requires FlexibleInstances
    

    Beware: this does not mean "Hey, if you've got an integral type i, you can have an instance Foo Double i for free using this handy round function", it means: "every time you have any type i, it's definitely an instance Foo Double i. By the way, I'm using round for this, so unless your type i is Integral, we're going to fall out." That's a big issue for the Foo Integer Char instance, for example.

    This can easily break your other instances, so if you now type f (5::Integer) :: Integer you get

    Overlapping instances for Foo Integer Integer
      arising from a use of `f'
    Matching instances:
      instance Foo Integer Integer
      instance Real r => Foo Integer r
    

    You can change your pragmas to include OverlappingInstances:

    {-# LANGUAGE OverlappingInstances, FlexibleInstances,TypeSynonymInstances,MultiParamTypeClasses #-}
    

    So now f (5::Integer) :: Integer returns 500, so clearly it's using the more specific Foo Integer Integer instance.

    I think this sort of approach might work for you, defining many instances by hand, carefully considering when to go completely wild making instances out of standard type classes. (Alternatively, there aren't all that many standard types, and as we all know, notMany choose 2 = notIntractablyMany, so you could just list them all.)

提交回复
热议问题