Type inference of instance functions

The Problem

I want to be able to create 2 data types : A and B and to create 2 functions f :

  • f :: A -> Int -> Int
  • f :: B -> String -> String -> String
  • The only way I can do it (so far as I know) is to use type classes and instances .

    The problem is, that I do not want to explicit write f signatures - I want type checker to infer it for me. Is it possible?

    Example code

    {-# LANGUAGE FlexibleInstances, FunctionalDependencies, UndecidableInstances #-}
    
    data A = A{ax::Int} deriving(Show)
    data B = B{bx::Int} deriving(Show)
    data C = C{cx::Int} deriving(Show)
    
    -- I don't want to explicit say the signature is Int->Int
    -- I would love to write: 
    -- instance Func_f A (a->b) where 
    instance Func_f A (Int->Int) where 
        f _ i = i*2
    
    -- I don't want to explicit say the signature is String->String->String
    -- I would love to write:
    -- instance Func_f B (a->b->c) where 
    instance Func_f B (String->String->String) where 
        f _ s1 s2 = "test"++s1++s2
    
    -- I don't want to explicit say the signature is a->a
    -- I would love to write:
    -- instance Func_f C (a->b) where 
    instance Func_f C (a->a) where 
        f _ i = i
    
    class Func_f a b | a -> b  where
        f :: a -> b
    
    f2 _ s1 s2 = "test"++s1++s2 -- Here the type inferencer automaticly recognizes the signature
    
    main :: IO ()
    main = do 
        let 
            a = A 1
            b = B 2
            c = C 3
            a_out = f a 5
            b_out = f b "a" "b"
            c_out = c 6
    
        print a_out
        print b_out
        print c_out
    

    Explaination

    I'm writing custom domain language compiler and I'm generating Haskell code as a result. I don't want the final users of my language write explicit types, so I want to use Haskells powerful type system to infer as much as possible.

    If I write function like f2 _ s1 s2 = "test"++s1++s2 I do not have to explicit write its signature - because compiler can infer it. Can we somehow ask compiler to infer the signatures of f in the above example?

    I would love to know every possible "hack" to solve this problem, even if this hack would be "ugly", because I'm generating Haskell code and it does not have to be "pretty".


    Here's one way that kind of works. There are many more cases to cover if your fA fB have type variables in their inferred types. In that case the following code will have some pattern match failures at compile time.

    {-# LANGUAGE FlexibleInstances, FunctionalDependencies, TemplateHaskell #-}
    
    import Language.Haskell.TH
    
    data A = A{ax::Int} deriving(Show)
    data B = B{bx::Int} deriving(Show)
    
    fA A{} i = i*(2 :: Int)
    
    fB B{} s1 s2 = "test"++s1++s2
    
    class Func_f a b | a -> b  where
        f :: a -> b
    
    let
        getLetter (AppT (AppT _ x) _) = x
        getSnd (AppT x y) = y
        mkInst name0  = do
            (VarI n ty _ _) <- reify name0
            fmap (:[]) $ instanceD (return [])
                [t| Func_f
                        $(return $ getLetter ty)
                        $(return $ getSnd ty) |]
                [valD (varP 'f) (normalB (varE name0)) []]
    
        in fmap concat $ mapM mkInst ['fB, 'fA]
    
    
    main :: IO ()
    main = do 
        let 
            a = A 1
            b = B 2
            a_out = f a 5
            b_out = f b "a" "b"
    
        print a_out
        print b_out
    

    Whether this even helps you with the language you're compiling to haskell is another question.


    Added Hints

    If the type is polymorphic, you'll see reify giving something that isn't covered by my example code above.

     > :set -XTemplateHaskell
     > :m +IPPrint Language.Haskell.TH
     > putStrLn $(reify 'id >>= stringE . pshow)
    

    Prints out something that describes (a->a):

    VarI GHC.Base.id
      (ForallT [PlainTV a_1627394484] []
         (AppT (AppT ArrowT (VarT a_1627394484)) (VarT a_1627394484)))
      Nothing
      (Fixity 9 InfixL)
    

    With a bit of work, you could split that Type in there into the CxtQ and TypeQ that instanceD needs.

    I don't know how much sense it makes to generate (a->b) instead. You could go about replacing all type variables with new unique ones, which is probably best done with something like Data.Generics.everywhereM because data Type has many many constructors.

    You'll still have an issue that this is invalid:

    instance Func_f (a -> b) where
       f _ = id
    

    This approach to getting (a->b) can make your program crash:

    instance Func_f (a -> b) where
       f _ = unsafeCoerce id
    

    This approach lets the instance get chosen when the types are different, but at some later point in ghc's execution you can end up with a failure if 'a' and 'b' can't be the same.

    instance (a~b) => Func_f (a->b) where
       f _ = unsafeCoerce id
    
    链接地址: http://www.djcxy.com/p/43056.html

    上一篇: 在Scalaz状态monad中查找Shapeless HList的类型实例

    下一篇: 输入实例函数的推理