【问题标题】:What is the Julian alternative to attribute inheritance?属性继承的 Julian 替代方案是什么?
【发布时间】:2017-07-27 05:52:27
【问题描述】:

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

如果字段是静态的,在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)

类型确实必须不同,因为它们也可能具有其他属性,并且可以为每种类型唯一定义方法。

  • 如何执行此要求?理想情况下,我不必在任何子类型中指定 name 字段,但如果必须,就这样吧。
  • 如果这在 Julia 中是不可能的,建议的替代方案是什么?我可以重构这种类型的代码以完全消除这种行为的需要吗?

【问题讨论】:

    标签: types abstract-class julia abstract


    【解决方案1】:

    我该如何执行?

    通过测试

    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
    

    这种“通过测试执行合同”是一种动态语言模式。

    结论

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-18
      • 1970-01-01
      • 1970-01-01
      • 2014-10-22
      • 2015-05-30
      相关资源
      最近更新 更多