首页 文章

静态类型元编程?

提问于
浏览
21

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

@specialclass
class Thing(object):
    @specialFunc
    def method1(arg1, arg2):
        ...
    @specialFunc
    def method2(arg3, arg4, arg5):
        ...

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

load/compile .py files
transform using decorators
// maybe transform a few more times using decorators
execute code // no more transformations!

这些阶段基本上完全不同;我不在装饰器中运行任何应用程序级代码,也不在主应用程序代码中执行任何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

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

如果应用程序代码被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

虽然高阶函数可以执行其中的一些操作或者带有函数的对象可以执行其中的一些操作,但是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)
    }
}

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

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

5 回答

  • 2

    非常有趣的问题 .

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

    • 在scala 2.10中将有scala reflection的发展

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

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

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

  • 5

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

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

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

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

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

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

  • 4

    不知道为什么你很难知道这种方法在Scala或F#中是否是正确的方法 . 但是现在忽略了这一点,至少在Scala中实现它是可能的,尽管不是在语言层面 .

    编译器插件使您可以访问树,并允许您对该树执行各种操作,所有这些操作都是完全类型化的 .

    在Scala编译器插件中有一些issues生成合成方法 - 我很难知道这对你来说是否会有问题 .

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

  • 0

    您可能对source-to-source program transformation systems (PTS)感兴趣 .

    这些工具解析源代码,产生AST,然后允许用户定义代码的任意分析和/或转换,最后从修改的AST重新生成源代码 .

    有些工具通过程序界面提供解析,树构建和AST导航,例如ANTLR . 许多更现代的动态语言(Python,Scala等)已经构建了一些自托管解析器库,甚至Java(编译器插件)和C#(开放编译器)也正在接受这个想法 .

    但大多数这些工具只提供对AST的程序访问 . 具有表面语法重写的系统允许您使用具有被操纵的语言的语法的模式来表达“如果您看到此更改它” . 这些包括Stratego/XTTXL .

    根据我们的经验,操纵复杂的语言需要复杂的编译器支持和推理;这是70年来人们构建编译器的规范教训 . 所有上述工具都无法访问符号表和各种流量分析;毕竟,程序的一部分如何运作取决于在远程部分采取的行动,因此信息流是基本的 . [如另一个答案的评论中所述,您可以使用这些工具实施符号表/流量分析;我的观点是他们没有给你这么做的特别支持,这些都是艰巨的任务,更糟糕的是在具有复杂类型系统和控制流的现代语言上] .

    我们的DMS Software Reengineering Toolkit是一个提供上述所有设施(Life After Parsing)的PTS,需要花费一些成本将其配置为您的特定语言或DSL,我们试图通过提供这些off-the-shelf for mainstream languages来改进 . [DMS提供用于构建/管理符号表,控制和数据流的显式基础设施;这已被用于实现Java 1.8和完整C 14的这些机制 .

    DMS也被用于定义meta-AOP,这些工具可以为任意语言构建AOP系统并应用类似操作的AOP .

    在任何情况下,只要您直接或间接修改AST,您就不能保证"type safety" . 你只能通过编写transformation rules来获得这一点,而不是't break it. For that, you'需要一个定理证明器来检查每个修改(或其组成)是否远远超出了现有技术水平 . 但是,您可以小心编写规则,并获得非常有用的系统 .

    您可以在此example that defines and manipulates algebra and calculus中使用DMS查看DSL的规范示例和使用表面语法源到源重写规则的操作,这些规则可以保留语义 . 我注意到这个例子简单易懂;特别是,它没有展示DMS提供的任何流动分析机械 .

  • 10

    理想情况下,我正在寻找的可以修改函数签名,更改输入参数或输出类型(a.l.a.函数组合),同时仍然保持类型检查 .

    我同样需要在类型安全的世界中提供R API . 这样我们就可以将来自R的丰富科学代码带入Scala的(类型)安全世界 .

    合理

    有关尝试改进当前在SparkR中的完成方式的信息,请参阅https://spark-summit.org/east-2015/functionality-and-performance-improvement-of-sparkr-and-its-application/ . 另请参阅https://github.com/onetapbeyond/renjin-spark-executor以获得简单的集成方式 .

    在解决方案方面,我们可以使用Renjin(基于Java的解释器)作为运行时引擎,但使用StrategoXT Metaborg来解析R并生成强类型的Scala API(就像您描述的那样) .

    StrategoTX(http://www.metaborg.org/en/latest/)是我所知道的最强大的DSL开发平台 . 允许使用允许编写语言(更长故事)的解析技术来组合/嵌入语言 .

相关问题