likes
comments
collection
share

Go+快速开始详细指南

作者站长头像
站长
· 阅读数 17

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.bashall.bat 将会在底层用 go run cmd/make.go.

在Go+ playground里运行

如果你不想安装Go+,你可以在Go+ playground中编写Go+程序。这是体验Go+的最快方式。

你可以和你的朋友分享你的Go+代码。这是我的“Hello world”程序:

你好,世界

println "Hello world"

将这段代码保存到一个名为“hello.gop”的文件中。现在执行:' gop run hello.gop '。

恭喜你——你刚刚编写并执行了你的第一个Go+程序!

你可以用gop build hello.gop编译一个不需要执行的程序。

有关所有支持的命令,请参阅' gop help '。

println是为数不多的内置函数之一。它将传递给它的值打印到标准输出。

详见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:tutorial.goplus.org/

转载自:https://juejin.cn/post/7324129182278615078
评论
请登录