Go+快速开始
Go+编程语言是为工程、STEM教育和数据科学设计的。
- 对于工程:用儿童能掌握的最简单的语言工作。
- 对于STEM教育:学习一门可以在未来工作中使用的工程语言。
- 对于数据科学:用同一种语言与工程师交流。
安装方法
现在,我们建议您从源代码安装Go+。
注意:需要go1.18或更高版本
git clone https://github.com/goplus/gop.git
cd gop
# On mac/linux run:
./all.bash
# On Windows run:
all.bat
实际上,all.bash
和 all.bat
将会在底层用 go run cmd/make.go
.
在Go+ playground里运行
如果你不想安装Go+,你可以在Go+ playground中编写Go+程序。这是体验Go+的最快方式。
- Go+ playground based on Docker: https://play.goplus.org/
- Go+ playground based on GopherJS: https://jsplay.goplus.org/
你可以和你的朋友分享你的Go+代码。这是我的“Hello world”程序:
你好,世界
println "Hello world"
将这段代码保存到一个名为“hello.gop”的文件中。现在执行:’ gop run hello.gop '。
恭喜你——你刚刚编写并执行了你的第一个Go+程序!
你可以用gop build hello.gop
编译一个不需要执行的程序。
有关所有支持的命令,请参阅’ gop help '。
println
是为数不多的内置函数之一。它将传递给它的值打印到标准输出。
详见https://tutorial.goplus.org/hello-world。
运行包含多个文件的项目文件夹
假设您有一个包含几个.gop文件的文件夹,并且您想要将它们全部编译成一个程序。只要做:gop run .
。
传递参数也可以,所以你可以这样做:
gop run . --yourparams some_other_stuff
.
然后你的程序可以像这样使用CLI参数:
import "os"
println os.Args
注释
# This is a single line comment.
// This is a single line comment.
/*
This is a multiline comment.
*/
变量
name := "Bob"
age := 20
largeNumber := int128(1 << 65)
println name, age
println largeNumber
变量用:=
声明和初始化。
变量的类型是从右边的值推断出来的。
要选择不同的类型,请使用类型转换:
表达式’ T(v) ‘将值’ v '转换为“T”型。
初始化vs赋值
请注意’:= ‘和’ = '之间的(重要)区别。
':= ‘用于声明和初始化,’ = '用于赋值。
age = 21
这段代码无法编译,因为没有声明变量“age”。所有变量都需要在Go+中声明。
age := 21
可以在一行中修改多个变量的值。
通过这种方式,可以在没有中间变量的情况下交换它们的值。
a, b := 0, 1
a, b = b, a
println a, b // 1, 0
Go+类型
基本数据类型
bool
int8 int16 int32 int int64 int128
uint8 uint16 uint32 uint uint64 uint128
uintptr // similar to C's size_t
byte // alias for uint8
rune // alias for int32, represents a Unicode code point
string
float32 float64
complex64 complex128
bigint bigrat
unsafe.Pointer // similar to C's void*
any // alias for Go's interface{}
字符串
name := "Bob"
println name.len // 3
println name[0] // 66
println name[1:3] // ob
println name[:2] // Bo
println name[2:] // b
// or using octal escape `\###` notation where `#` is an octal digit
println "\141a" // aa
// Unicode can be specified directly as `\u####` where # is a hex digit
// and will be converted internally to its UTF-8 representation
println "\u2605" // ★
字符串值是不可变的。你不能改变元素:
s := "hello 🌎"
s[0] = `H` // not allowed
请注意,对字符串进行索引将生成一个’ byte ‘,而不是’ rune ‘或另一个’ string '。
字符串可以很容易地转换为整数:
s := "12"
a, err := s.int
if err != nil {
println(err)
return
}
println("a:", a)
b := s.int! // will panic if s isn't a valid integer
println("b:", b)
字符串操作符
name := "Bob"
bobby := name + "by" // + is used to concatenate strings
println bobby // Bobby
s := "Hello "
s += "world"
println s // Hello world
Go+中的所有操作符两边必须具有相同类型的值。不能连接一个整型转换为字符串:
age := 10
println "age = " + age // not allowed
我们也可以将’ age ‘转换为’ string ':
age := 10
println "age = " + age.string
Runes
’ rune’表示单个Unicode字符,是’ int32 '的别名。
rocket := '🚀'
println rocket // 128640
println string(rocket) // 🚀
Numbers
a := 123
这将把123的值赋给’ a ‘。默认情况下’ a ‘的类型为’ int '。
您还可以使用十六进制,二进制或八进制表示法来表示整数字面值:
a := 0x7B
b := 0b01111011
c := 0o173
所有这些都会被赋相同的值123。它们都有类型’ int ',不管你用什么符号。
Go+还支持用’ _ '作为分隔符书写数字:
num := 1_000_000 // same as 1000000
如果你想要一个不同类型的整数,你可以使用强制转换:
a := int64(123)
b := uint8(12)
c := int128(12345)
赋值浮点数的工作方式相同:
f1 := 1.0
f2 := float32(3.14)
如果不显式指定类型,默认情况下float字面值将具有’ float64 '类型。
Float字面值也可以声明为10的幂:
f0 := 42e1 // 420
f1 := 123e-2 // 1.23
f2 := 456e+2 // 45600
Go+内置了对有理数的支持:
a := 1r << 200 // suffix `r` means `rational`
b := bigint(1 << 200)
你可以将bool类型转换为数字类型(这在Go中不支持):
println int(true) // 1
println float64(true) // 1
println complex64(true) // (1+0i)
切片
切片是具有相同类型的数据元素的集合。切片字面量是用方括号括起来的表达式列表。单个元素可以是使用index表达式访问。索引从“0”开始:
nums := [1, 2, 3]
println nums // [1 2 3]
println nums.len // 3
println nums[0] // 1
println nums[1:3] // [2 3]
println nums[:2] // [1 2]
println nums[2:] // [3]
nums[1] = 5
println nums // [1 5 3]
自动推断切片文字的类型。
a := [1, 2, 3] // []int
b := [1, 2, 3.4] // []float64
c := ["Hi"] // []string
d := ["Hi", 10] // []any
d := [] // []any
强制转换切片片字面量也有效。
a := []float64([1, 2, 3]) // []float64
集合
a := {"Hello": 1, "xsw": 3} // map[string]int
b := {"Hello": 1, "xsw": 3.4} // map[string]float64
c := {"Hello": 1, "xsw": "Go+"} // map[string]any
d := {} // map[string]any
如果没有找到键,默认返回零值:
a := {"Hello": 1, "xsw": 3}
c := {"Hello": 1, "xsw": "Go+"}
println a["bad_key"] // 0
println c["bad_key"] // <nil>
您还可以检查是否存在一个键,并获取它的值。
a := {"Hello": 1, "xsw": 3}
if v, ok := a["xsw"]; ok {
println "its value is", v
}
模块导入
模块可以使用’ import '关键字导入:
import "strings"
x := strings.NewReplacer("?", "!").Replace("Hello, world???")
println x // Hello, world!!!
模块导入别名
任何导入的模块名都可以使用别名:
import strop "strings"
x := strop.NewReplacer("?", "!").Replace("Hello, world???")
println x // Hello, world!!!
表达式和语句
If…else
在Go+中,’ if '语句非常简单,与大多数其他语言类似。
与其他类c语言不同,在条件周围没有括号,并且总是需要大括号。
a := 10
b := 20
if a < b {
println "a < b"
} else if a > b {
println "a > b"
} else {
println "a == b"
}
For循环
Go+只有一个循环关键字:’ for ',有几种形式。
for
/<-
这是最常见的形式。您可以将它与切片、映射、数字范围或自定义迭代器一起使用。
Slice for
’ for value < - arr '形式用于遍历切片的元素。
numbers := [1, 3, 5, 7, 11, 13, 17]
sum := 0
for x <- numbers {
sum += x
}
println sum // 57
如果需要索引,可以使用另一种形式’ for index, value < - arr ’
names := ["Sam", "Peter"]
for i, name <- names {
println i, name
// 0 Sam
// 1 Peter
}
Map for
m := {"one": 1, "two": 2}
for key, val <- m {
println key, val
// one 1
// two 2
}
for key, _ <- m {
println key
// one
// two
}
for val <- m {
println val
// 1
// 2
}
Range for
你可以在for循环中使用range expression
(start:end:step
)。
for i <- :5 {
println i
// 0
// 1
// 2
// 3
// 4
}
for i <- 1:5 {
println i
// 1
// 2
// 3
// 4
}
for i <- 1:5:2 {
println i
// 1
// 3
}
for
/<-
/if
所有for
/<-
形式的循环都可以有一个可选的’ if '条件。
numbers := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
for num <- numbers if num%3 == 0 {
println num
// 0
// 3
// 6
// 9
}
for num <- :10 if num%3 == 0 {
println num
// 0
// 3
// 6
// 9
}
Condition for
sum := 0
i := 1
for i <= 100 {
sum += i
i++
}
println sum // 5050
这种形式的循环类似于其他语言中的“while”循环。
一旦布尔条件求值为false,循环将停止迭代。
同样,条件周围没有括号,并且总是需要大括号。
C for
for i := 0; i < 10; i += 2 {
// Don't print 6
if i == 6 {
continue
}
println i
// 0
// 2
// 4
// 8
}
最后,还有传统C风格的for循环。它比“while”形式更安全
因为使用后者很容易忘记更新计数器和get
陷入无限循环。
Bare for
for {
// ...
}
可以省略该条件,从而导致无限循环。你可以使用’ break ‘或’ return '来结束循环。
错误处理
我们在Go+中重新定义了错误处理规范。我们称它们为“ErrWrap表达式”:
expr! // panic if err
expr? // return if err
expr?:defval // use defval if err
如何使用它们?下面是一个例子:
import (
"strconv"
)
func add(x, y string) (int, error) {
return strconv.Atoi(x)? + strconv.Atoi(y)?, nil
}
func addSafe(x, y string) int {
return strconv.Atoi(x)?:0 + strconv.Atoi(y)?:0
}
println `add("100", "23"):`, add("100", "23")!
sum, err := add("10", "abc")
println `add("10", "abc"):`, sum, err
println `addSafe("10", "abc"):`, addSafe("10", "abc")
The output of this example is:
add("100", "23"): 123
add("10", "abc"): 0 strconv.Atoi: parsing "abc": invalid syntax
===> errors stack:
main.add("10", "abc")
/Users/xsw/tutorial/15-ErrWrap/err_wrap.gop:6 strconv.Atoi(y)?
addSafe("10", "abc"): 10
与相应的Go代码相比,它更清晰,更具可读性。
最有趣的是,返回错误包含了整个错误堆栈。当我们遇到错误时,很容易找到根本原因。
这些“ErrWrap表达式”是如何工作的?有关详细信息,请参阅错误处理
函数
func add(x int, y int) int {
return x + y
}
println add(2, 3) // 5
返回多个值
func foo() (int, int) {
return 2, 3
}
a, b := foo()
println a // 2
println b // 3
c, _ := foo() // ignore values using `_`
可变参数
func sum(a ...int) int {
total := 0
for x <- a {
total += x
}
return total
}
println sum(2, 3, 5) // 10
输出参数可以有名称。
func sum(a ...int) (total int) {
for x <- a {
total += x
}
return // don't need return values if they are assigned
}
println sum(2, 3, 5) // 10
高阶函数
函数也可以是参数。
func square(x float64) float64 {
return x*x
}
func abs(x float64) float64 {
if x < 0 {
return -x
}
return x
}
func transform(a []float64, f func(float64) float64) []float64 {
return [f(x) for x <- a]
}
y := transform([1, 2, 3], square)
println y // [1 4 9]
z := transform([-3, 1, -5], abs)
println z // [3 1 5]
Lambda表达式
你也可以使用’ lambda表达式’来定义一个匿名函数。
func transform(a []float64, f func(float64) float64) []float64 {
return [f(x) for x <- a]
}
y := transform([1, 2, 3], x => x*x)
println y // [1 4 9]
z := transform([-3, 1, -5], x => {
if x < 0 {
return -x
}
return x
})
println z // [3 1 5]
结构体
自定义的迭代器
For range of UDT
type Foo struct {
}
// Gop_Enum(proc func(val ValType)) or:
// Gop_Enum(proc func(key KeyType, val ValType))
func (p *Foo) Gop_Enum(proc func(key int, val string)) {
// ...
}
foo := &Foo{}
for k, v := range foo {
println k, v
}
for k, v <- foo {
println k, v
}
println {v: k for k, v <- foo}
注意:你不能在udt.Gop_Enum(callback)的范围内使用break/continue或return语句
For range of UDT2
type FooIter struct {
}
// (Iterator) Next() (val ValType, ok bool) or:
// (Iterator) Next() (key KeyType, val ValType, ok bool)
func (p *FooIter) Next() (key int, val string, ok bool) {
// ...
}
type Foo struct {
}
// Gop_Enum() Iterator
func (p *Foo) Gop_Enum() *FooIter {
// ...
}
foo := &Foo{}
for k, v := range foo {
println k, v
}
for k, v <- foo {
println k, v
}
println {v: k for k, v <- foo}
推断结构类型
type Config struct {
Dir string
Level int
}
func foo(conf *Config) {
// ...
}
foo {Dir: "/foo/bar", Level: 1}
这里’ foo {Dir: “/foo/bar”, Level: 1} ‘等价于’ foo(&Config{Dir: “/foo/bar”, Level: 1}) ‘。然而,你不能将’ foo(&Config{“/foo/bar”, 1}) ‘替换为’ foo {“/foo/bar”, 1} ‘,因为将’ {“/foo/bar”, 1} '视为结构字面值会令人困惑。
您还可以在返回语句中省略结构类型。例如:
type Result struct {
Text string
}
func foo() *Result {
return {Text: "Hi, Go+"} // return &Result{Text: "Hi, Go+"}
}
重载操作符
import "math/big"
type MyBigInt struct {
*big.Int
}
func Int(v *big.Int) MyBigInt {
return MyBigInt{v}
}
func (a MyBigInt) + (b MyBigInt) MyBigInt { // binary operator
return MyBigInt{new(big.Int).Add(a.Int, b.Int)}
}
func (a MyBigInt) += (b MyBigInt) {
a.Int.Add(a.Int, b.Int)
}
func -(a MyBigInt) MyBigInt { // unary operator
return MyBigInt{new(big.Int).Neg(a.Int)}
}
a := Int(1r)
a += Int(2r)
println a + Int(3r)
println -a
自动属性
Let’s see an example written in Go+:
import "gop/ast/goptest"
doc := goptest.New(`... Go+ code ...`)!
println doc.Any().FuncDecl().Name()
在许多语言中,有一个名为" property “的概念,它有” get “和” set "方法。
假设我们有’ get property ',上面的例子将是:
import "gop/ast/goptest"
doc := goptest.New(`... Go+ code ...`)!
println doc.any.funcDecl.name
在Go+中,我们引入了一个名为“自动属性”的概念。它是一个“get属性”,但是是自动实现的。如果我们有一个名为’ Bar() ‘的方法,那么我们将同时有一个名为’ Bar ‘的’ get属性’。
Go/Go+ 混合程序设计
这是一个展示如何在同一个包中混合Go/Go+代码的示例。
在这个例子中,我们有一个名为’ a.go '的Go源文件:
package main
import "fmt"
func p(a interface{}) {
sayMix()
fmt.Println("Hello,", a)
}
我们有一个Go+源文件名为’ b.gop ':
func sayMix() {
println "Mix Go and Go+"
}
p "world"
你可以看到Go调用名为’ sayMix ‘的Go+函数,Go+调用名为’ p '的Go函数。正如你在Go编程中习惯的那样,这种循环引用是允许的。
运行’ gop Run . '来查看这个示例的输出:
Mix Go and Go+
Hello, world
在监视模式下运行Go+
’ gop '命令可以在监视模式下运行,以便每次更改Go+文件时将其转换为Go文件:
gop watch [-gentest] [dir]
默认情况下,’ gop watch ‘不会转换测试文件(通常以’ _test.gop ‘结尾)。你可以指定’ -gentest '标志来强制转换所有Go+文件。
从Go+调用C
- ’ gop c ‘命令(相当于独立的’ c2go '命令)可用于将c项目转换为Go项目。
- ’ import “C” ‘和’ import “C/xxx” '用于导入c2go转换的C项目。其中“import”C是“import”C/github.com/goplus/libc”的缩写。
- ’ C"xxx" '语法表示C风格的字符串常量。
Here is an example to show how Go+ interacts with C.
import "C"
C.printf C"Hello, c2go!\n"
C.fprintf C.stderr, C"Hi, %7.1f\n", 3.14
在这个例子中,我们调用两个C标准函数’ printf ‘和’ fprintf ‘,传递一个C变量’ stderr ‘和两个C字符串,形式为’ C"xxx" ’ (Go+语法表示C风格字符串)。
运行’ gop run . '来查看这个示例的输出:
Hello, c2go!
Hi, 3.1
当然,目前Go+对C语言的支持只是一个预览版本,还没有到实际工程中可用的程度。就libc而言,目前的移民进度只有5%左右,这只是一个开始。
在即将到来的Go+ v1.2版本计划中,完全支持C语言被列为首要任务。当然,对Go和Go模板的支持也在计划之中,这对Go/Go+混合项目来说是一个至关重要的功能增强。
数据处理
有理数
我们引入有理数作为原始Go+类型。我们使用后缀“r”来表示有理字面值。例如, 1r << 200
表示一个大int,它的值等于 2200.
a := 1r << 200
b := bigint(1 << 200)
默认情况下,’ 1r ‘的类型为’ bigint ’
4/5r表示有理常数4/5。
它的类型是’ bigrat '。
a := 4/5r
b := a - 1/3r + 3 * 1/2r
println a, b // 4/5 59/30
转换有理数的工作方式类似于其他基本数据类型:
列表推导式
a := [x*x for x <- [1, 3, 5, 7, 11]]
b := [x*x for x <- [1, 3, 5, 7, 11] if x > 3]
c := [i+v for i, v <- [1, 3, 5, 7, 11] if i%2 == 1]
arr := [1, 2, 3, 4, 5, 6]
d := [[a, b] for a <- arr if a < b for b <- arr if b > 2]
x := {x: i for i, x <- [1, 3, 5, 7, 11]}
y := {x: i for i, x <- [1, 3, 5, 7, 11] if i%2 == 1}
z := {v: k for k, v <- {1: "Hello", 3: "Hi", 5: "xsw", 7: "Go+"} if k > 3}
从集合中选择数据
type student struct {
name string
score int
}
students := [student{"Ken", 90}, student{"Jason", 80}, student{"Lily", 85}]
unknownScore, ok := {x.score for x <- students if x.name == "Unknown"}
jasonScore := {x.score for x <- students if x.name == "Jason"}
println unknownScore, ok // 0 false
println jasonScore // 80
检查集合中是否存在数据
type student struct {
name string
score int
}
students := [student{"Ken", 90}, student{"Jason", 80}, student{"Lily", 85}]
hasJason := {for x <- students if x.name == "Jason"} // is any student named Jason?
hasFailed := {for x <- students if x.score < 60} // is any student failed?
Unix shebang
现在可以使用Go+程序作为shell脚本。例如:
#!/usr/bin/env -S gop run
println "Hello, Go+"
println 1r << 129
println 1/3r + 2/7r*2
arr := [1, 3, 5, 7, 11, 13, 17, 19]
println arr
println [x*x for x <- arr, x > 3]
m := {"Hi": 1, "Go+": 2}
println m
println {v: k for k, v <- m}
println [k for k, _ <- m]
println [v for v <- m]
与Go的兼容性
所有Go的特性都将被支持.
所有Go包(即使这些包使用’ cgo ')都可以通过Go+.导入
import (
"fmt"
"strings"
)
x := strings.NewReplacer("?", "!").Replace("hello, world???")
fmt.Println "x:", x
所有Go+包也可以导入到Go程序中。你需要做的就是使用’ gop ‘命令而不是’ go ’
首先,让我们创建一个名为“14-Using-goplus-in-Go”的目录。
然后在其中编写一个名为foo的Go+包:
package foo
func ReverseMap(m map[string]int) map[int]string {
return {v: k for k, v <- m}
}
然后在Go包中使用它14-Using-goplus-in-Go/gomain
package main
import (
"fmt"
"github.com/goplus/tutorial/14-Using-goplus-in-Go/foo"
)
func main() {
rmap := foo.ReverseMap(map[string]int{"Hi": 1, "Hello": 2})
fmt.Println(rmap)
}
如何构建这个示例?你可以用:
gop install -v ./...
字节码 vs Go代码
当我们使用’ gop '命令时,它会生成Go代码将Go+包转换为Go包。
gop run # Run a Go+ program
gop install # Build Go+ files and install target to GOBIN
gop build # Build Go+ files
gop test # Test Go+ packages
gop fmt # Format Go+ packages
gop clean # Clean all Go+ auto generated files
gop go # Convert Go+ packages into Go packages
当我们使用’ igop '命令时,它会生成字节码来执行。
igop # Run a Go+ program
在字节码模式下,Go+不支持’ cgo ‘。然而,在Go代码生成模式下,Go+完全支持’ cgo '。
References:https://tutorial.goplus.org/