Instance function output type

Problem

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

  • f :: A -> Int -> Int
  • f :: B -> String -> String -> String

The only way to do this (as far as I know) is to use type classes and instances .

The problem is that I don't want to explicitly write f subscriptions - I want the checker type to do this for me. Is it possible?

Code example

 {-# 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 ab | 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 = fa 5 b_out = fb "a" "b" c_out = c 6 print a_out print b_out print c_out 

Explaination

I write my own domain language compiler, and as a result I generate Haskell code. I don’t want the end users of my language to write explicit types, so I want to use the powerful Haskells type system to output as much as possible.

If I write a function like f2 _ s1 s2 = "test"++s1++s2 , I should not explicitly write my signature, because the compiler can output it. Can we somehow ask the compiler to print the signature f in the above example?

I would like to know all the possible “hacks” to solve this problem, even if this hack is “ugly” because I am generating Haskell code and it should not be “beautiful”.

+4
source share
1 answer

Here is one way to do this kind of work. There are many more cases to close if your fA fB has the type of variables in their intended types. In this case, the following code will have some pattern matching errors 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 ab | a -> b where f :: a -> b let getLetter (AppT (AppT _ x) _) = x getSnd (AppT xy) = 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 = fa 5 b_out = fb "a" "b" print a_out print b_out 

It will even help you with the language that you compile in haskell, this is another question.


Added hints

If the type is polymorphic, you will see that reify gives something that does not apply to my sample code above.

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

Prints out what 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 little work you can split this type into CxtQ and TypeQ this instance of D needs.

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

You will still have a problem that this is not true:

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

This approach to getting (a-> b) may cause your program to crash:

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

This approach allows the instance to choose when the types are different, but at some later point in the execution of ghc, you may fail if "a" and "b" cannot be the same.

 instance (a~b) => Func_f (a->b) where f _ = unsafeCoerce id 
+5
source

Source: https://habr.com/ru/post/1491916/


All Articles