fortran语言和python_对于科学计算,大家对新出来语言Julia怎么看,相比C、Python和Fortran有什么优势发展?...

这个问题是不是钓鱼的问题。。

其实Julia已经不能算是新出的语言了,Julia正式发布于2012年,现在已经八年了。八年是一个什么概念?让我们看一下大多数编程语言的出现时间(来源于wikipedia):

70年代:C&Fortran&Lisp

80年代:1980 – C++,1984 – Common Lisp&MATLAB, 1986 – Objective-C

90年代:1990 – Haskell, 1991 – Python, 1993 – Lua&R, 1995 – Ruby&Java&JavaScript&PHP

2000年:2001 –C#, 2003 – Scala, 2008-Nim, 2007 – Clojure, 2009 – Go, 2010 – Rust&Chapel

2010年以后:2011 – Dart&Kotlin&Elixir, 2012 – Julia&TypeScript, 2014 – Swift&Crystal

和科学计算有点关系的我都打上了黑体,例如说C++可以用于游戏引擎,用于图形学领域,勉强也算是和科学计算有点关系(虽然并不广泛用于通用的科学计算)。

所以你看,Julia明明和Kotlin之流几乎是同时发布的,但在知乎上总是被说成是新语言,原因很简单:无非是像Rust和Go这些语言已经向大多数程序员证明了其自身的实用性(虽然还没有说服所有人,不过至少声势在那里),而大家都已经接受了它们,但是Julia还没有,Julian在这里就没有话语权。Julia本来就是专精科学计算的语言(虽然也可以做通用编程就是了),而科学计算的领域和多数程序员的领域很多时候八杆子打不着,一般人不知道Julia一点都不奇怪,这样子的话总被说成是新语言也正常。。。

纵观历史,我们会发现专门为科学计算设计的编程语言根本不多。所以你看Fortran用了那么多年,因为在各种各样的编译器优化实现以前,科学计算要性能的地方,不乱搞指针又支持各种数组的Fortran是刚需。

长文预警,主要内容:

1.Julia为什么很快?

2.Julia与别的高性能语言有什么区别?

3.Julia的社区与生态

4.Julia的缺点

5.一些总结

首先是一个彩蛋:

下列截图来自于科幻剧The 100 的Season 3 Episode 12,讲述了由人工智能塑造的世界末日后的地球的故事。

里面AI的代码是Julia写的。

所以你看我大Julia怎么样也是上过电视剧的编程语言(逃)。

我个人当然是很看好Julia的,只不过Julia实在是要走非主流风格,所以一般程序员根本不能忍受。

首先我们要先理解Julia的语言设计,因为Julia和别的语言走的是不同路线,才能了解Julia的哲学,才能明白Julia和C,Python相比有什么优缺点,很多人完全误解了Julia的运作机制(我不知道他们到底从哪里得出的结论),把别的语言的东西直接带入到Julia里面就会出问题,就好比刚学Haskell的人会以为Haskell里面的class是面向对象里面的class一样。

一.Julia为什么很快?

但凡有过一点计算机基础的人都知道,任何高级语言都不能直接在机器上执行而要先转换成机器码,因此一个很重要的问题在于,如何将一个用抽象方式书写的程序转换成一个高效执行的机器码。首先我们要明确一点,一个全能的编译器是不存在的(然而很多希望提高Python/R运行速度的人都或多或少带有这种幻想,总想着是不是有一种神奇的JIT技术能够在不改动编程语言和代码的情况下,奇迹般加速Python/R)。编译器仅有的信息就是这个程序,它必须生成等价的正确程序,优化能做多少,取决于它能有多少信息,所以真正重要的问题在于:合适的抽象手段是什么?

C语言提出了一个方案:C有着简单的类型系统,一个类型总是对着一个固定大小的内存区域(不管是int,float还是复合的类型),并且编译时所有的类型都已知。再与同时期Lisp的相对比:类型都不知道,因此内存大小不固定,所以要用type tag标记一个值,从而运行时得知其类型,并且只能存放void*,不能存放值,因而存在着指针跳转(最好的例子就是Lisp的单链表了,什么都能装)。所以这样设计的静态类型系统能够良好的编译成优化的代码。

