2011-06-26 81 views
11

是否可以使用自己的数据类型来模拟一个带有一些GHC扩展的函数?我想要做的是是否可以使用自己的数据类型来模拟函数?

(假想语法)

data MyFunc = MyFunc String (Int->Int) 

instance (Int->Int) MyFunc where 
    ($) (MyFunc _ f) i = f i 

inc = MyFunc "increment" (1+) 

test = inc 1

即数据与它携带一些元信息,可以进行模式匹配,但仍然可以像常规函数那样调用。现在,我知道我可以定义我自己的中缀运算符,如$$,并调用inc $$ 1,但能够使用常规函数调用语法在嵌入式DSL中非常有用。

回答

18

是的,它可以在一定程度上完成。

但首先我们需要

{-# LANGUAGE Rank2Types #-} 

让我们来定义

data M a b = M { name :: Int -> String -> String, eval :: a -> b } 

我增加更多的结构,以你的名字,所以我可以得到更好的显示支持。 )

然后允许定义一个类:

class Magic m where 
    magic :: M a b -> m a b 

instance Magic M where 
    magic = id 

instance Magic (->) where 
    magic (M _ f) = f 

现在,考虑类型:

type MyFunc a b = forall m. Magic m => m a b 

magic结果类型为(a -> b)M a b

因此它可以用作MyFunc的成员。现在,这种类型有点不满意,因为你不能使情况调度就可以了,但它确实意味着

inc :: MyFunc Int Int 
inc = magic (M (const (showString "inc")) (+1)) 

test :: Int 
test = inc 1 

的作品就好了。

我们甚至可以制作一个相当不错的方式来展示它们。尽管我们无法使用MyFunc上的节目,但我们可以将它定义为M

instance Show (M a b) where 
    showsPrec d (M s _) = s d 

然后我们就可以使我们能够适用于M a b(通过扩展任何MyFunc)走出一个M a b的功能。

m :: M a b -> M a b 
m = id 

,我们可以定义一个特殊的组合子显示MyFunc S:

showM :: MyFunc a b -> String 
showM f = show (m f) 

然后我们就可以玩了。我们可以定义组成MyFunc s。

infixr 9 .# 
(.#) :: MyFunc b c -> MyFunc a b -> MyFunc a c 
f .# g = magic (M 
    (\d -> showParen (d > 9) $ showsPrec 10 (m f) . 
           showString " . " . 
           showsPrec 9 (m g)) 
    (f . g)) 

inc2 :: MyFunc Int Int 
inc2 = inc .# inc 

test2 :: Int 
test2 = inc2 1 

bar, baz :: String 
bar = showM inc 
baz = showM inc2 

而且因为我给了足够的结构的名称,我们甚至可以为更复杂的组合正确的括号,没有不必要的括号。

*Main> showM $ inc2 .# inc 
"(inc . inc) . inc" 

*Main> showM $ inc .# inc2 
"inc . inc . inc" 

但要记住,你将无法定义任何情况下为MyFunc,因为它只能是type,而不是newtype。为了定义实例,您必须在M上定义它们,然后使用m转换为该类型,以便隐式调度具有要抓取的类型。

由于排名第二的类型,如果您在本地环境中大量使用它们,您可能还需要打开NoMonoLocalBinds和/或NoMonomorphismRestriction

+9

这是一种可怕的。我喜欢它。 –

5

不,语法f e不能重载。 f必须有类型S -> T

但是,如果您进行深度嵌入,即让您的函数构建语法树而不是计算,则仍然可以使用EDSL做很多工作。

3

您不能直接重载函数调用语法,否。

可以使您自己的自定义箭头类型---请参阅Control.Arrow。然后,您可以使用arrow notation来调用您的“功能”(您需要在源文件的顶部包含{-# LANGUAGE Arrows #-})。这对你来说是否足够取决于你的DSL的需求。

+0

++ 1用于箭头。 – fuz

+0

不要忘记应用程序!他们也有应用运营商。 – luqui

相关问题