热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Go/Python/Erlang编程语言对比分析及示例

本文主要是介绍Go,从语言对比分析的角度切入。之所以选择与Python、Erlang对比,是因为做为高级语言,它们语言特性上有较大的相似性,不过最主要的原因是这几个我比较熟悉。Go的很多语言特性借

本文主要是介绍Go,从语言对比分析的角度切入。之所以选择与Python、Erlang对比,是因为做为高级语言,它们语言特性上有较大的相似性,不过最主要的原因是这几个我比较熟悉。

Go的很多语言特性借鉴与它的三个祖先:C,Pascal和CSP。Go的语法、数据类型、控制流等继承于C,Go的包、面对对象等思想来源于Pascal分支,而Go最大的语言特色,基于管道通信的协程并发模型,则借鉴于CSP分支。

 

Go/Python/Erlang语言特性对比

 如《编程语言与范式》一文所说,不管语言如何层出不穷,所有语言的设计离不开2个基本面:控制流和数据类型。为了提升语言描述能力,语言一般都提供控制抽象和数据抽象。本小节的语言特性对比也从这4个维度入手,详见下图(点击见大图)。

图中我们可以看出,相比于Python的40个特性,Go只有31个,可以说Go在语言设计上是相当克制的。比如,它没有隐式的数值转换,没有构造函数和析构函数,没有运算符重载,没有默认参数,也没有继承,没有泛型,没有异常,没有宏,没有函数修饰,更没有线程局部存储。

但是Go的特点也很鲜明,比如,它拥有协程、自动垃圾回收、包管理系统、一等公民的函数、栈空间管理等。

Go作为静态类型语言,保证了Go在运行效率、内存用量、类型安全都要强于Python和Erlang。

Go的数据类型也更加丰富,除了支持表、字典等复杂的数据结构,还支持指针和接口类型,这是Python和Erlang所没有的。特别是接口类型特别强大,它提供了管理类型系统的手段。而指针类型提供了管理内存的手段,这让Go进入底层软件开发提供了强有力的支持。

Go在面对对象的特性支持上做了很多反思和取舍,它没有类、虚函数、继承、泛型等特性。Go语言中面向对象编程的核心是组合和方法(function)。组合很类似于C语言的struct结构体的组合方式,方法类似于Java的接口(Interface),但是使用方法上与对象更加解耦,减少了对对象内部的侵入。Erlang则不支持面对对象编程范式,相比而言,Python对面对对象范式的支持最为全面。

在函数式编程的特性支持上,Erlang作为函数式语言,支持最为全面。但是基本的函数式语言特性,如lambda、高阶函数、curry等,三种语言都支持。

控制流的特性支持上,三种语言都差不多。Erlang支持尾递归优化,这给它在函数式编程上带来便利。而Go在通过动态扩展协程栈的方式来支持深度递归调用。Python则在深度递归调用上经常被爆栈。

Go和Erlang的并发模型都来源于CSP,但是Erlang是基于actor和消息传递(mailbox)的并发实现,Go是基于goroutine和管道(channel)的并发实现。不管Erlang的actor还是Go的goroutine,都满足协程的特点:由编程语言实现和调度,切换在用户态完成,创建销毁开销很小。至于Python,其多线程的切换和调度是基于操作系统实现,而且因为GIL的大坑级存在,无法真正做到并行。

而且从笔者的并发编程体验上看,Erlang的函数式编程语法风格和其OTP behavior框架提供的晦涩的回调(callback)使用方法,对大部分的程序员,如C/C++和Java出身的程序员来说,有一定的入门门槛和挑战。而被称为“互联网时代的C”的Go,其类C的语法和控制流,以及面对对象的编程范式,编程体验则好很多。

Go/Python/Erlang语言语法对比

所有的语言特性都需要有形式化的表示方式,Go、Python、Erlang三种语言语法的详细对比如下(点击见完整大图第一部分,第二部分,第三部分)。这里(链接)有一个详细的Go 与 C 的语法对比,这也是我没有做Go vs. C对比的一个原因。

正如Go语言的设计者之一Rob Pike所说,“软件的复杂性是乘法级相关的”。这充分体现在语言关键词(keyword)数量的控制上,Go的关键词是最少的,只有25个,而Erlang是27个,Python是31个。从根本上保证了Go语言的简单易学。

Go语言将数据类型分为四类:基础类型、复合类型、引用类型和接口类型。基础类型包括:整型、浮点型、复数、字符串和布尔型。复合数据类型有数组和结构体。引用类型包括指针、切片、字典、函数、通道。其他数据类型,如原子(atom)、比特(binary)、元组(tuple)、集合(set)、记录(record),Go则没有支持。

Go对C语言的很多语法特性做了改良,正如Rob Pike在《Less is Exponentially More》中提到,Go的“起点: C语言,解决一些明显的瑕疵、删除杂质、增加一些缺少的特性。”,比如,switch/case的case子程序段默认break跳出,case语句支持数值范围、条件判断语句;所有类型默认初始化为0,没有未初始化变量;把类型放在变量后面的声明语法(链接),使复杂声明更加清晰易懂;没有头文件,文件的编译以包组织,改善封装能力;用空接口(interface {})代替void *,提高类型系统能力等等。

