导入gust包体验Golang的声明式编程
本文作者:github.com/GpigZ
地址:github.com/andeya/gust
gust基本介绍
官方介绍
A Rust-inspired declarative programming module for Golang that helps reduce bugs and improve development efficiency. For example results, options, iterators, etc.
个人介绍
官方介绍解析
希望通过良好的编程规范或者说希望通过最大限度的利用编译器来解决运行时可能出现的bug。并且通过声明式编程来提高开发效率。
声明式编程
维基百科中:
声明式编程(英语:Declarative programming)或译为声明式编程,是对与命令式编程不同的编程范型的一种合称。它们建造计算机程序的结构和元素,表达计算的逻辑而不用描述它的控制流程。
常见的声明式语言包括:数据库查询语言如SQL的查询子集和XQuery(英语:XQuery),正则表达式,配置管理系统如Puppet管理配置语言。归入这种范型的很多语言,描述问题领域(英语:Domain knowledge)内目标的性质,让电脑明白目标,而非流程,从而尝试极小化有关的副作用。而命令式编程则需要用语言原语(英语:Language primitive)来明确的指出每一步该怎么做。
可归入声明式编程范型的领域专属语言(DSL)还包括:yacc语法解析器,编译说明语言Make等。DSL不需要是图灵完全的,往往容易以一种纯声明式的方式来表达。很多文本标记语言例如HTML、MXML、XAML和XSLT往往是声明式的。
声明式编程,通常被定义为除命令式以外的任何编程范型。同时存在一些其他的定义,简单的将声明式编程和命令式编程做对比,例如:
- 告诉计算机需要计算“什么”而不是“如何”去计算的高级程序。
- 明确的对应数理逻辑的编程语言(([4]))。
- 任何没有副作用的编程语言,或者更确切一点,任何参照透明(英语:referential transparency)的编程语言。
这些定义存在着一些重合。
体现gust中声明式编程的例子(部分内容,详细内容可在使用中体验)
不使用gust
numbers := []int{1, 2, 3}
for _, v := range numbers {
if v > 1 {
return
}
}
fmt.Println("all numbers less than or equal to 1")
使用gust
var iter = FromVec([]int{0, 1})
if !iter.Any(func(x int) bool {
return x > 1
}) {
fmt.Println("all numbers less than or equal to 1")
}
不使用gust
array := []int{0, 1, 2, 3}
var result []int
for _, num := range array {
if num%2 != 0 {
result = append(result, num)
}
}
使用gust
var iter = FromVec([]int{0, 1, 2, 3})
var results []int
iter.Inspect(func(num int) {
if num%2 != 0 {
results = append(results, num)
}
}).Collect()
不使用gust
array := []int{1, 2, 3}
for i, _ := range array {
array[i] = 2 * array[i]
}
使用gust
array := FromVec([]int{1, 2, 3, 4}).
var result = array.Map(func(x int) any { return 2 * x }).Collect()
gust体现易用性和安全性的例子(部分内容,详细内容可在使用中体验)
- 我们需要处理strconv中返回value和err,并且err为空的时候值也有可能为空的时候。
var okStr = "20" var errStr = "not a number" var emptyStr string // Ret encapsulates value and err into result okResult := gust.Ret(strconv.ParseUint(okStr, 10, 64)) errResult := gust.Ret(strconv.ParseUint(errStr, 10, 64)) emptyResult := gust.Ret(strconv.ParseUint(emptyStr, 10, 64)) assert.Equal(t, uint64(20), okResult.Unwrap()) assert.Equal(t, true, errResult.IsErr()) assert.Equal(t, true, emptyResult.IsErr()) // If an error occurs Ok will set it to empty // If you encounter the return value is empty but err is also empty, You can use ok to convert result to option okOption := okResult.Ok() errOption := errResult.Ok() emptyOption := emptyResult.Ok() assert.Equal(t, uint64(20), okOption.Unwrap()) assert.Equal(t, true, errOption.IsNone()) assert.Equal(t, true, emptyOption.IsNone())
我们可以使用result接收value和err,然后把result转换成option进行非空判断。这样可以更便捷的处理value和err,并且在err为空和值同时为空的时候gust会自动帮我们声明值,避免直接调用值导致的空指针panic。
- 以下是体现其易用性的例子。当struct中属性可能为空的时候。
type SearchUserReq struct { Type gust.Option[string] Name gust.Option[string] Email gust.Option[string] } func (dao Dao) SearchUser(c context.Context, req SearchUserReq) gust.Result[Data]{ var db = dao.WithDB(c) req.Type.Inspect(func(v string) { db = db.Where("type=?", v) }) req.Name.Inspect(func(v string) { db = db.Where("type=?", v) }) req.Email.Inspect(func(v string) { db = db.Where("type=?", v) }) var r Data err :=db.Find(&r).Error return gust.Ret(r,err) }
使用Option表达 struct 字段可选,有效区分 零值和空值,比用 *T 的方式要更加安全,语义也更明确,且提供了丰富的类型方法,快捷满足各自处理逻辑。
gust内容展示(部分内容,详细内容可在使用中体验)
Option
struct:
// Option can be used to avoid `(T, bool)` and `if *U != nil`, // represents an optional value: // // every [`Option`] is either [`Some`](which is non-none T), or [`None`](which is none). type Option[T any] struct { value *T }
use:
如果为空执行默认func否则执行目标func
var k = 21 { var x = gust.Some("foo") assert.Equal(t, 3, x.XMapOrElse(func() any { return 2 * k }, func(v string) any { return len(v) })) } { var x gust.Option[string] assert.Equal(t, 42, x.XMapOrElse(func() any { return 2 * k }, func(v string) any { return len(v) })) }
Result
struct:
// Result can be used to improve `func()(T,error)`, // represents either success (T) or failure (error). type Result[T any] struct { inner EnumResult[T, error] } // EnumResult represents a success (T) or failure (E) enumeration. type EnumResult[T any, E any] struct { val any isErr bool }
use:
在对错误值进行Map处理时会跳过,避免对错误值进行运算从而导致发生未知的bug,并且提供了丰富的类型方法。
var goodResult1 = gust.Ok(10) var badResult1 = gust.Err[int](10) // The `IsOk` and `IsErr` methods do what they say. assert.True(t, goodResult1.IsOk() && !goodResult1.IsErr()) assert.True(t, badResult1.IsErr() && !badResult1.IsOk()) // `map` consumes the `Result` and produces another. var goodResult2 = goodResult1.Map(func(i int) int { return i + 1 }) var badResult2 = badResult1.Map(func(i int) int { return i - 1 }) // Use `AndThen` to continue the computation. var goodResult3 = ret.AndThen(goodResult2, func(i int) gust.Result[bool] { return gust.Ok(i == 11) }) // Use `OrElse` to handle the error. var _ = badResult2.OrElse(func(err error) gust.Result[int] { fmt.Println(err) return gust.Ok(20) }) // Consume the result and return the contents with `Unwrap`. var _ = goodResult3.Unwrap()
EnumResult(自定义Result)
struct:
// EnumResult represents a success (T) or failure (E) enumeration. type EnumResult[T any, E any] struct { val any isErr bool }
use:
EnumResult.IsErrrAnd判断EnumResult[T any, E any]范型E中是否有值,如果有值对值的内容进行回调函数中的判断返回相应的bool,没有值则返回false。
var x = gust.EnumErr[int, int8](-1) assert.True(t, x.IsErrAnd(func(x int8) bool { return x == -1 }))
Errable
struct:
type Errable[T any] struct { errVal *T }
use:
我们使用自定义错误的时候。尽管我们写入的参数4是符合预期的,但是因为在error封装了一层自定义实现错误,error是interface类型,在interface中只有类型和值均为空才是nil,所以在Bar返回的err!=nil但是预期上err应该为空,使用errable可以避免这个问题。
var _ error = new(MyError) type MyError struct { val string } func (m *MyError) Error() string { return m.val } func Foo(n int) *MyError { if n%2 == 0 { return nil } return &MyError{ val: "bad", } } // Use errable to avoid err not being empty func Foo2(n int) gust.Errable[string] { if n%2 == 0 { return gust.NonErrable[string]() } return gust.ToErrable[string]("bad") } func Bar2(n int) error { return Foo2(n).ToError() } func Bar(n int) error { return Foo(n) } func TestErrableExample(t *testing.T) { err := Bar(4) if err != nil { fmt.Println("i have error") } err = Bar2(4) assert.Equal(t, nil, err) }
Iterator
struct:(部分接口)
Next() gust.Option[T] NextChunk(n uint) ([]T, bool) SizeHint() (uint, gust.Option[uint]) Count() uint Fold(init any, fold func(any, T) any) any TryFold(init any, fold func(any, T) gust.Result[any]) gust.Result[any] Last() gust.Option[T] AdvanceBy(n uint) gust.Errable[uint] Nth(n uint) gust.Option[T] ForEach(f func(T)) Reduce(f func(accum T, item T) T) gust.Option[T] All(predicate func(T) bool) bool Any(predicate func(T) bool) bool . Find(predicate func(T) bool) gust.Option[T] FindMap(f func(T) gust.Option[any]) gust.Option[any] TryFind(predicate func(T) gust.Result[bool]) gust.Result[gust.Option[T]] Position(predicate func(T) bool) gust.Option[int] StepBy(step uint) *StepByIterator[T] Filter(f func(T) bool) *FilterIterator[T] FilterMap(f func(T) gust.Option[T]) *FilterMapIterator[T] Chain(other Iterator[T]) *ChainIterator[T] Map(f func(T) any) *MapIterator[T, any] Inspect(f func(T)) *InspectIterator[T] Fuse() *FuseIterator[T] Collect() []T
use:
使用Any来遍历一组数据,并且对其中的值进行判断
var iter = FromVec([]int{1, 2, 3}) if !iter.Any(func(x int) bool { return x > 1 }) { t.Error("Any failed") }
初心与展望
我们希望我们的编程变得更加的便捷,我们希望我们在编程的时候只需要关注于我们的目标结果而不是里面的各种细节。并且我们希望我们的代码可以在编译期就发现问题的所在,而不在运行的时候才发现问题。在使用gust的时候如果你的代码编写得不那么符合规范的时候,它会在编译期就发现你的错误,我希望代码只要编译通过了,逻辑上没有问题,他就是没有bug的或者说是几乎没有bug。而且你的代码会变得更加的美观。这将让我们代码的维护成本降低,
我们希望它可以给大家带来更好的编程体验,所以有什么让你使用得不那么舒服的地方,我们希望你可以马上提出来,每个人宝贵的意见都会被我们看到,都会成为我们进步的助力。而且你在使用过程中发现了我们没有罗列出来的让你感到有趣的亮点,你也可以与我们分享与大家分享。我们希望编程不仅仅是人机交流,也是人与人之间的交流,思想是流动在人与人之间的。每个人的创造力都是伟大的,只是它可能没有被发现。
接口文档:pkg.go.dev/github.com/andeya/gust
本作品采用《CC 协议》,转载必须注明作者和本文链接
推荐文章: