【问题标题】:Julialang: Enforcing interfaces on Abstract TypesJulialang:在抽象类型上强制执行接口
【发布时间】:2019-06-11 01:54:42
【问题描述】:

我一直在尝试理解 Julialang 的类型系统,但一些设计方面仍然让我感到困惑。我希望有人能澄清一下。

所以这里的问题是关于抽象类型及其具体实现。从我understandJulia 来看,抽象类型不会对其具体实现施加任何约束。因此,不能保证适用于 Abstract 类型的方法将适用于该类型的具体实现。

我知道 Julia 不使用类或遵循继承。但我只是想避免在我的代码中产生各种错误。如果有不同的设计范式,那么有人可以回答下面的问题 2。

所以我有 2 个问题。

  1. 这仍然是该语言的工作方式吗?只是为了确认自博客文章以来没有任何变化。

  2. 用户如何围绕这个看似漏洞设计他们的软件?

链接帖子中的问题示例:

abstract type AbstractPerson end
abstract type AbstractStudent <: AbstractPerson end
abstract type AbstractTeacher <: AbstractPerson end

struct Person <: AbstractPerson
  name::String    
end

struct Student <: AbstractStudent
  name::String  
  grade::Int
  hobby::String
end

struct MusicStudent <: AbstractStudent
  grade::Int
end

现在,如果我在抽象类型上创建一些方法。

get_name(x::AbstractPerson) = x.name
p1 = Person("elroy")
get_name(p1)

>"elroy"

所以即使MusicStudentAbstractPerson 的子类型,MusicStudent 也没有name 属性。这意味着观察到跟随行为。

m1 = MusicStudent(10)
get_name(m1)


ERROR: type MusicStudent has no field name

Stacktrace:
 [1] getproperty(::Any, ::Symbol) at ./sysimg.jl:18
 [2] get_name(::MusicStudent) at ./In[2]:1
 [3] top-level scope at In[13]:2

所以这里的问题是Julia 允许我用一个不完整的构造函数来实例化类型变量m1。当我尝试运行该功能时,它只会给我一个错误。

也就是说,如果我为抽象类型编写函数,我不能保证该类型的每个具体实现都具有相同的接口。这似乎会使代码变得非常脆弱,因为开发人员不知道哪些类型实现了哪些属性和方法。

【问题讨论】:

    标签: julia


    【解决方案1】:

    这种行为不只是 Persons 实现中的一个错误吗?如果你真的希望行为没有例外,你可以定义一个默认方法:

    julia> get_name(p::AbstractPerson) = try return p.name catch y return "" end
    get_name (generic function with 1 method)
    
    julia> m1 = MusicStudent(10)
    MusicStudent(10)
    
    julia> get_name(m1)
    ""
    

    我认为潜在的困难可能是在 Julia 中,您不能继承名为“name”的数据字段作为对象层次结构的一部分。这里对这个真正的问题进行了很好的讨论(请参阅@forward 宏的提及):

    https://discourse.julialang.org/t/composition-and-inheritance-the-julian-way/11231

    【讨论】:

    • 啊,所以还是这样。我不确定。非常感谢。我明白了默认构造函数的意思,但我想知道语言创建者对此的意图是什么?就像他们打算如何在不陷入陷阱的情况下编写代码一样。我看到所有这些关于从数据中分离算法的抽象对话,但是没有明确的例子。我想尝试编写 Julia 代码,但只是担心我会设计错误并花很长时间尝试修复它。
    • 也感谢您提供对话链接。那里有很多很好的意见。
    【解决方案2】:

    基本答案是,在 julia 中,方法的接口被认为是定义为采用该类型元素的方法。例如AbstractArray 指定实现应实现getIndexsize。不将字段作为接口的一部分的原因是不这样​​做允许内存高效的代码,因为每种类型都可以以最明智的方式定义方法。例如,如果我想创建一个名为 Bob 的类型,它是所有名为 bob 的人的子类型,我不想每次都存储他的名字。通过使用方法,Julia 可以在未来以意想不到的方式进行扩展。

    从技术上讲,这种方法会失去“安全性”,但唯一的方法是使用可能不存在的字段编写代码,在这种情况下会出错。这种类型的安全性并没有那么有用,因为它只会给您一个编译错误,从而减慢开发速度。

    【讨论】:

    • 感谢您的来信。我想我明白你的意思。因此存在安全性损失,因为您不能再保证接口或字段存在于类型上。但权衡是通过将一些此类信息加载到方法中,这可以实现优化和速度优势。似乎范式是只向用户公开少数类型,并将大部分处理保留在包内部。这样就减少了这些安全问题出现的机会。这更有意义。我可以看到它是如何工作的。
    • 如果你定义你的接口,并且根据其他方法而不是字段来编写方法,那么就不存在安全问题。我认为可能导致此点击的是查看 abstractarray 或数字类型系统。它们都是非常复杂的类型树,但是定义一个新的类型树几乎是微不足道的,因为只需定义几个方法就可以让一切正常工作。
    • 好建议。我将看看 abstractarray 和 number 类型系统并尝试收集一些经验教训。再次感谢您的指导。
    猜你喜欢
    • 1970-01-01
    • 2017-11-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-08-13
    • 2012-10-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多