简体   繁体   English

输入实例函数的推断

[英]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 : 我希望能够创建2种data typesAB并创建2个函数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 . 我能做到的唯一方法(据我所知)是使用type classesinstances

The problem is, that I do not want to explicit write f signatures - I want type checker to infer it for me. 问题是,我不想明确写f签名 - 我希望类型检查器为我推断它。 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. 我正在编写自定义域语言编译器,因此我正在生成Haskell代码。 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. 我不希望我的语言的最终用户写出显式类型,所以我想使用Haskells强大的类型系统来尽可能地推断。

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. 如果我写这样的功能f2 _ s1 s2 = "test"++s1++s2不必明确写入其签名-因为编译器可以推断它。 Can we somehow ask compiler to infer the signatures of f in the above example? 在上面的例子中,我们可以以某种方式要求编译器推断出f的签名吗?

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". 我很想知道解决这个问题的每一个可能的“hack”,即使这个hack会“丑陋”,因为我正在生成Haskell代码而且它不一定是“漂亮”。

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. 如果您的fA fB在其推断类型中具有类型变量,则还有更多案例要涵盖。 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. 这是否有助于你使用你正在编译的语言来帮助你使用haskell是另一个问题。


Added Hints 添加了提示

If the type is polymorphic, you'll see reify giving something that isn't covered by my example code above. 如果类型是多态的,你会看到reify给出了我上面的示例代码未涵盖的内容。

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

Prints out something that describes (a->a): 打印出描述的内容(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. 通过一些工作,您可以将那里的Type拆分为instanceD需要的CxtQ和TypeQ。

I don't know how much sense it makes to generate (a->b) instead. 我不知道它产生了多少感觉(a-> b)。 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. 您可以使用新的唯一变量替换所有类型变量,这可能最好用Data.Generics.everywhereM来完成,因为数据类型有许多构造函数。

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: 获取(a-> b)的方法可能会导致程序崩溃:

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. 这种方法允许在类型不同时选择实例,但是在ghc执行的某个稍后阶段,如果'a'和'b'不能相同,则最终会失败。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM