4

问题

我希望能够创建 2 data types:AB创建 2 个函数f

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

我能做到的唯一方法(据我所知)是使用type classesand instances

问题是,我不想显式写f签名 - 我希望类型检查器为我推断它。可能吗?

示例代码

{-# 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

解释

我正在编写自定义域语言编译器,因此我正在生成 Haskell 代码。我不希望我的语言的最终用户编写显式类型,所以我想尽可能使用 Haskells 强大的类型系统来推断。

如果我像这样编写函数f2 _ s1 s2 = "test"++s1++s2则不必显式编写其签名-因为编译器可以推断出它。我们可以以某种方式要求编译器推断上述示例中的签名吗?f

我很想知道解决这个问题的所有可能的“hack”,即使这个 hack 会“丑陋”,因为我正在生成 Haskell 代码,它不一定是“漂亮的”。

4

1 回答 1

5

这是一种有效的方法。如果您的 fA fB 在其推断类型中具有类型变量,则还有更多情况需要介绍。在这种情况下,以下代码将在编译时出现一些模式匹配失败。

{-# 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

这是否甚至可以帮助您使用要编译为 haskell 的语言是另一个问题。


添加提示

如果类型是多态的,您将看到 reify 给出了我上面的示例代码未涵盖的内容。

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

打印出描述 (a->a) 的内容:

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

通过一些工作,您可以将其中的 Type 拆分为 instanceD 需要的 CxtQ 和 TypeQ。

我不知道生成 (a->b) 有多大意义。您可以用新的唯一变量替换所有类型变量,这可能最好使用 Data.Generics.everywhereM 之类的东西来完成,因为数据类型有很多构造函数。

您仍然会遇到无效的问题:

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

这种获取 (a->b) 的方法可能会使您的程序崩溃:

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

这种方法允许在类型不同时选择实例,但是在 ghc 执行的稍后时间点,如果“a”和“b”不能相同,您可能会以失败告终。

instance (a~b) => Func_f (a->b) where
   f _ = unsafeCoerce id
于 2013-07-17T17:34:58.130 回答