属性继承的 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 必须不同,因为它们可能还有其他属性,并且可能为每种类型定义了唯一的方法。
- 如何执行此要求?理想情况下,我不必在任何子类型中指定
name
字段,但如果必须,那就这样吧。
- 如果这在 Julia 中是不可能的,建议的替代方案是什么?我能否重构此类代码以完全消除对这种行为的需求?
我该如何执行?
有测试
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"是一个动态语言模式。
结论
取决于方法更正确。
方法定义功能。
字段只是一个实现细节。
作为抽象类型的定义者,更重要的是作为使用抽象类型的方法的定义者,您非常清楚每个子类型必须具有的功能。
您不太确定实施情况。
如果您的方法将抽象类型的字段作为参数,
考虑一下它是否完全写对了
在许多编程语言中,父 类 可以要求任何子类 包含特定字段。
如果该字段是静态的,可以通过以下方式在 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 必须不同,因为它们可能还有其他属性,并且可能为每种类型定义了唯一的方法。
- 如何执行此要求?理想情况下,我不必在任何子类型中指定
name
字段,但如果必须,那就这样吧。 - 如果这在 Julia 中是不可能的,建议的替代方案是什么?我能否重构此类代码以完全消除对这种行为的需求?
我该如何执行?
有测试
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"是一个动态语言模式。
结论
取决于方法更正确。 方法定义功能。 字段只是一个实现细节。 作为抽象类型的定义者,更重要的是作为使用抽象类型的方法的定义者,您非常清楚每个子类型必须具有的功能。 您不太确定实施情况。
如果您的方法将抽象类型的字段作为参数, 考虑一下它是否完全写对了