但是这并不意味着动态语言天生就比静态语言慢,考虑以下事实:

1.假设我们写了一个非常动态的Lisp程序,它把各种各样的不同类型的东西装在一个单链表里,那么在C语言重新实现一个相同的功能,不见得会快到哪里去。因为问题的本质就带有可变类型,至于用type tag还是在C语言中用enum+union type不实质影响运行速度。

2.我们刚刚说的是一个很保守的Lisp的编译器,因为根据Lisp的语义,一般情况下类型是不确定。但这是一般情况,我们可以假设我们把一个(比较简单的,例如一个纯函数的)C语言的程序翻译成了Lisp的程序(指针的使用也对应转化),那么我们可以想象出这样一个编译器,它可以从main开始,逐步证明出每个变量与每个函数返回值的类型(这是有可能的,因为C类型系统比较简单,完全推理是可能的),从而完全消除掉类型的动态性,进而生成优化代码。

3.更复杂的静态类型系统,允许一个类型对着多个内存布局,例如在允许子类化的系统当中,一个东西是A,则运行时可以为A的任何子类,这可以导致不良的代码生成。而且静态类型系统可以直到运行的时候才检查类型(一些dependent type的编程语言)。

当然,如果我们纵观历史上的大多数动态语言,你会发现他们通常比静态语言慢,但这并不是一个必然结果,而仅仅是说,大多数动态语言设计的时候没有选择要性能而更加倾向于自由性,因此2中那种理想情况往往实现不了(Python和Ruby这类动态语言把类当字典,可以随意改动属性,导致了那种类型推断不可行),而大多数(常见的)静态语言的类型系统也都设计的性能良好(通过区分非虚与虚函数消除运行类型检测),但看看Java和Haskell的性能就知道性能不是静态天生就有的东西。

Julia的创始人很敏锐地觉察到了这一点。实际上,科学计算并不需要过多的动态性,只要消除掉一些动态性,编程语言就能够完成上面的2(可以证明类型的稳定性)从而获得性能,并且仍然可以“看上去”很动态。比如说Javascript可以随便给一个类加属性(把类当字典用),这对于科学计算程序没有太大用处,反而很伤害性能。另外一点就是像Python和Javascript可以重定义(覆盖)一个类,这是很不良的行为,尽管可能并没有人这么做,但是(一个比较好实现)编译器会采取保守策略。在Julia中重定义类型是禁止的。但是可以重定义函数,因为在科学计算中有这个需求(因为经常要实验代码,很方便)。

很多人都以为LLVM的JIT自动让Julia很快,其实不是。很多语言的JIT是Profile-based JIT, 例如根据代码运行时的统计数据来加快运行,类似于CPU上的分支预测原理。然而Julia的JIT完全不是profile-based。Julia的JIT更像是一个运行时的模板编译器(运行时编译)。这是因为Julia的所有函数都是泛型, 换句话说,全是模板。考虑这样一个Julia函数:

function f(x)

return x*x

end

等价于C++的

T f(T x){

return x*x;

};

所以对于每一个不同的类型T都会产生不同的编译代码。Julia的JIT技术和Rust/Clang的LLVM编译器实现没有实质上的区别,只不过Julia把编译推到了运行时(而且还有GC的问题),这和做科学计算的人的工作流程是有关的,在REPL中一个一个代码块的边执行代码边做实验,而不是先把代码全部写好再编译执行,所以不可能一次性获得所有的信息。基于C++的REPL也必须带着所有的模板运行时编译(见ClangJIT,和Julia一个原理)。

我不知道 @Andy Seeder 为什么在他的回答下说jit部分的优化技巧大量来自纯理论解释和实测数据/经验,而这两部分是相当割裂的,即使julia宣称是一个「面向jit的语言」,也没有一个trivial的profile思路来指导人「面向jit优化」。普通开发者无从得知jit过程中获得和未获得哪些优化,完全是一个黑箱。