Go对函数,方法,接口做了清晰的区分。与Erlang类似,Go的函数作为第一公民。函数可以让我们将一个语句序列打包为一个单元,然后可以从程序中其它地方多次调用。函数和方法的区别是指有没有接收器,而不像其他语言那样是指有没有返回值。接口类型具体描述了一系列方法的集合,而空接口interfac{}表示可以接收任意类型。接口的这2中使用方式,用面对对象编程范式来类比的话,可以类比于subtype polymorphism(子类型多态)和ad hoc polymorphism(非参数多态)。

从图中示例可以看出,Go的goroutine就是一个函数,以及在堆上为其分配的一个堆栈。所以其系统开销很小,可以轻松的创建上万个goroutine,并且它们并不是被操作系统所调度执行。goroutine只能使用channel来发送给指定的goroutine请求来查询更新变量。这也就是Go的口头禅“不要使用共享数据来通信,使用通信来共享数据”。channel支持容量限制和range迭代器。

 

 

 

 

Go/Python/Erlang语言词法对比

Go、Python、Erlang三种语言词法符号的详细对比如下(点击见完整大图)。Go的词法符号是3个语言中最多的,有41个,而且符号复用的情况也较多。相对来说,Python最少,只有31个。

Go语言在词法和代码格式上采取了很强硬的态度。Go语言只有一种控制可见性的手段:大写首字母的标识符会从定义它们的包中被导出,小写字母的则不会。这种限制包内成员的方式同样适用于struct或者一个类型的方法。

在文件命名上,Go也有一定的规范要求,如以_test.go为后缀名的源文件是测试文件,它们是go test测试的一部分;测试文件中以Test为函数名前缀的函数是测试函数,用于测试程序的一些逻辑行为是否正确;以Benchmark为函数名前缀的函数是基准测试函数,它们用于衡量一些函数的性能。

除了关键字,此外,Go还有大约30多个预定义的名字,比如int和true等,主要对应内建的常量、类型和函数。

 

 

TDD Go编程示例

本小节以TDD方式4次重构开发一个斐波那契算法的方式,来简单展示Go的特性、语法和使用方式,如Go的单元测试技术,并发编程、匿名函数、闭包等。

首先,看一下TDD最终形成的单元测试文件:

package main

import (
	"testing"
)

func TestFib(t *testing.T) {
	var testdatas = []struct {
		n    int
		want int64
	}{
		{0, 0},
		{1, 1},
		{2, 1},
		{3, 2},
		{4, 3},
		{16, 987},
		{32, 2178309},
		{45, 1134903170},
	}

	for _, test := range testdatas {
		n := test.n
		want := test.want
		got := fib(n)

		if got != want {
			t.Errorf("fib(%d)=%d, want %d\n", n, got, want)
		}
	}

}

 

基于递归的实现方案:

func fib1(n int) int64 {
	if n == 0 || n == 1 {
		return int64(n)
	}
	return fib1(n-1) + fib1(n-2)

}

测试结果:

crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 9.705s

real 0m10.045s
user 0m9.968s
sys 0m0.068s

基于goroutine实现的并发方案:

func fib2(n int) int64 {
	var got int64
	var channel = make(chan int64, 2)

	if n == 0 || n == 1 {
		return int64(n)
	}

	runtime.GOMAXPROCS(2)

	go func() { channel <- fib1(n - 2) }()
	go func() { channel <- fib1(n - 1) }()

	got = <-channel
	got += <-channel
	return got

}

测试结果:

crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 6.118s

real 0m6.674s
user 0m10.268s
sys 0m0.148s

基于迭代的实现方案:

func fib3(n int) int64 {
	var a, b int64
	a, b = 0, 1

	for i := 0; i  

测试结果:

crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 0.002s

real 0m0.547s
user 0m0.328s
sys 0m0.172s

基于闭包的实现方案:

func fibWrapper4() func() int64 {
	var a, b int64
	a, b = 0, 1

	return func() int64 {
		a, b = b, a+b
		return a
	}
}

func fib4(n int) int64 {
	var got int64
	got = 0
	f := fibWrapper4()
	for i := 0; i  

测试结果:

crbsp@fib$ time go test
PASS
ok _/home/crbsp/alex/go/fib 0.002s

real 0m0.411s
user 0m0.260s
sys 0m0.140s

 

--完--  

  

  


推荐阅读
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • 深入理解排序算法:集合 1(编程语言中的高效排序工具) ... [详细]
  • 目前我有两张 BMP 图像文件 a.bmp 和 b.bmp,希望将它们按照以下方式进行融合:首先提取 a.bmp 的所有奇数行像素(如第 1、3、5 行),接着获取 b.bmp 的所有偶数行像素(如第 2、4、6 行)。最终目标是将这些行像素交替排列,生成一张新的图像。此过程需要确保像素顺序正确,并保持图像的整体结构和质量。 ... [详细]
  • 本文详细解析了 Android 系统启动过程中的核心文件 `init.c`,探讨了其在系统初始化阶段的关键作用。通过对 `init.c` 的源代码进行深入分析,揭示了其如何管理进程、解析配置文件以及执行系统启动脚本。此外,文章还介绍了 `init` 进程的生命周期及其与内核的交互方式,为开发者提供了深入了解 Android 启动机制的宝贵资料。 ... [详细]
  • 深入解析Java虚拟机的内存分区与管理机制
    Java虚拟机的内存分区与管理机制复杂且精细。其中,某些内存区域在虚拟机启动时即创建并持续存在,而另一些则随用户线程的生命周期动态创建和销毁。例如,每个线程都拥有一个独立的程序计数器,确保线程切换后能够准确恢复到之前的执行位置。这种设计不仅提高了多线程环境下的执行效率,还增强了系统的稳定性和可靠性。 ... [详细]
  • 深入解析Android 4.4中的Fence机制及其应用
    在Android 4.4中,Fence机制是处理缓冲区交换和同步问题的关键技术。该机制广泛应用于生产者-消费者模式中,确保了不同组件之间高效、安全的数据传输。通过深入解析Fence机制的工作原理和应用场景,本文探讨了其在系统性能优化和资源管理中的重要作用。 ... [详细]
  • OSChina 周末闲谈 —— 程序员的浪漫情话
    在OSChina的周末闲谈栏目中,探讨了程序员特有的浪漫情话。本文不仅分享了一些程序员如何用代码表达爱意的有趣例子,还推荐了一首适合程序员聆听的歌曲——李克勤的《啜泣》。对于喜欢在编程之余享受音乐的朋友们,不妨点击链接试听一下。 ... [详细]
  • Swoole加密机制的安全性分析与破解可能性探讨
    本文深入分析了Swoole框架的加密机制,探讨了其在实际应用中的安全性,并评估了潜在的破解可能性。研究结果表明,尽管Swoole的加密算法在大多数情况下能够提供有效的安全保护,但在特定场景下仍存在被攻击的风险。文章还提出了一些改进措施,以增强系统的整体安全性。 ... [详细]
  • 本文深入解析了 Kuangbin 数学训练营中的经典问题——Ekka Dokka,并通过详细的代码示例和数学推导,探讨了该问题的多种解法及其应用场景。通过对算法的优化和扩展,本文旨在为读者提供全面的理解和实用的参考。 ... [详细]
  • 经过两天的努力,终于成功解决了半平面交模板题POJ3335的问题。原来是在`OnLeft`函数中漏掉了关键的等于号。通过这次训练,不仅加深了对半平面交算法的理解,还提升了调试和代码实现的能力。未来将继续深入研究计算几何的其他核心问题,进一步巩固和拓展相关知识。 ... [详细]
  • 在当前的软件开发领域,Lua 作为一种轻量级脚本语言,在 .NET 生态系统中的应用逐渐受到关注。本文探讨了 Lua 在 .NET 环境下的集成方法及其面临的挑战,包括性能优化、互操作性和生态支持等方面。尽管存在一定的技术障碍,但通过不断的学习和实践,开发者能够克服这些困难,拓展 Lua 在 .NET 中的应用场景。 ... [详细]
  • Python内置模块详解:正则表达式re模块的应用与解析
    正则表达式是一种强大的文本处理工具,通过特定的字符序列来定义搜索模式。本文详细介绍了Python内置的`re`模块,探讨了其在字符串匹配、验证和提取中的应用。例如,可以通过正则表达式验证电子邮件地址、电话号码、QQ号、密码、URL和IP地址等。此外,文章还深入解析了`re`模块的各种函数和方法,提供了丰富的示例代码,帮助读者更好地理解和使用这一工具。 ... [详细]
  • 第六章:枚举类型与switch结构的应用分析
    第六章深入探讨了枚举类型与 `switch` 结构在编程中的应用。枚举类型(`enum`)是一种将一组相关常量组织在一起的数据类型,广泛存在于多种编程语言中。例如,在 Cocoa 框架中,处理文本对齐时常用 `NSTextAlignment` 枚举来表示不同的对齐方式。通过结合 `switch` 结构,可以更清晰、高效地实现基于枚举值的逻辑分支,提高代码的可读性和维护性。 ... [详细]
  • 如何精通编程语言:全面指南与实用技巧
    如何精通编程语言:全面指南与实用技巧 ... [详细]
  • 在C++程序中,文档A的每一行包含一个结构体数据,其中某些字段可能包含不同数量的数字。需要将这些结构体数据逐行读取并存储到向量中,随后不仅在控制台上显示,还要输出到新创建的文档B中。希望得到指导,感谢! ... [详细]
author-avatar
心只为你跳国
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有