朱莉娅的多重派单

2020-07-06 03:02:18

我不时听说我的帖子对于初学者来说有点太深奥和复杂了。我完全同意:尤其是在我关于如何从头开始构建约束解算器的最新系列中,这可能是一个永无止境的系列。我确实喜欢写博客,而且还会继续写下去,但这个系列不同。

朱莉娅是一种相对年轻的语言,使用它的人不是太多。它在科学计算中蓬勃发展,但我相信它可以用于通用计算(可能在启动时间不太相关的地方),也就是说,我确实使用它与Franklin一起创建了这个博客,我在这里也写了一些关于它的内容。

本系列试图向初学者解释Julia的一些核心概念,也许还有一些包。我,这里解释这些东西的人,绝对不是这些方面的专家。如果你读过这个博客,那么你可能知道,在我学会了让他们比那些一辈子都在做这件事并且是这方面的专家的人更好地沟通之后,我正试着直接解释他们的事情。专家的博客有时对我来说很难理解,也许对你来说也是如此。我会让他们校对我的博客文章,以确保我的解释没有错;)。

在每种编程语言中都有一些函数,它们的目的是提供代码的结构和可重用性。这些函数有一个名称和一些参数。它们的定义如下。

在Python中,可以用add(2,3)调用它,它会给出预期的5,但也会给出';23";,";2";,";23";这取决于您询问的是谁:d。

这会导致错误TypeError:只能将字符串(不是整型)连接到字符串,这也是有意义的。嗯,我想说确实是这样,但是Javascript做了一些不同的事情:

当然,在像C++这样的语言中,这一切并不那么简单,因为我们需要引入类型:

每个人都很清楚,它接受两个int,然后返回一个int。因为它是一种编译语言,所以当尝试使用add(";2";,3)调用它时,我们会直接收到错误。

现在有什么意义呢?我们可以在这里看到不同的概念,比如C++中的静态类型和JS和Python中的动态类型。他们都有利有弊,这可能是显而易见的。其中一个更容易推理,其他的可能更容易讨价还价。

然后,我们可能会发现该函数不像add(";2";,3)那样工作,然后抛出一个错误,或者它会解决问题并返回答案。

JS中的情况基本相同。有可能会有一些扩展名,比如TypeScript,事情可能会发生不同的情况。我最近没有用这些语言中的任何一种编程,所以请记住这一点。

在C++中,由于每个变量都有一个静态类型,所以会有更多的事情发生,人们可以直接检查这是否合适。这意味着,正如我们稍后可以看到的那样,可以有更多同名的函数。我将在该部分解释函数重载和多次分派之间的区别;)。

在所有这些语言中,我们都可以有这样的类,这样我们就可以有一个类制造商,我们可以定义add(self,thing)或类似的东西,并且可以用Manufacturer.add(Thing)调用函数。这可以被看作是单一的调度。调度基本上是决定调用哪个函数的过程。在这里,这取决于制造商的类型。是制造商还是盒子?对于Box,我们可能已经定义了一个类Box,并在其中添加(self,box)。

对于在Julia中编写代码时间更长的人来说,这听起来可能是个奇怪的概念。实际上,在写到这个问题时,我在想:如果我一直在使用其中一种语言的多个分派(就像在ConstraintSolver中),我该如何做一些事情呢?

是的,在Python中有@SingleDispatch这样的单一分派方式,但考虑到我在搜索时发现的主要帖子都是3-4年前的,我怀疑是否有很多人在使用它:d。

既然这样,让我们来看看“朱莉娅”中的一个例子:

只是为了展示一下定义单行函数的一种简洁的小方法……。(或者在Julia中这样称呼:方法)。

有趣的是,当您在Julia REPL(read-eval-print循环)中键入以下内容时,您会得到:

调用该函数的工作方式与Python基本相同(目前从用户角度来看)。不过,它对字符串不起作用。

Julia和+表示字符串:Julia是一种数学语言,+是可交换的,而连接字符串不是。因此";2";+";3";不是";3";+";2";。因此,朱莉娅决定用*代替。例如,它对于数字是可交换的,但对于矩阵是不可交换的。

好的,我在哪里打断我自己了?.。啊,是的,所以我们现在在REPL中有一个只有一个方法的Add函数。看起来我们可以加一个新的,对吗?

正如Reddit上正确指出的那样:我主要使用“功能”这个词。在Julia中,函数和方法实际上是不同的。有一个具有许多不同实现的+函数:称为方法。

好吧,这不起作用,因为它仍然允许所有类型的输入(并在稍后当它不起作用时抛出一个错误),因为编译器无法决定调用哪个函数方法。