按照这个逻辑,人们同样也无法理解如何在基于LLVM的Rust和基于Clang的C++中优化,我已经指出了Julia的JIT和这两个没本质区别(有GC除外),当然我知道C++有专门的工具可以查看生成的代码中到底哪些优化有哪些没有(这倒是Julia比较欠缺的),但你要说大多数普通开发者会用我是不信的。再说了,那种“一个trivial的profile思路”在任何地方都根本就不存在,loop会不会unroll,constant有没有传播,函数会不会inline,可不可以开fastmath这种东西从来就不直观,更何况还和指令架构有关(应该这么说,要靠经验摸索。。。),编译器优化本来就是很古怪的东西,Julia论坛上找出LLVM优化的Bug又不是一次两次了。

还有一些别的因素会影响性能,可以看一下Biojulia最近写的一篇文章https://biojulia.net/post/hardware/​biojulia.net

,我就不说了,重点是Julia的语言设计是Julia高效的核心,而不是别的原因。网上也有一些Benchmarks,足以佐证Julia的高效性。

2.Julia与别的高性能语言有什么区别?

首先和Python一样,Julia是动态语言,在REPL里面写代码很方便(不过LLVM的JIT有overhead,这倒是个问题。未来可能会慢慢解决这个问题)。

其次,Julia比C和Fortran灵活的多。Julia的类型系统非常丰富,支持了泛型,子类化和联合类型。然而要注意到一点,与Typescript(和绝大多数静态语言)恰好相反,Julia的类型系统完全就不是为安全设计的,仅仅是为了获得更多的性能与提供代码复用。丰富的类型系统可以携带更多信息,因此编译的时候可以利用这些信息获得良好优化。所以但凡问出“要写类型干嘛不用Rust,Rust不是更安全”这种问题的,都是没搞懂Julia为什么要有这么多类型标注。类型标注只能用来做Multiple dispatch,换言之根据类型来选择不同的方法,除此之外几乎没有别的用处。所以下列代码:

f(x::Integer) = 1

f(x::AbstractFloat) = 1.0

和别的编程语言的类型标注完全不是一个作用。

丰富的类型系统+Multiple dispatch可以少写很多代码,但是偷懒并不是最终目的,代码复用才是。引数学家庞加莱的话就是:“数学家用一个名称替代不同事物”。很多语言都能模拟Multiple dispatch,但是只有在Julia中这才是一种被倡导的实践。单独一个人用Multiple dispatch没有什么用,最多偷偷懒,但是整个社区都这么做呢?每个库的函数都具有多态性呢?当基数很大的时候,才能体现出Multiple dispatch的威力,每个函数都多态,叠加在一起就可以创造有指数级别的多态。举个简单的例子而言,我写的求和函数不仅可以作用在内置的Int,Float,Complex上,还可以作用在用户定义的含单位物理量、定点小数甚至有限域上。另外一个例子是Julia的broadcast,其作用相当于一个map:

sin.([1,2,3])==[sin(1),sin(2),sin(3)]

这个‘.’是一个语法糖,其展开为broadcast(sin,[1,2,3])。但是不仅仅作用在内置类型上,任何人都可以定义自己的容器类型然后支持broadcast语法,只要多重派发broadcast对应函数。(之所以用broadcast,是因为多个map的串联会有中间结果,但是Julia的broadcast可以融合多个broadcast在一起)。

Multiple dispatch的流行(和简单的包安装操作)自然也鼓励了大家自定义类型。自定义的类型是一等的,和内置类型一样平起平座,所以你可以放心使用这些类型(不管多复杂),也可以获得良好的性能; 这和Numba之类的是不一样的,就我上次看Numba的时候,Numba还只能优化少数几个类型,用户自定义的类都不行,高级特性不能用那还不如写C语言。Julia提供有各种各样的类型以在不同场合下产生最优效果。光是数组就有一大堆:见Github上的组织JuliaArrays和JuliaMatrices 。

当然你可以在C++实现同样的功能,可是有几个问题:

1.不是人人都会搞模板并实现相同的功能,但是几乎每个Julian都知道怎么写多重派发和泛型。实际上很多时候出现的情况就是大家互相学习(=抄+改进)对方的代码(学习Julia的一个重要方法之一)。这也是Julia为什么要坚持着open source的原则。很多时候好的代码是可以直接被开发团队吸收到Base库里面。

