Generics 强制F中内部DSL的现有类型#

Generics 强制F中内部DSL的现有类型#,generics,f#,Generics,F#,为AST(一个非常简单的表达式树)提供DU 而且 x = Add [(Var "s"); (Val 2.0)] 此外,我希望它可以从其他程序集中使用(通过打开一些东西) 我真正的应用程序是一个类似但更大的DU,用于真正的AST。我在F#4.8上 到目前为止有效的是 let (+) a b = Add [a; b] x = (Var "s") + (Val 2.0) 但是在这里,我仍然需要手工包装“s”和“2.0”。我想避免这种包装 我尝试了其他

为AST(一个非常简单的表达式树)提供DU

而且

x = Add [(Var "s"); (Val 2.0)]
此外,我希望它可以从其他程序集中使用(通过打开一些东西)

我真正的应用程序是一个类似但更大的DU,用于真正的AST。我在F#4.8上

到目前为止有效的是

let (+) a b =
    Add [a; b]
x = (Var "s") + (Val 2.0)
但是在这里,我仍然需要手工包装
“s”
“2.0”
。我想避免这种包装

我尝试了其他几种方法:

声明类型扩展和接口,并同时使用静态类型参数和接口约束:

首先是界面

type IToExpression =
  abstract member ToExpression : Expression

type Expression with
  member this.ToExpression = this

type System.String with
  member this.ToExpression = Var this

type System.Double with
  member this.ToExpression = Val this

let (+++)
    (a : 'S when 'S :> IToExpression)
    (b : 'T when 'T :> IToExpression) =
    Add [a.ToExpression; b.ToExpression]
静态解析的类型参数也是如此

let (++) a b =
  let a = (^t : (member ToExpression: Expression) a)
  let b = (^t : (member ToExpression: Expression) b)
  Add [a; b]
编辑:但正如本文所指出的(由于我草率的复制),这种方法需要更多的工作才能解决真正的问题

但是
++
++++
都无法在想要的表达式(即行)中进行类型检查

let x = "s" ++ 2.0
let x = "s" +++ 2.0
我通读了一遍

  • /类型扩展
我的理解是,使用一些额外的代码可以转换一切,但将接口重新安装到现有类型上,并将其与静态解析的类型参数混合以应用成员约束是一项挑战


有办法解决这个问题吗?

这里有几个微妙的问题

首先,SRTPs仅适用于
内联功能。这是因为它们不能被编译成IL(因为IL不支持这种约束),所以它们必须在编译期间被解析。静态地。这就是为什么它们是“静态解析的”。
inline
关键字使编译器能够这样做

let inline (+) a b = ...
其次,您所引用的通用类型是什么?是什么类型的?我想你的意思是它是
a
b
的类型,但你没有这样声明,所以它只是一些随机的泛型类型。它需要绑定到以下参数:

let inline (+) (a: ^t) (b: ^t) = ...
第三,从你的例子来看,你实际上是想让
a
b
属于不同的类型,不是吗

let inline (+) (a: ^a) (b: ^b) = ...
第四,扩展方法不计算静态解析的类型,因此您不能在
String
float
上定义
ToExpression
,并期望它工作。通常的技巧是在一个特殊类上声明所有方法,该类的存在只是为了保存这些方法:

type ToExpressionStub() =
    static member ToExpression s = Var s
    static member ToExpression f = Val f
然后人们可能会认为这是可行的:

let inline (+) (a: ^a) (b: ^b) =
  let a = (ToExpression : (static member ToExpression: ^a -> Expression) a)
  let b = (ToExpression : (static member ToExpression: ^b -> Expression) b)
  Add [a; b]
但不会的。这是因为静态解析的约束不能应用于具体类型,只能应用于类型变量,如
^a
^b
。那怎么办呢

我们可以给函数一个额外的参数,如下所示:

let inline (+) (dummy: ^c) (a: ^a) (b: ^b) =
  let a = (^c : (static member ToExpression: ^a -> Expression) a)
  let b = (^c : (static member ToExpression: ^b -> Expression) b)
  Add [a; b]
然后我们必须在每次调用时传递一个值
到expressionStub()

let x = (+) (ToExpressionStub()) "foo" 2.0
type ToExpressionStub() = 
    static member val Value = ToExpressionStub()
    ...

let inline (+) (a: ^a) (b: ^b) = doAdd ToExpressionStub.Value a b
当然,这非常不方便,因此我们将添加另一个带有三个参数的中间函数,并从操作符
(+)
调用它:

快到了!但这也不太管用:在
let b=…
行上,我们得到一个警告,“这个构造使代码不那么通用…”,然后在使用站点上,我们得到一个错误,我们不能使用
string
float
,这取决于哪个先到

发生这种情况的原因非常模糊。编译器看到两个具有相同形状且应用于相同类型的约束,并确定它们必须是相同的约束,因此,
^a=^b
。为了打破这种僵局,我们可以简单地更改约束的形状,使其不同:

let inline doAdd (dummy: ^c) (a: ^a) (b: ^b) =
  let a = ((^a or ^c) : (static member ToExpression: ^a -> Expression) a)
  let b = ((^b or ^c) : (static member ToExpression: ^b -> Expression) b)
  Add [a; b]
请注意,它们现在分别应用于
^a或^c
^b或^c
,而不仅仅是
^c
。这还有一个额外的效果:我们不再局限于在
ToExpressionStub
中枚举的类型。我们可以使用任何定义了非扩展方法
ToExpression
的类型。例如,
表达式本身:

type Expression =
  | Add of list<Expression>
  | Var of string
  | Val of float
  with
    static member ToExpression (e: Expression) = e
最后,为了降低运行时分配成本,我通常使用
ToExpressionStub
的单例实例,而不是每次调用都创建一个新实例:

let x = (+) (ToExpressionStub()) "foo" 2.0
type ToExpressionStub() = 
    static member val Value = ToExpressionStub()
    ...

let inline (+) (a: ^a) (b: ^b) = doAdd ToExpressionStub.Value a b

底线是,是的,你可以做到,但请仔细考虑你是否应该做到


在实际操作中,这种欺骗实际上阻碍而不是帮助发展。当然,一开始它可能看起来非常整洁和聪明,但几个月后,您将看到自己的代码,无法理解发生了什么。请相信我的经验:必须在
Val
Var
中包装值是一种特性,而不是bug。程序代码读的比写的多得多。不要打自己的脚。

读到这篇文章,我想起了我试图修改其中一个任务库,使其与选项和结果包装器集成时的情景……感谢您的详细回答。我认为一些错误(内联、静态成员)我可以自己解决,但是使用伪值来快速处理编译器泛化类型可能会结束这个实验。在封闭世界的场景中,使用接口很容易,这里的问题是集成现有类型。TypeClass和模块函子可能会有所帮助。但这两种方法在F#中都不可用。我在我的完整示例中尝试了这种方法,并同意:它不值得付出代价。这个想法是用我可以用来在Jupyther笔记本中快速编写内容的东西来包装一个很好的功能框架(只使用核心ML构造),以生成一个更稳定的表示。因此,我认为阅读维护不太重要,但对实现的关注使我不再关注。然而,这是值得探索的。
> let x = 2.0 + "foo"
Add [Val 2.0; Var "foo"]

> let y = x + "bar"
Add [Add [Val 2.0; Var "foo"]; Var "bar"]
type ToExpressionStub() = 
    static member val Value = ToExpressionStub()
    ...

let inline (+) (a: ^a) (b: ^b) = doAdd ToExpressionStub.Value a b