再次执行一个小步骤:让';检查调用Add(";2";,";3";)Julia&>;Add(";a";,";b";)错误:MethodError:没有匹配*(::Int64,::String)的方法最接近的候选项是:*(::Any,:Any.)。at operators.jl:529*(::Missing,::AbstractString)at missing.jl:174*(::t,::t)WHERE T<;:Union{Int128,Int16,Int32,Int64,Int8,UInt128,UInt16,UInt32,UInt64,UInt8}at int.jl:54.Stacktrace:[1]Add(::String,::String)at int.jl:54.Stacktrace:[1]Add(::String,::String)at int.jl:54.Stacktrace:[1]Add(::String,::String)at int.jl:54.

该错误发生在调用2x时,其中它认为2是整数,而x=";2";是字符串。它给我们提供了它可以繁殖哪种类型的信息。

让我们在int.jl:54处选择一个:*(::t,::t)其中T<;:Union{Int128,Int16,Int32,Int64,Int8,UInt128,UInt16,UInt32,UInt64,UInt8}。

这告诉我们,当这些类型中的两个数字相同时,我们可以将它们相乘。所以UInt8和UInt8,但稍后我会介绍那个语法。

您可能想知道这些*函数方法中有多少:357是您在键入以下命令时得到的答案。

这会给你一个很长的列表,里面有各种奇怪的类型,有时会分布在几行中。我是说这是什么?:D。