2.Julia的社区更加团结,如果你觉得一个包实现的不太好的话,可以直接在Github/discourse上和作者开讨论然后提交PR,实际上Julia很多不同库是互相依赖的,有需求向上游做个feature request或者自己PR,也可以自己另写一个库,只要实现同一套接口的话,使用起来就没有区别。Discourse上有很多高手也很乐意帮助优化你的程序。最近就有一个例子:Home · ParallelKMeans.jl​pydatablog.github.io

有一个人想要在Julia里面实现KMeans,然后大家就各出主意,把这个代码直接从串行优化到并行,然后速度超过了sklearn(不过我不太确定到底是不是一个算法,如果sklearn用的是elkan/Lloyd的话确实是超过了)。

因为在Julia里面做一个库是这么容易,所以大家也很乐意分享代码,即使那些人并不是专业的程序员。这也是开源的一大好处之一。从实践上看Julia比C++更好写一些,虽然模板比Julia的多重派发还要强大,对于熟练的C++程序员来书说写起来应该也没什么区别,但是社区的影响反而更大。

和Python比的话,在科学计算方面,Julia设计得更优秀。纯Python没有性能,用C++写Binding的话扩展性不好(而且本质上还是在写C++),两个语言交互的话debug也会很不方便,对开发者很不友好。但是Python的历史积累更加丰富一些,如果想做的东西在Python已经有解决方案的话,Python是更好的选择。

3.Julia的社区与生态

很多人都吐嘈过Julia生态很小,但是要注意一点:Julia目前主要是面向研究人员,所以你会看到很多库还在快速更新当中,而且时不时就break一下API。另外,很多人希望在Julia中找到Matlab中的一个功能X,但却频频失望。Matlab很多工具箱中封装的是已经成熟的工具,而不是拿来做研究的东西(打个比方,Matlab提供一个框架,用户在框架下做东西,而Julia的用户则直接做的是新框架),所以有很多东西Julia是暂缺的。要是你是一个机器学习从业者,就会觉得Julia的机器学习生态真是贫瘠,很不方便,但要是你想要结合微分方程和神经网络的话,Julia就有很多工具了(这实际上就是DifferentialEquations.jl和Flux.jl两个库的合作了),这也算是一个研究方向。

实际上Julia还是有很多有特色的库的,我举几个例子就我所知,Julia至少有三个原生实现的概率编程的库(PPL): SOSS.jl, Turing.jl和Gen.jl,这三个库的上游又有一大堆概率分布,贝叶斯推理还有随机过程的库,就不提了。

HomotopyContinuation.jl,求解多项式方程组的零点。性能也不错。

Convex.jl,Optim.jl及其相关的一大堆做优化的库。知乎上很多其他人都提到了,这个库也是历史比较悠久的库了。

Floops.jl和LoopVectorization.jl,这两个库提供宏来自动并行化迭代循环(不是线程并行,是SIMD并行)。这两个项目真的很有意思,刚出不久就立马被Julia别的线性代数库引用了。

GLM.jl,一个广义线性模型的库,ApproxFun.jl,以函数为基做线性拟合。

Measurements.jl和Unitful.jl 物理单位和不确定度的计算

BioJulia的一些库,例如Biosequence.jl,性能不错,功能当然没有R与Python齐全。

CUDAnative.jl和CuArray.jl,用LLVM后端把Julia代码编译到GPU上运行。很多Julia的库都直接调用这个库来获得GPU支持。

量子相关的库,知乎上也有用Julia做量子计算的大佬。

前面也说了,各种矩阵库,做起计算来特别方便

其实对于不少的研究人员来说,Julia的社区倒是已经足够大了。虽然在数据可视化和IDE等一些方面做的确实不太好,也因为这种功能让做科学计算的人来写和维护确实比较困难,毕竟不是专业的,很多人都直接PyCall也不愿用原生实现的Plots。但是有也很多东西吸引了研究人员。我就拿矩阵库来举例子,Julia有专门对小矩阵操作进行优化的库。小矩阵这个东西确实不是什么不的了的东西,但是有能力实现快速小矩阵运算的人不多(要看不同的论文),能够提供这种库的语言也不多,很多时候性能瓶颈就在这种小的东西上面(举例:3x3矩阵的奇异值分解在一个大循环里面)。就这么一个小小因素就足以让一些研究人员选择Julia了。

