4

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".

Wojciech Danilo
  • 11,573
  • 17
  • 66
  • 132
  • There is no way (that I know of) to have the type of a type class parameter inferred from a given implementation of the class. That could be difficult since your first example could be inferred to have type `f :: (Num a) => A -> a -> a`. Will any of the implementations of `f` make use of the first argument or is it just used to determine which version of `f` to use? – sabauma Jul 17 '13 at 17:12
  • @sabauma - I know it - but I really do not want to infer very detailed type like `Int->Int` - I would be happy if Haskell would infer it as `f :: (Num a) => A -> a -> a` or something else - if I can later use it (If its even possible). The first argument would be sometimes used - you can think of it like "this" in imperative languages. – Wojciech Danilo Jul 17 '13 at 17:22
  • @sabauma - I've edited the example - please see the comments. – Wojciech Danilo Jul 17 '13 at 17:28
  • @danillo, perhaps you could come up with something similar to how [`printf`](http://stackoverflow.com/questions/7828072/how-does-haskell-printf-work) is implemented. – sabauma Jul 17 '13 at 17:53

1 Answers1

5

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
aavogt
  • 1,308
  • 6
  • 14
  • thank you for this example - it works very well. Could you please tell me if is it possible to somehow use this technique to also support functions, which are not strictly typed, like `fA A{} i = i` ? – Wojciech Danilo Jul 17 '13 at 21:29
  • I've edited the question - added `C` data type and related instance to better show the problem. If it would be possible to use you roslution with such functions like `fA A{} i = i` it would be a lifesaver for me :) – Wojciech Danilo Jul 17 '13 at 21:38
  • I've added some more hints for getting that C (or your original A) accepted: template haskell is not known for being pleasant, so I've left you with the exercise of writing that code ;) – aavogt Jul 18 '13 at 20:29