静态类型元编程?

Li *_*oyi 21 python f# scala metaprogramming

我一直在考虑将一些Python代码移植到静态类型语言(例如F#或Scala)时我会想念的内容.库可以替换,简洁可比,但我有很多python代码,如下所示:

@specialclass
class Thing(object):
    @specialFunc
    def method1(arg1, arg2):
        ...
    @specialFunc
    def method2(arg3, arg4, arg5):
        ...
Run Code Online (Sandbox Code Playgroud)

装饰器做了大量工作:用可调用对象替换方法与状态,用额外的数据和属性扩充类等等.虽然Python允许动态猴子补丁元编程任何地方,任何人,任何人,我发现基本上所有我的元编程是在程序的一个单独的"阶段"完成的.即:

load/compile .py files
transform using decorators
// maybe transform a few more times using decorators
execute code // no more transformations!
Run Code Online (Sandbox Code Playgroud)

这些阶段基本上完全不同; 我不在装饰器中运行任何应用程序级代码,也不在主应用程序代码中执行任何ninja replace-class-with-other-class或replace-function-with-other-function.虽然语言的"动态"说我可以在任何我想要的地方这样做,但我从不在主应用程序代码中替换函数或重新定义类,因为它很快就会变得疯狂.

基本上,我在开始运行之前对代码执行单个重新编译.

我在静态类型语言中知道的唯一类似的元编程是反射:即从字符串中获取函数/类,使用参数数组调用方法等.但是,这基本上将静态类型语言转换为动态类型语言,从而失去所有类型的安全性(如我错了请纠正我?).理想情况下,我认为,我会有如下内容:

load/parse application files 
load/compile transformer
transform application files using transformer
compile
execute code
Run Code Online (Sandbox Code Playgroud)

从本质上讲,您将使用使用普通编译器编译的任意代码来扩充编译过程,该代码将对主应用程序代码执行转换.关键是它基本上模拟了"加载,转换,执行"工作流程,同时严格保持类型安全.

如果应用程序代码被borked编译器会抱怨,如果变换器代码被borked编译器会抱怨,如果变换器代码编译但没有做正确的事情,它将崩溃或编译后的步骤将抱怨最终类型不加起来.在任何情况下,您都不会通过使用反射来进行动态调度来获得运行时类型错误:它将在每一步都进行静态检查.

所以我的问题是,这可能吗?它是否已经在我不知道的某种语言或框架中完成了?这在理论上是不可能的吗?我对编译器或形式语言理论不太熟悉,我知道它会使编译步骤完整并且不保证终止,但在我看来,这就是我需要匹配的那种方便的代码 - 转换我在保持静态类型检查的同时使用动态语言.

编辑:一个示例用例将是一个完全通用的缓存装饰器.在python中它将是:

cacheDict = {}
def cache(func):
    @functools.wraps(func)
    def wrapped(*args, **kwargs):
        cachekey = hash((args, kwargs))
        if cachekey not in cacheDict.keys():
            cacheDict[cachekey] = func(*args, **kwargs)
        return cacheDict[cachekey]
    return wrapped


@cache
def expensivepurefunction(arg1, arg2):
    # do stuff
    return result
Run Code Online (Sandbox Code Playgroud)

虽然高阶函数可以执行其中的一些操作或者带有函数的对象可以执行其中的一些操作,但是AFAIK它们不能用于任何使用任意参数集的函数并返回任意类型,同时保持类型安全性.我可以这样做:

public Thingy wrap(Object O){ //this probably won't compile, but you get the idea
    return (params Object[] args) => {
        //check cache
        return InvokeWithReflection(O, args)
    }
}
Run Code Online (Sandbox Code Playgroud)

但是所有铸件都完全杀死了型号的安全性.

编辑:这是一个简单的例子,函数签名不会改变.理想情况下,我正在寻找的可以修改函数签名,更改输入参数或输出类型(ala函数组合),同时仍然保持类型检查.

Jai*_*rge 10

非常有趣的问题.

关于Scala元编程的一些观点:

  • 在scala 2.10中,scala反射会有所发展

  • 源代码转换(宏)是你正在寻找的工作:scalamacros.org

  • Java有内省(通过反射api),但不允许自我修改.但是,您可以使用工具来支持此功能(例如javassist).从理论上讲,您可以在Scala中使用这些工具来实现更多的内省.

  • 根据我对您的开发过程的理解,您可以将域代码与装饰器分开(或者如果您愿意,可以使用交叉问题),这样可以实现模块化和代码简单性.这可以很好地用于面向方面的编程,它允许这样做.对于Java,theres是一个库(aspectJ),但我怀疑它将与Scala一起运行.

  • 根据他描述python装饰器的用例的方式,它听起来确实像OP在做AOP.我知道有Java和.NET的AOP工具,但如果有更多支持AOP的语言,我也不会感到惊讶..NET AOP工具拦截编译阶段,对源进行必要的更改,然后让编译阶段运行 - 模仿OP讨论的确切流程. (2认同)

Pau*_*her 5

在不知道为什么要这样做的情况下,很难知道这种方法在 Scala 或 F# 中是否正确。但是暂时忽略这一点,至少可以在 Scala 中实现,尽管不是在语言级别。

编译器插件可让您访问树,并允许您对该树执行各种操作,所有操作均经过完全类型检查。

在 Scala 编译器插件中生成合成方法存在一些问题- 我很难知道这对您来说是否会成为问题。

可以通过创建一个编译器插件来解决这个问题,该插件生成源代码,然后在单独的通道中进行编译。例如,这就是ScalaMock 的工作方式。


Jon*_*rop 5

所以我的问题是,这可能吗?

有许多方法可以在静态类型编程语言中实现相同的效果.

您基本上已经描述了在执行程序之前对程序进行一些术语重写的过程.这个功能可能以Lisp宏的形式最为人所知,但是一些静态类型的语言也有宏系统,最着名的是OCaml的camlp4宏系统,它可以用来扩展语言.

更一般地说,您正在描述一种语言可扩展性.有许多替代方案,不同的语言提供不同的技术.有关详细信息,请参阅我的博客文章功能编程中可扩展性.请注意,其中许多语言都是研究项目,因此动机是添加新功能而不一定是好功能,因此它们很少改进其他地方发明的好功能.

包括标准ML,OCaml和F#在内的ML(元语言)语言系列专门用于元编程.因此,他们倾向于对lexing,解析,重写,解释和编译提供强大的支持.然而,F#是这个家族中去除最远的成员,缺乏像OCaml这样的语言受益的成熟工具(例如camlp4,ocamllex,dypgen,menhir等).F#确实部分实现了fslex,fsyacc和一个名为FParsec的Haskell启发式解析器组合库.

您可能会发现使用更传统的元编程形式(最常见的是DSL或EDSL)可以更好地解决您所面临的问题(您没有描述过).