Julia的社区(以discourse为例)有来自各行各业的人,从语言创始人、到做生物信息。数值计算中有很多问题在Discourse上有详尽的讨论,例如浮点数表示和误差分析,内存使用与程序优化,怎么样设计更加通用的程序,在上面看看也可以学到很多东西,问问题也很快有人回复。

由于Julia始终是面向科学计算的,Julia的设计一定把科学计算放在首位。举几个语言设计方面的例子:

1.Julia1.3新加入了可组合的线程并行机制(目前还在实验),这个机制是深度优先而非广度优先的,大多数科学计算任务没有实时性要求,因此选用深度优先更加内存友好(相反,在服务器领域应该用广度优先,要不然会导致有的请求等待太久)。

2.Julia在1.0前最有名的讨论(最令人津津乐道的)Taking vector transposes seriously,在极其冗长的讨论,考察了线性代数的历史以及其它编程语言的设计之后最终得出了现有的符合直觉的线性代数系统。这个issue引领了Julia以后一系列taking X seriously的issue。

3.Julia对浮点数标准的支持是一流的。体现在很多函数怎么处理NaN,Inf这些特殊值的问题上,例如sin(Inf)是多少,clamp(a,b,NaN)应不应当返回NaN,Github上一搜一大堆这种Issue。

4.性能。编译器开发团队非常重视性能,每个版本都会做regression test,Github上也有相应的issue,相当与说,Julia的编译器有相当多的样本来进行测试。Julia的Base库就是一个很好的例子,因为Base是完全纯Julia实现的,所以如果编译器能够良好优化Base的函数,那么有理由相信他也能良好优化用户的代码。另外Julia团队似乎有一些推广Julia的项目,例如帮助MIT做气象学的同事把Fortran代码迁移到Julia上,最后还提速了三倍的故事(原本的期望是只有不要慢三倍就好)。。。

总而言之,Julia对数值计算可以说是非常上心了。当然也必须承认社区的生态还不够完善,社区的人手也不够,所以很多东西也都不方便(比如万恶的包管理器,我现在升级到1.4才觉得好用了一点;还有Package compiler也是,总算稳定了一些)。总之如果在别的语言已经有稳定的工作栈的话,就没有必要迁移过到Julia来了。除非:

1.你想做相关方面的研究,所以要手撸轮子

2.你对原有语言的性能很不满意,想自己写一个。

四.Julia的缺点

这个回答下很多人压根就没喷到点子上,真是要把我笑死。什么既要XX又要XX,编程语言又不是只有性能和易用这两个评测标准,你让Haskell和Typescript这种为安全设计的脸往哪里搁。Julia最大的问题就是不安全(那种静态语言人口中的)。因为Julia离经叛道,因为Julia名摆着自己标注类型又能编译还说是动态语言(笑)。Julia的问题就是静态语言和动态语言交互的时候可能出现的问题:

1.抛错的时候,错误很长,有很多底层函数也出现在错误栈中,这个目前开发团队列为要解决的问题之一

2.JIT有第一次启动的时间,而且编译越来越慢(一部分LLVM的锅)

3.编译的函数可能因为新添加的方法失效(这个问题很麻烦),是目前要重点解决的问题之一

4.因为编译慢,所以很多原生实现的库载入很慢(而Julia有很多库自举),等未来二进制编译稳定下来应该就可以部分解决问题了。

另一个就是Julia没有别的语言那种接口也没有继承,所以很多时候都要靠看API文档来实现接口。因为不是强制的所以有点不爽。不过话说回来,考虑到科学计算的复杂性,类型安全的接口应该是不可能写出来的,写出来也很verbose(见Haskell的几个数组库的设计。。。),光是迭代器的接口就可以难哭一大堆人,好比是Rust比C难写一般。