[345]*(A::LinearAlgebra.LQ{TA,S}其中S<;:AbstractArray{TA,2},B::Union{DenseArray{tb,1},DenseArray{tb,2},Base.Re解释tArray{TB,1,S,A}where S where A<;:Union{SubArray{T,N,A,I,true}其中i<;:Union{Tuple{Vararg{Real,用法:DenseArray WHERE N WHERE T,DenseArray},Base.Re解释tArray{TB,2,S,A}WHERE A<;:UNION{SubArray{T,N,A,I,TRUE}WHERE I<;:UNION{Tuple{Varg{Real,N}where N},Tuple{AbstractUnitRange,Varg{Any,N}where N}WHERE A<;:DenseArray WHERE N WHERE T,DenseArray。:Tuple{Vararg{Base.MultiplicativeInverses.SignedMultiplicativeInverse{Int64},N}WHERE N}WHERE A<;:UNION{Base.Re解释tArray{T,N,S,A}WHERE S WHERE A<;:UNION{子数组{T,N,A,I,TRUE}WHERE I<;:UNION{元组{变量{实数,N}WHERN},元组{AbstractUnitRange,Varg{Any,N}WHERN}WHERE A<;:DenseArray WHERE N WHERE T,DenseseArray。用法:UNION{tuple{vararg{Real,N}where N},tuple{AbstractUnitRange,vararg{any,N}where N}where A<;:DenseArray where N where T,DenseArray},Base.ReshapedArray{TB,2,A,MI}where Base.2,A,MI}where MI<;:Tuple{Vararg{Base.MultiplicativeInverses.SignedMultiplicativeInverse{Int64},N}where A<;:Union{Base.Re解释tArray{T,N,S,A}where S where A<;:Union{。用法:UNION{Tuple{Vararg{Real,N}where N},Tuple{AbstractUnitRange,Vararg{Any,N}where N}where A<;:DenseArray where N where T,DenseArray}where N where T,SubArray{T,N,A,I,true}where I<;:Union{Tuple{Varg{Real,N}where N},Tuple{AbstractUnitRange,Vararg。:tuple{Vararg{Union{Int64,AbstractRange{Int64},Base.AbstractCartesianIndex},N}WHERE N}WHERE A<;:UNION{Base.Re解释tArray{T,N,S,A}WHERE S WHERE A<;:UNION{SubArray{T,N,A,I,TRUE}WHERE I<;:UNION{{Varg{Real,N}where N},Tuple{Abt。用法:DenseArray WHERE N WHERE T,DenseArray}WHERE N WHERE T,Base.ReshapedArray{T,N,A,MI}WHERE MI<;:Tuple{Vararg{Base.MultiplicativeInverses.SignedMultiplicativeInverse{Int64},N}WHERE A<;:UNION{Base.Re解释tArray{T,N,S,A}WHERE S WHERE A<;:UNION{子数组{T,N,A,I,TRUE}WHERE I<;:UNION{元组{可变{实数,N}WHERE N},元组{抽象未。用法:DenseArray where N where T,DenseArray}where N where T,SubArray{T,N,A,I,true}where I<;:Union{Tuple{Vararg{Real,N}where N},Tuple{AbstractUnitRange,Vararg{Any,N}where N}where A<;:DenseArray where N where T,DenseArray}where N where T,DenseArray},SubArray{TB,2,A,I,用法:Union{Base.Re解释tArray{T,N,S,A}where S where A<;:Union{SubArray{T,N,A,I,true}where I<;:Union{Tuple{Varg{Real,N}where N},Tuple{AbstractUnitRange,Varg{Any,N}where N}where A<;:DenseArray where N where T,DenseArray}where N where T,Base.Reshaped。用法:Union{Base.Re解释tArray{T,N,S,A}where S where A<;:Union{SubArray{T,N,A,I,true}where I<;:Union{Tuple{Varg{Real,N}where N},Tuple{AbstractUnitRange,Varg{Any,N}where N}where A<;:DenseArray where N where T,DenseArray}where N where T,SubArray{T,:UNION{Tuple{vararg{Real,N}where N},Tuple{AbstractUnitRange,Vararg{Any,N}where N}其中A<;:DenseArray where N where T,DenseArray}where N where T,DenseArray}})where{TA,TB}in LinearAlgebra at/home/ole/packages/julia-1.4.1/share/julia/stdlib/v1.4/LinearAlgebra/src/lq.jl:180。

你看到那个滚动条了吗?太神奇了!因为我现在就在这里,我想不管怎样向更多的人提及这个诀窍。

您可能想看看那个特定的方法定义。至少我对看到这种疯狂很感兴趣。最后,你可以看到那张单子,然后拿到号码。这里是345。在REPL中键入该数字并使用CTRL+Q,然后它就会为您打开。可能是Vim或Nano或其他格式(取决于您的设置)。

如果您希望在编辑器中查看它,则需要在~/.julia/config/Startup.jl文件中更改ENV[";julia_edit";]。我用了该死的让我们再谈一次多重派单。这基本上是多个分派:,这个函数有357个方法,它们都被称为*,它们就在那里,周围没有任何类。仍然可以决定调用哪个方法,这是重要的部分。

在单一分派语言中,第一个参数只能有不同的类型,然后编译器检查哪个函数/方法适合第一个参数。在Julia中,正如名称Multiple Dispatch所暗示的那样:它查看所有参数。

函数add(x::string,y::string)x<;y?";$x$y";:";$y$x";endfunction add(x,y::string)返回add(y,x)endadd(x::string,y)=";$x$y";

在我们继续之前,让我们简短地提一下:使用内联IF,其语法与其他一些语言的语法相同。及:它使用$进行变量插值,并且在Julia中返回最后调用的表达式,因此在本例中我们不需要返回。

Julia&>Methods(Add)#4泛型函数的方法";Add";:[1]Add(x::String,y::String)in Main at REPL[9]:2[2]Add(x,y::String)in Main at REPL[10]:2[3]Add(x::String,y)in Main at REPL[13]:1[4]Add(x,y)in Main at REPL[3]:1。

我决定在这里把add变成可交换的,尽管它叫做add,而不是+,但不管怎样…。在克服这一点的同时,让我们调用其中的一些方法:

这仍然有效,即使用户可能想要得到结果5:d。

工作得和我定义它的方式一样好,即字符串位于其他字符串的前面。因此,它产生的结果与add(";abc";,2)相同。

如何让像add(";Hello";,";World";)这样的东西成为可交换的?好的,我们可以比较这两个字符串,并检查哪一个在字典顺序上更小。

但怎么做呢?嗯,它只是多个分派,但主要语言不支持这一点。它检查了这4种方法中的哪一种适合,哪一种是最专业的。

如果您查看输入";Hello";、";World";这两个字符串,您会发现这四个方法都可以调用:

[1]ADD(x::String,y::String)in Main at REPL[9]:2[2]Add(x,y::String)in Main at REPL[10]:2[3]Add(x::String,y)in Main at REPL[13]:1[4]Add(x,y)in Main at REPL[3]:1。

但是[1]是最专业的版本。[4]基本上是后备的。(没有要求必须存在这样的后备)。

在单一分派语言中,只考虑第一个参数,这样[1]&;[3]将是不明确的。

如果您很好奇,您可能想检查一下调用哪个方法来调用";Hello&34;<;&34;World";。

在Julia中,您可以使用名为“Which”的宏来完成此操作。(宏是另一个帖子的主题,我还没有使用这些的经验)

我发现在这里调用泛型方法实际上相当有趣。也许我们想看看源代码。

这里的一个问题是,我们的CTRL+Q技巧不再用于检查获取源代码,因为没有[number],而且它无论如何都是一个不同的宏,并且我们不能单击Operators.jl,因为没有给出完整的路径。也许是另一个把戏?

将我们带到<;(x,y)=isless(x,y),这只会给我们带来一点点更多的真相:D。

告诉我们现在派到AbstractString的位置,这可能也是什么是AbstractString和什么是String的另一个主题的一部分。

函数cmp(a::string,b::string)al,bl=sizeof(A),sizeof(B)c=_memcmp(a,b,min(al,bl))返回c<;0?-1:c>;0?+1:cmp(al,bl)end。

好的,也许我们也应该谈谈这件事。我必须承认,总的来说,我不太喜欢在大学里用没人用过的怪异课程教授的面向对象编程和愚蠢的例子,但你看:我自己就是这种类型的愚蠢例子!

(好的,它基本上是相同的,只是有一些不同的动物和不同的功能名称)。尽管如此,它在这里是博客格式,所以您可以更容易地复制和粘贴代码。

摘要类型Animal endstruct Lizard<;:动物名称::String endstruct Rabbit<;:动物名称::String endrace(l::lizard,r::Rabb)=";$(l.name)在爬墙比赛中获胜。";race(r::兔子,l::lizard)=";$(r.name)在正常比赛中获胜。";race(a::t,b::t)其中T&llt。$(a.name)和$(b.name)永远运行。";函数相遇(a::Animal,b::Animal)println(";$(a.name)与$(b.name)相遇!";)println(";)println(";结果:$(race(a,b))";)endbayi=蜥蜴(";bayi";)Sally=Rabbit(";Sally";

在我们谈到它的输出是什么之前,这应该是我们所期望的,让我先提一下,蜥蜴有一种感觉,它们非常快,因为它们太小了。

#include<;iostream>;#include<;string>;using<;class Animal{public:string name;};string race(Animal a,Animal b){return a.name+";and";+b.name+";一直运行到一个人获胜";;}void Meet(Animal a,Animal b){cout<;<;a.name<;<;";Endl;结果:";<;<;race(a,b)<;<;Endl;}类蜥蜴:公共动物{};类兔子:公共动物{};串式竞赛(蜥蜴l,兔子r){return l.name+#34;在爬墙比赛中获胜;}串式竞赛(Rabbit r,蜥蜴l){return r.name+&34;;获胜。bayi.name=";八一;;兔子Sally;Sally.name=";Sally&34;;Meet(八一,Sally);Meet(Sally,八一);Meet(Sally,Sally);Return 0;}。

此处字符串race(Animal a,Animal b){return a.name+";and";+b.name+";一直运行,直到一个人赢得";}。

基本上是一种退路,而不是像朱莉娅那样,两种动物必须是同一类型的。

八一遇到萨利结果:八一和萨利跑到一场胜利萨利和八一跑到一场胜利萨利和八一跑到一场胜利萨利和萨利跑到一场胜利。

Meet示例的问题在于它是函数重载,而不是多次分派。不同之处:使用静态类型而不是实际类型的函数重载。基本会议将始终调用函数字符串RACE(Animal a,Animal b){return a.name+";and";+b.name+";一直运行到一个人获胜;;}。

因为它在Meet中使用的是静态类型的a和b,在那里它只知道自己是一只动物。

结果:八一遇到萨利,他们比赛!结果:八一在爬墙比赛中获胜。萨利遇到八一,他们比赛!结果:萨利在一场正常的比赛中获胜。萨利遇到萨利,他们比赛!结果:萨利和萨利永远奔跑。

在这里,我可能应该提到在最后一步中调用的方法的定义:

这里T是Animal的一个子类型,通过两次使用T,我们定义它们必须是同一类型。这意味着当定义struct Cat和调用RACE(::CAT,::CAT)时,我们将获得相同的结果,但是RACE(::CAT,::lizard)不起作用,因为它们是不同的Animal子类型。

这是相当主观的,但我的一个普遍感觉是,如果一门语言有多个分派,它就不需要它。

OOP为代码提供了结构,有时它被用来通过引入至少一个分派来克服这些语言中分派的限制。我经常很难在OOP中扩展代码,因为有时您想使用一个类,但不知何故需要一个额外的部分,它必须在您没有直接访问权限的类中。这对我来说有点限制,别人会说,当你不被允许做所有事情的时候,有时会说这很好。同样的人说,不应该在Javascript中工作,是的,好的,我可能会同意最后一点,但有时看看一些东西是否不转换类型就可以工作,这真是太棒了:d。

无论如何,我不想说朱莉娅在任何方面都更好,也不想说不要使用C++。他们只是有不同的概念。

这篇帖子希望能让你知道朱莉娅做了什么不同的事情,以及这是如何有用的。我鼓励您自己尝试一下,一定要看看Stefan Karpinski的演示文稿,因为他也展示了这是如何让包之间的对话变得更容易的。我不想在这里重复这一点,也不想举其他例子。在以后的帖子中,我希望在做一些比我经常使用的ConstraintSolver更简单的事情时,能用我的代码展示一些真实世界的例子。

我很乐意在博客上再次见到你,并请在评论中分享你的想法!

每月捐款一美元,你就可以提前访问这些帖子。在月初试用一下,如果你不喜欢,就在发薪日(月底)之前取消订阅。

我会让您随时了解Twitter OpenSourcES以及我个人的Twitter Wikunia_de的最新动态