属性继承的 Julian 替代方案是什么?

What is the Julian alternative to attribute inheritance?

在许多编程语言中,父 类 可以要求任何子类 包含特定字段。

如果该字段是静态的,可以通过以下方式在 Julia 中实现相同的效果。

julia> abstract Fruit

julia> type Apple <: Fruit end

julia> type Orange <: Fruit end

julia> type Banana <: Fruit end

julia> color(::Apple) = :red
color (generic function with 1 method)

julia> color(::Orange) = :orange
color (generic function with 2 methods)

julia> color(::Banana) = :yellow
color (generic function with 3 methods)

但是,如果字段是动态的,这将不起作用。在下面的示例中,我想要求 Pet 的任何子类型包含字段 name.

julia> abstract Pet

julia> type Cat <: Pet
           name::String
           hairless::Bool
       end

julia> type Dog <: Pet
           name::String
       end

julia> abstract Bird <: Pet

julia> type Parrot <: Bird
           name::String
           color::Symbol
       end

julia> type Conure <: Bird
           name::String
       end

julia> feet(::Cat) = 4
feet (generic function with 1 method)

julia> feet(::Dog) = 4
feet (generic function with 2 methods)

julia> feet(::Bird) = 2
feet (generic function with 3 methods)

类型 do 必须不同,因为它们可能还有其他属性,并且可能为每种类型定义了唯一的方法。

我该如何执行?

有测试

using Base.Test 
@testset "Field Contract" begin
    for sub in subtypes(Pet)
        @test fieldtype(sub, :name) == String 
        #will error if no field names string.
        #will fail if it is has wrong type
    end
end

如何在子类型上自动填充它

从技术上讲,使用元编程可以做到这一点。 我推荐它。 这只是概念的 hack 证明:

macro declare_abstract(typename, fields...)
    quote
        abstract $(esc(typename))
        const $(esc(Symbol(:__abfields_,typename)))  = $(esc(fields))
        $(esc(typename))
    end
end

error("Please don't use this in real code") #prevent trivial copy paste

macro declare(type_expr)
    @assert type_expr.head == :type
    @assert type_expr.args[2].head == :(<:)
    parent_typename::Symbol = type_expr.args[2].args[2]
    if isdefined(Symbol(:__abfields_, parent_typename))
        @assert type_expr.args[3].head == :block
        abfields = eval(Symbol(:__abfields_, parent_typename)) #Read a globel constant. Iffy practice right here -- using eval in a macro

        push!(type_expr.args[3].args, abfields[1].args...)
    end
    type_expr
end

用法示例:

@declare_abstract Pet (name::String), (owner_id::Int)
@declare type Cat <: Pet
end

检查一下:

?Cat
...

Summary:
    type Cat <: Pet
Fields:
    name     :: String
    owner_id :: Int64

等等?你为什么说不要这样做?

这是一种代码味道。 依赖抽象类型的字段不应该做。 Julia 还不够成熟,无法制定这些约定, 与标准解决方案。 这里我说说我自己的经历。

如果您的方法采用抽象类型作为参数。 那么它应该取决于实现类型的方法; 但不在实现类型的字段上。 这也适用于 Informal Interfaces

当你发现你需要一个没有这个字段的子类型时,它会使你的代码不灵活。 它发生在我身上。 我想我知道所有的子类型,但后来我意识到我想把别人的东西作为子类型包装起来,而这个字段对此毫无意义。我所有的代码都中断了

我不是要在这里进行组合与继承的辩论。虽然这是相关的。

例子

例如, 如果您需要处理不符合您假设的宠物怎么办。 也许是你包装牲畜包裹的东西。 这些宠物实际上 Bees 它们没有名称字段,因为它们没有名称。 他们有一个 hive 字段,但它是一个 Int.

如果你使用的是方法,你会没事的: 假设您有一个 get_identifier 方法

对于您已经准备好的类型: get_identifier(x::Union{Dog,Cat})=x.name

然后介绍与Bee类型的兼容性 您只需添加 get_identifier(x::Bee)="Member of hive# $(x.hive)"。 您的所有代码都有效。

另一方面,如果您的代码中到处都是 x.name,那么它就会全部崩溃。 当然,您可以向构造函数添加一些内容,它会自动设置一个名称字段。 (当我发现自己处于这种情况时,我就是这么做的)。 但这是一个黑客和维护负担。 当然在这个虚构的例子中小

异常

当然这个规则有一个例外: 当你真的确实知道所有子类型的字段时。 例如,如果我编写一个解决特定问题系列的数学求解器。我有一个 AbstractResults 类型,其中我解决的每种问题都有自己的具体子类型,用于存储这种结果的特殊因素;然后我知道我家只有5种可能的问题。 所以我知道 AbstractResults 只有 5 个具体子类型, 我知道他们因此都有我给他们的实现;并且没有其他字段集有意义。 (如果都是非常简单的类型是可行的)。 然后就可以了。 只是不要错了。

测试

您也可以使用一些测试代码检查方法的实现

using Base.Test 
@testset "Method Contract" begin
    for sub in subtypes(Pet)
        @test method_exists(get_identifier, (sub,))
    end
end

这个"enforcing contracts with tests"是一个动态语言模式。

结论

取决于方法更正确。 方法定义功能。 字段只是一个实现细节。 作为抽象类型的定义者,更重要的是作为使用抽象类型的方法的定义者,您非常清楚每个子类型必须具有的功能。 您不太确定实施情况。

如果您的方法将抽象类型的字段作为参数, 考虑一下它是否完全写对了