其它的问题倒是暂时性,网不好,生态欠缺,倒不太影响。当初Julia的初始人早就打算好花个十几年时间来推动Julia社区的发展(见Alan Edelman在获得 IEEE-CS Sidney Fernbach Award 的展示“Power of Language”)https://www.youtube.com/watch?v=nwdGsz4rc3Q​www.youtube.com

最后一点。如果你想学Julia的话,一定要去找英文资源。到discourse.julialang.org/上是最好的选择上,也可以加入Julia Stack。(大多数资源都要梯子)

我总听到有人吐嘈Julia学习资料很少,这当然或多或少是事实。学习新语言的最好路径就是泡论坛,啃文档和博客或者看看别人的开源代码(Julia的代码可比STL的代码好读多了),和C++这种老语言不一样,这些有语言往往有很多纸质书作资料,也可以直接搜索到答案。https://www.juliabloggers.com/​www.juliabloggers.com

Julia编程语言的文档写很详细,覆盖了方方面面(有点枯燥就是),各类教程也有很多。各个库的文档取决于作者的精力,原来确实不怎么样,不过现在有点改观,没例子的API文档确实比较难懂就是了。

最后说一句:怎么写既抽象又高性能的Julia(或者任何编程语言)程序是一个要不断实践的事情,要从经验中获得。我从没见过有系统教授怎么写一个抽象又高性能的C程序书,我只见过剖析某个具体框架设计和源代码的书。那种抽象的书是写不出来的,因为它将空洞又无物,根本不存在一个万有的方法或者公理来设计良好的抽象程序。所以要想学好Julia,首先就要避免先入为主地认为Julia很容易写。作为动态语言Julia很容易用,但是要开发高性能Julia程序还是要花时间积累的。

我帮@Andy Seeder说一下Julia的几个缺点吧,要不然一个个反驳实在是累死我:

1.关于Julia的内存布局

在Julia1.0后,Julia主要的类型有可变结构体,不可变结构体和联合体,其它不可构造的其他抽象类型。主要的考虑是Julia一定是一门内存安全的编程语言,设计和Java类似,采用引用模型。

第一点,关于栈与指针

目前Julia暴露的语义只有堆分配,但如果编译器能够证明变一个可变结构体变量不会逃逸的时候,可以把它分配到栈上(否则这个变量就会在销毁之后被引用)。不可变结构体无关经要,反正拷贝就好,问题不大。

第二点,关于指针的问题:不可变结构体是值,因此可以存在多个拷贝。不可变结构体总是直接存放在数组里面,例如Vector{Int},因此内存连续。

可变结构体用指针表示,因为我们不能有多个拷贝,否则会不一致。因此可变结构体一点是用指向结构体的指针放在数组里面。

其它不可构造的其他抽象类型也总是用指针放在数组里面,例如Array{Number}

联合类型总是直接存放在数组里面,而不用指针,具体怎么存放不在这里展开。

有几个问题万一我们要按值存放的可变结构体的数组怎么办?一个办法是用不可变结构体的数组,然后每个更新整个结构体,LLVM会消除掉多余的赋值。但能不能直接inline存储?

栈上分配?

问题在于,允许此等分配策略必然是Unsafe的,比如说一个栈上分配的变量却逃逸了(放进了一个数组里面),很难确保安全性。另外假设有一个按值存放的可变结构体的数组A,其中一个变量持有该数组某个元素的指针,GC无法简单的释放数组A,因为会导致变量指向一个被回收的内存区域。所以此等语义完全不安全。

当然对C++这种可以手动管理内存的语言来说,这些都不是问题。

@Andy Seeder的回答里面提到了我,然而他误解了我的意思:

Julia的抽象类型的容器是被做了派发(aka “模板偏特化”)以非组合类型的方式实现的,并不是我要表达的意思。他以为的是:

“Array{Int,1}和Array{Number,1}两个东西有不同的实现,是靠Julia程序员在Julia写了两个派发来区分Array{T,1}的T是不是抽象类型的缘故”

我要表达的是:

这是编译器的选择,Julia的编译器对待抽象类型和具体类型的内存布局有不同的选择,这个选择是一致的,也就是说,这两个东西的内存布局不同。Number是抽象的,而Int是具体的,所以编译器选择把Int直接放在Array里面,而用间接指针存放Number。那这个是不是就叫派发了?

