0%

julia holy trait 简介

这是一篇简单介绍julia里面的holy trait的文章,大部分参考了这篇文章,节选了一部分将英文翻译成中文,毕竟元编程还不是很熟高级的实现还没太弄明白。

原因

由于julia采用的是多重派发和单继承,所以在对某些功能进行拓展时就很不现实,比如有一个已经实现了的功能,需要对新的类进行适配,就需要修改源函数。给一个例子:

1
2
aslist_direct(x) = [x]
aslist_direct(x::Union{AbstractArray, Tuple}) = x

这是把一个输入变量变成列表的函数,已经实现的是对于AbstractArray, Tuple不进行变换,但是还有很多不是标量,不需要进行操作的类型,此时用这个类型进行输入就会产生错误的结果。常规的解决办法就是复制上面的代码对新类型创建一个新的函数,但是重复代码是糟糕的。

我们能否对一些类型就行标注,是否为标量。一个解决的办法是通过抽象类型,使标量类型称为它的子类,我们就能写出下面的代码:

1
2
aslist(x::AbstracScalar) = [x]
aslist(x) = x

但是很多类都已经有了父类,而且在julia中只能有一个父类,所以这个办法不太行。

Trait

另一个解决办法就是采用traittrait在julia中的实现是采用一个对类型返回用来进行多重派发的类型值的函数。这被广泛应用在index,broadcast,iterator之中。

trait非常实用,因为你只需要写一写声明语句,而不需要像上面一样重复代码。而且这还狠快,因为它们是类型的pure function,julia编译器能够在编译时对它们进行优化,对它们采用静态分配到目标函数,所以在运行时不会被计算。

实现

我们首先需要创建trait函数将要返回的值的类型,它们应该是具体的类型,并且不需要任何数据。尽管它们不需要超类,实际中还是给它们一个超类,来管理trait。下面是实现

trait function

1
2
3
4
5
6
7
8
9
10
# 返回值的类型
abstract type Scalarness end
struct Scalar <: Scalarness end
struct NonScalar <: Scalarness end

# trait function
scalarness(::Type) = Scalar() #fall-back, by default everything is scalar
scalarness(::Type{<:AbstractArray}) = NonScalar()
scalarness(::Type{<:Tuple}) = NonScalar()
scalarness(::Type{<:AbstractString}) = NonScalar()

dispatch traits

1
2
3
4
aslist(x::T) where T = aslist(scalarness(T), x)

aslist(::Scalar, x) = [x]
aslist(::NonScalar, x) = x

这就是基本的trait的实现了,还有更高级的基于类型方法的,和采用元编程来使得类型稳定加速基于类型方法的trait,这些看随着学习的深入什么时候再抄过来吧。