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

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

4

3 回答 3

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

工作得很好。

我们甚至可以用一种相当不错的方式来展示它们。即使我们不能使用 show on MyFunc,我们也可以为M.

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

然后我们可以创建一个我们可以应用的函数M a b(并且通过扩展 any MyFunc)来得到一个M a b.

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

我们可以定义一个特殊的组合器来显示MyFuncs:

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

然后我们就可以玩了。我们可以定义MyFuncs 的组合。

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,因为它只能是 a type,而不是 a newtype。为了定义实例,您必须在 上定义它们M,然后使用m它转换为该类型,以便隐式调度具有可以抓取的类型。

由于 rank 2 类型,如果您在本地环境中大量使用这些,您可能还需要打开NoMonoLocalBinds和/或NoMonomorphismRestriction.

于 2011-06-26T15:54:59.170 回答
5

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

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

于 2011-06-26T09:35:20.900 回答
3

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

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

于 2011-06-26T09:59:56.847 回答