数学上的变量类型为 Variable <: Expression
,使用 name
字段唯一区分;常数为抽象类型 Constant <: Expression
。表达式为抽象类型 Expression
,不含分支、循环等结构,使用类型及类型参数来区分不同类型的表达式。例如,二元运算表达式的类型为 BinaryOpExpr{op <: Function, T <: Expression, S <: Expression}
,其中 op
为二元运算对应的 Julia 函数,T
、S
分别为参数的类型;设变量 x
、常数 a
,则 a^x
的类型可以是 BinaryOpExpr{typeof(^), Constant, Variable}
,也可以是 BinaryOpExpr{typeof(^), Expression, Expression}
,前者提供了更多关于表达式的信息,根据此信息可以其化简为 exp(log(a) * x)
,不过也更消耗资源。
通过重载函数,让一个不含分支、循环等结构的 Julia 函数作用在变量或表达式上生成一个 Expression
、作用在常数上生成一个 Constant
、作用在普通的 Julia 变量就照常运行。例如,设变量 x
,则 exp(x)
返回一个 Expression
、exp(Constant(1))
返回一个 Constant
、exp(1)
返回一个 Float64
。
等式、不等式、微分式、积分式等皆应作为 Expression
的子类型,在添加新的数学对象时除了要实现对应的函数和类型,还应实现对应的 Expression
子类型并重载运算规则。
Roger
2019 年3 月 20 日 08:03
2
我觉得不要做成严格subtype,做成trait更好,因为我发现很多package或多或少会需要一些符号计算的功能。
这样当别的package想做customize的时候只要用trait标记自己的object就可以利用这些已有的工具。
trait 虽然在文档上有看到过,不过是什么东东呀,可以给些例子或资料吗?
Roger
2019 年3 月 20 日 10:36
4
Julia里用的是holy trait,因为Julia是duck type,所以一个object能做什么取决于它有什么方法,我们可以定义一些property来在编译时期确定这个object是否有某种功能。
abstract type VariableType end
struct Variable <: VariableType end
struct Constant <: VariableType end
VariableType(x::Foo) = Variable()
VariableType(x::Goo) = Constant()
some_operation(x::Foo, y::Goo) = some_operation(VariableType(x), x, VariableType(y), y)
# implementation
some_operation(::Constant, x, ::Constant, y) = #blabla
some_operation(::Variable, x, ::Constant, y) = #blabla
# etc.
这个会让实现复杂一些,但是好处就是可以扩展到其它package自己定义的类型里面去。
特征是不是还可以有类似继承的关系,例如:
abstract type VariableType end
abstract type Constant <: VariableType end
struct ZZ <: Constant end
然后具有 ZZ
这个 trait 的变量也具有 Constant
。
Roger
2019 年3 月 20 日 14:47
6
是的,原理和功能和subtype能实现的差不多,但是能方便加到别的对象上去。
否则类似的东西已经有人写过了。
Roger
2019 年3 月 21 日 07:37
8
我其实是想说另外一个:
功能上这些都已经完善了,不过都没法直接利用duck typing拓展到一个以及定义好的 Expression
类里去。我觉得在一些数值库里提供一定的符号计算功能还是很方便的。
下面简单介绍几点设计思路,并没有完全实现,希望各位可以指出不足之处。
约定:下文中提及的「变量」、「函数」和「表达式」指的是数学意义上的,若需要表示 Julia 中的则用「Julia 变量」、「Julia 函数」和「Julia 表达式」指代。
抽象类型 MathStruct
指代所有要支持的数学结构,如实数、有理数等。若一个类型 T
是 MathStruct
的直接子类型,则说明 T
对应一个数学结构,比如表示全体复数可用 CC <: MathStruct
表示。若有 T <: S <: MathStruct
,则 T
对应的数学结构为 S
的子集,比如 NN <: ZZ <: QQ <: RR <: CC <: MathStruct
。可以自定义新的数学结构,但应定义为抽象类型,以便其他人复用代码。
所有变量 x
属于某个 MathStruct
,其可在声明时指定,例如 @var x::CC
会定义一个 CC
上的变量。函数的定义域和值域与之类似,例如 @func f(::ZZ)::ZZ
会定义一个定义域为 ZZ
的、值域为 ZZ
的函数。
函数可以计算,比如 (f + g)(x) = f(x) + g(x)
、(f ∘ g)(x) = f(g(x))
。微分视为函数,(D ∘ sin)(x) = D(sin(x)) = cos(x) * D(x)
意味着 (D ∘ sin) = cos * D
。若将关于不同变量 x_1, x_2, \dots , x_n 的微分记为 d_1, d_2, \dots , d_n ,则以其为基底,在 +
和 ∘
以及下可张成一个交换环。
1 个赞
然后有没有专门用于转换(as…)/代入(subs)和运行(eval)的Julia函数?
代入肯定是要有的,转换我觉得不需要有,每个符号表达式应该有字段来表示其所属 Mathstruct
。执行根本就没有对应的语义,符号表达式的任何处理不会改变系统状态。