就算不用数组,一样会遇到类似的问题,例如:

function f(x::Array{Number})

y = x[1]

return y

end

编译器只能证明x[1]的类型是Number,不能进一步缩小范围,这个时候这个变量也是用指针表示的。而且Array是Julia的内置类型,是在C++里面实现的,不是在Julia里面实现的。所以哪里搞出来的“派发”。编译器要特殊对待Array,是因为Array是基本类型之一(好比数组是C的一个基本类型),Array的语义要直接编码在编译器里的,要不然谁知道Array的意思是一个装着T元素,维度为N的容器。所以这并不是Array被特化了,而是抽象类型和具体类型的区别,Array只不过是把这些东西打包装在一起而已,行为是一致的。

当然你也可以用多重派发可以来做std::vector,你完全可以搞出各种奇葩类型的布局。你可以构造自己的MyArray,然后你爱怎么实现怎么实现,只要你还是用struct定义类型的话,你只能派发类型的构造器,不能派发类型。。。但这并不是Array的实现方式,也和抽象类/具体类没有一点关系。本质上,如果你允许多个类型构造器存在,就不可能阻止这件事情发生(所以C++等语言一样可以)。Julia和别的语言的区别是:Julia尽量没有暴露底层实现的,因为multiple dispatch的存在让我们根本就没必要管到底怎么实现的,只要定义了方法,那就可以一致的使用。而且Julia也不鼓励这么做,Julia建议对不同布局的类型采用不同的类型标志。

第二个问题是抽象类的实例化问题。抽象类不能被构造的原因,和void*指针的原理类似。你可以取整数的指针然后构造一个int指针,但void*只能来源于其它指针的转换,你不能直接取XX的指针为void*。因此抽象类不能直接通过类型构造器构造,但是抽象类可以用子类的值来构造(要不然为什么叫子类化的类型系统)。Array{Number,1}没有神奇的让Number可以构造,不信的话:

x = Number[1,1.0,1+1im]

y = x[1]

typeof(y)==Int而不是Number

y::Int是对的,y::Number也是对的,一个东西可以从属于多个类型vector但不能写vector,

能不能写vector这个要取决于number抽不抽像。然而Julia的抽象类型和C++的抽象类根本就不是一个概念。。。一个C++的抽象类必然对着Julia的抽象类,但是C++的具体类可以对着Julia的抽象类型。我们就假设number是一个不可构造的抽象类好了,那么写不写那个指针根本没区别,既然vector是唯一合法的类型,反正就是指针。(好比,C的void*指针就是any,你只要写any[]等价于(void*)[]既可,写个any*[]完全就是多此一举)。

而vector不能实存的的原因和类型系统根本没有一点关系。本质上是C++编译器特殊对待了这种容器,不是从类型合法性,而是从不可构造的角度驳斥了此类型的存在性。如果把vector视为一个类型,那么它是合法的,然而一个类型合不合法和可不可构造根本没有关系,这是人们对类型系统的常见误解。@Andy Seeder和我说的合不合法根本不是一回事。我说的合法,就是type theory里的那个合法。他说的合法,是从能不能分配来说的。然而恰好是后面这一点证明了模板不在类型系统中。。。那为什么list又是合法的类型?list和vector从泛型角度看有什么区别,他们的签名完全一样好吧?

问题的根源就在于,本来Julia和C++对Array泛型的解释就不一样。Array{Number}的含义本来就是说,一个装了各种数字(含子类)的数组,这和vector不一样,vector的意思是,装了number(不含number子类)的数组。vector的意思是,装了number指针的数组,只不过指针可以用来转子类。list的意思是一个装了各种数字(含子类)的数组。vector之所以在Julia里面没有对应物,那是因为根本连语义都对不上。。。Julia里也有指针,没有指针并不是不写*number的原因,而纯粹是因为语义不需要。。。

你可能感兴趣的:(fortran语言和python_对于科学计算,大家对新出来语言Julia怎么看,相比C、Python和Fortran有什么优势发展?...)