带有函数应用程序的类型化抽象语法树

Tim*_*imC 5 dsl f# abstract-syntax-tree

我正在尝试编写一个可以表示函数应用程序的类型化抽象语法树数据类型.

到目前为止我有

type Expr<'a> =
    | Constant    of 'a
    | Application of Expr<'b -> 'a> * Expr<'b> // error: The type parameter 'b' is not defined
Run Code Online (Sandbox Code Playgroud)

我不认为F#中有一种方法可以在最后一行写出"for all b"之类的东西 - 我是否错误地接近了这个问题?

Tom*_*cek 10

通常,F#类型系统的表达力不足以(直接)定义类型化的抽象语法树,如您的示例中所示.这可以使用F#中不支持的通用代数数据类型(GADT)来完成(尽管它们在Haskell和OCaml中可用).在F#中使用它会很好,但我认为它使语言更复杂一些.

从技术上讲,编译器抱怨是因为'b没有定义类型变量.但是,当然,如果你定义它,那么你会得到Expr<'a, 'b>具有不同含义的类型.

如果你想在F#中表达这一点,你必须使用基于接口的解决方法(一个接口可以有一个泛型方法,它为你提供了一种方法来表达exists 'b你需要的约束).这可能会很快变得非常丑陋,所以我认为这不是一个好方法,但它看起来像这样:

// Represents an application that returns 'a but consists
// of an argument 'b and a function 'b -> 'a
type IApplication<'a> =
  abstract Appl<'b> : Expr<'b -> 'a> * Expr<'b> -> unit

and Expr<'a> = 
  // Constant just stores a value...
  | Constant    of 'a 
  // An application is something that we can call with an 
  // implementation (handler). The function then calls the
  // 'Appl' method of the handler we provide. As this method
  // is generic, it will be called with an appropriate type
  // argument 'b that represents the type of the argument.
  | Application of (IApplication<'a> -> unit) 
Run Code Online (Sandbox Code Playgroud)

要表示表达式树(fun (n:int) -> string n) 42,您可以编写如下内容:

let expr = 
  Application(fun appl -> 
    appl.Appl(Constant(fun (n:int) -> string n), 
              Constant(42)))
Run Code Online (Sandbox Code Playgroud)

评估表达式的函数可以这样写:

let rec eval<'T> : Expr<'T> -> 'T = function
  | Constant(v) -> v   // Just return the constant
  | Application(f) ->
      // We use a bit of dirty mutable state (to keep types simpler for now)
      let res = ref None
      // Call the function with a 'handler' that evaluates function application
      f { new IApplication<'T> with
            member x.Appl<'A>(efunc : Expr<'A -> 'T>, earg : Expr<'A>) = 
              // Here we get function 'efunc' and argument 'earg'
              // The type 'A is the type of the argument (which can be
              // anything, depending on the created AST)
              let f = eval<'A -> 'T> efunc
              let a = eval<'A> earg
              res := Some <| (f a) }
      res.Value.Value
Run Code Online (Sandbox Code Playgroud)

正如我所说,这是一个非常极端的解决方法,所以我认为实际使用它并不是一个好主意.我认为这样做的F#方式是使用无类型的Expr类型.你能写一些关于你项目总体目标的更多信息(或许有另一种好的方法)?