百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 热门文章 > 正文

golang中的函数式编程 golang math

bigegpt 2024-09-25 14:32 4 浏览

函数式编程

本篇来学习Go语言的函数式编程,函数式编程不是Go语言独有的,像Python也是支持函数式编程的,不过Go语言支持函数式编程主要体现在闭包上。

Go语言闭包应用:1)不需要修饰如何访问自由变量;2)没有Lambda表达式,但是有匿名函数 (其实两者差不多)。

接下来谈一谈函数式编程和函数指针的区别,其实我个人更倾向于函数式编程,因为在函数式编程(如Python)中,函数是一等公民,因此参数,变量及返回值都可以是函数;而像函数指针(如C++、Java)函数只是一个名字,其实就是指针。

在函数式编程中,有一个高阶函数的概念,也就是说一个函数可以作为参数传给另外一个函数,或者一个函数的返回值为另外一个函数(若返回值为该函数本身,则为递归),满足其一则为高阶函数,如python中的map,reduce,filter等。还有就是闭包这个概念。

当然可能有人要拿出"正统"函数式编程来说话了,需要满足两点:1)不可变性:不能有状态,只有常量和函数;2)函数只能有一个参数。

这个"正统"函数式编程要求里面不能有变量,只有常量和函数这两种,甚至连选择、循环语句都不能使用;更过分的要求是参数只能有一个参数,之前的参数列表都不能用了,太特么变态了吧。由于Go语言设计时要求按照了这个规定,但是实际上灵活性很大,可以不按照上面"正统"函数式编程的要求来。

下面结合一个例子说明Go语言的函数式编程:计算1+2+3+...+9=?我们先用普通的方法,接着使用函数式编程,然后试着体会两者的不同之处。

普通方法实现的代码如下,这个其实非常简单:

package main
import "fmt"
//定义求和函数,测试函数式编程
func test(n int)int{
 sum:=0
 for i:=0;i<n;i++{
 sum+=i
 }
 return sum
}
func main() {
 fmt.Println(test(10))
}
//运行结果:
45

接下来看一下如何使用函数式编程来实现这个功能:

package main
import "fmt"
//定义求和函数,测试函数式编程
func functionaltest()func(int)int{
 sum:=0
 return func(v int) int {
 sum+=v
 return sum
 }
}
func main() {
 a:=functionaltest()
 for i:=0;i<10;i++{
 fmt.Printf("0+1+...+%d=%d\n",i,a(i))
 }
}
//运行结果:
0+1+...+0=0
0+1+...+1=1
0+1+...+2=3
0+1+...+3=6
0+1+...+4=10
0+1+...+5=15
0+1+...+6=21
0+1+...+7=28
0+1+...+8=36
0+1+...+9=45

其实上面就是闭包,在函数体中包含自由变量和局部变量,这里的sum就是自由变量,v是局部变量。

下面是我从网上找的其他语言如何通过闭包来实现相应的功能:

1)Python中的闭包:python原生支持闭包、使用_closure_来查看闭包内容

def test():
 sum = 0
 def f(value):
 nonlocal sum
 sum += value
 return sum 
 return f

2)C++中的闭包:过去stl或者boost带有类似库;C++11及以后:支持闭包,以下是C++14下编译通过的:

auto test(){
  auto sum = 0;
 return [-] (int value) mutable {
    sum += value;
    return sum;
  }
} 

3)Java中的闭包:1.8以后使用Function接口和Lambda表达式来创建函数对象,函数本身不能作为参数和返回值的;1.8以前匿名类或Lambda表达式均支持闭包

Function<Integer,Integer> test() {
  final Holder<Integer> sum = new Holder<>(0);
 return (Integer value) -> {
    sum.value += value;
    return sum.value;
  }
}



还有一个问题就是前面说的"正统"函数式编程要求:1)不可变性:不能有状态,只有常量和函数;2)函数只能有一个参数。我们尝试使用代码来实现这个要求,但是实现正统函数式编程不能有状态,那么应该将状态(函数执行结果)放在另一个函数中:

//使用正统函数式编程,只有常量和函数,没有变量
type itest func(int)(int,itest)
func ftest(base int)itest{
 return func(v int) (int, itest) {
 return base+v,ftest(base+v)
 }
}
func main() {
 a:=ftest(0)
 for i:=0;i<10;i++{
 var s int
 s,a =a(i)
 fmt.Printf("0+1+...+%d=%d\n",i,s)
 }
}
//运行结果:
0+1+...+0=0
0+1+...+1=1
0+1+...+2=3
0+1+...+3=6
0+1+...+4=10
0+1+...+5=15
0+1+...+6=21
0+1+...+7=28
0+1+...+8=36
0+1+...+9=45

不过这种正统函数式编程理解起来非常困难,写起来也不容易理解。

斐波那契数列理解闭包

接下来通过斐波那契数列来加深自己对于闭包的理解,同样先使用普通方法,然后使用闭包的方式实现。

普通方法实现输出斐波那契数列:

//chapter06/fibonaqitest/fibonaqi.go文件
package fibonaqi
//1,1,2,3,5,8,13,21...
func FBtest(n int)int{
 a,b:=0,1
 for i:=0;i<n;i++{
 a,b = b,a+b
 }
 return a
}
//chapter06/fibonaqitest/main.go文件:
package main
import (
 "chapter06/fibonaqitest/fibonaqi"
 "fmt"
)
func main() {
 fmt.Println(fibonaqi.FBtest(5))
}
//运行结果:
5

再来试试闭包的实现:

//chapter06/fibonaqitest/fibonaqi.go文件
//闭包
func FPtest()func()int{
 a, b:=0,1
 return func() int {
 a,b=b,a+b
 return a
 }
}

但是这边有一个问题,就是这个函数内无法判断何时输出。其实这种和生成器非常相似,因此每次调用会执行一次该函数:

func main() {
 f:=fibonaqi.FPtest()
 fmt.Println(f()) //闭包函数测试
 fmt.Println(f()) 
}
//运行结果:
1
1

但是这个闭包其实变成了生成器,如果我们想输出斐波那契数列中小于10000的元素,我们需要多次调用这个生成器,直到输出的元素小于10000才停止运行,那么有没有简单的方法呢?我们可以让这个斐波那契函数实现一个输出内容的接口就行了:

//闭包
func FPtest()func()int{
 a, b:=0,1
 return func() int {
 a,b = b,a+b
 return a
 }
}
type IntGenerator func() int
func (g IntGenerator)Read(p[]byte)(n int,err error){
 next:=g() //获取下一个元素
 if next >10000{ //达到10000以上结束
 return 0, io.EOF
 }
 s:=fmt.Sprintf("%d\n",next) //转换成字符串
 // TODO: incorrect if p is too small!
 return strings.NewReader(s).Read(p)
}
//之前用于从文件中输出内容的函数
func PrintFileContent(reader io.Reader){
 scanner:=bufio.NewScanner(reader)
 for scanner.Scan(){
 fmt.Println(scanner.Text())
 }
}
func main() {
 var ft fibonaqi.IntGenerator =fibonaqi.FPtest()
 fibonaqi.PrintFileContent(ft)
}
//运行结果:
1
1
2
3
5
8
13
21
34
55
89
144
233
377
610
987
1597
2584
4181
6765

不过这个代码有一个瑕疵就是这个p对象不能太小,太小就无法输出信息,后续会对这段代码进行修改。

二分搜索树遍历理解闭包

接下来使用之前介绍的二分搜索树遍历的例子来加深对闭包的理解。学过二分搜索树的人肯定知道中序遍历结果是0 9 2 0 6:



但是之前的遍历函数只能实现遍历的功能,接下来让函数实现接口,那它就能干很多事了:

//函数闭包,演示二分搜索树的遍历
func (node *treeNode)Traverse(){
 node.TraverseFunc(func(n *treeNode) {
 n.Print()
 })
 fmt.Println()
}
func (node *treeNode)TraverseFunc(f func(*treeNode)){
 if node==nil{
 return
 }
 node.left.TraverseFunc(f)
 f(node)
 node.right.TraverseFunc(f)
}
func main() {
 var root treeNode //声明一个二分搜索树对象
 root = treeNode{value: 2} //二分搜索树root节点初始化
 root.left = &treeNode{} //二分搜索树root节点左子树初始化
 root.right = &treeNode{6, nil, nil} //二分搜索树root节点右子树初始化,其本质也是一个treeNode对象
 root.right.left =new(treeNode) //给二分搜索树root节点的左子树的左侧创建一个节点
 root.left.right = createTreeNode(9)
 root.reverse()
 fmt.Println("********************")
 root.Traverse()
 //数一下二分搜索树中元素的个数
 nodeCount:=0
 root.TraverseFunc(func(node *treeNode) {
 nodeCount++
 })
 fmt.Println("********************")
 fmt.Println("节点总数为:",nodeCount)
}
//运行结果:
0
9
2
0
6
节点总数为: 5

看到没,我们后实现的TraverseFunc函数的功能非常强大,不仅仅限于遍历。

相关推荐

C#.NET Autofac 详解(c# autoit)

简介Autofac是一个成熟的、功能丰富的.NET依赖注入(DI)容器。相比于内置容器,它额外提供:模块化注册、装饰器(Decorator)、拦截器(Interceptor)、强o的属性/方法注...

webapi 全流程(webapi怎么部署)

C#中的WebAPIMinimalApi没有控制器,普通api有控制器,MinimalApi是直达型,精简了很多中间代码,广泛适用于微服务架构MinimalApi一切都在组控制台应用程序类【Progr...

.NET外挂系列:3. 了解 harmony 中灵活的纯手工注入方式

一:背景1.讲故事上一篇我们讲到了注解特性,harmony在内部提供了20个HarmonyPatch重载方法尽可能的让大家满足业务开发,那时候我也说了,特性虽然简单粗暴,但只能解决95%...

C# 使用SemanticKernel调用本地大模型deepseek

一、先使用ollama部署好deepseek大模型。具体部署请看前面的头条使用ollama进行本地化部署deepseek大模型二、创建一个空的控制台dotnetnewconsole//添加依赖...

C#.NET 中间件详解(.net core中间件use和run)

简介中间件(Middleware)是ASP.NETCore的核心组件,用于处理HTTP请求和响应的管道机制。它是基于管道模型的轻量级、模块化设计,允许开发者在请求处理过程中插入自定义逻辑。...

IoC 自动注入:让依赖注册不再重复劳动

在ASP.NETCore中,IoC(控制反转)功能通过依赖注入(DI)实现。ASP.NETCore有一个内置的依赖注入容器,可以自动完成依赖注入。我们可以结合反射、特性或程序集扫描来实现自动...

C#.NET 依赖注入详解(c#依赖注入的三种方式)

简介在C#.NET中,依赖注入(DependencyInjection,简称DI)是一种设计模式,用于实现控制反转(InversionofControl,IoC),以降低代码耦合、提高可...

C#从零开始实现一个特性的自动注入功能

在现代软件开发中,依赖注入(DependencyInjection,DI)是实现松耦合、模块化和可测试代码的一个重要实践。C#提供了优秀的DI容器,如ASP.NETCore中自带的Micr...

C#.NET 仓储模式详解(c#仓库货物管理系统)

简介仓储模式(RepositoryPattern)是一种数据访问抽象模式,它在领域模型和数据访问层之间创建了一个隔离层,使得领域模型无需直接与数据访问逻辑交互。仓储模式的核心思想是将数据访问逻辑封装...

C#.NET 泛型详解(c# 泛型 滥用)

简介泛型(Generics)是指在类型或方法定义时使用类型参数,以实现类型安全、可重用和高性能的数据结构与算法为什么需要泛型类型安全防止“装箱/拆箱”带来的性能损耗,并在编译时检测类型错误。可重用同一...

数据分析-相关性分析(相关性 分析)

相关性分析是一种统计方法,用于衡量两个或多个变量之间的关系强度和方向。它通过计算相关系数来量化变量间的线性关系,从而帮助理解变量之间的相互影响。相关性分析常用于数据探索和假设检验,是数据分析和统计建模...

geom_smooth()函数-R语言ggplot2快速入门18

在每节,先运行以下这几行程序。library(ggplot2)library(ggpubr)library(ggtext)#用于个性化图表library(dplyr)#用于数据处理p...

规范申报易错要素解析(规范申报易错要素解析)

为什么要规范申报?规范申报是以满足海关监管、征税、统计等工作为目的,纳税义务人及其代理人依法向海关如实申报的行为,也是海关审接单环节依法监管的重要工作。企业申报的内容须符合《中华人民共和国海关进出口货...

「Eurora」海关编码归类 全球海关编码查询 关务服务

  海关编码是什么?  海关编码即HS编码,为编码协调制度的简称。  其全称为《商品名称及编码协调制度的国际公约》(InternationalConventionforHarmonizedCo...

9月1日起,河南省税务部门对豆制品加工业试行新政7类豆制品均适用投入产出法

全媒体记者杨晓川报道9月2日,记者从税务部门获悉,为减轻纳税人税收负担,完善农产品增值税进项税额抵扣机制,根据相关规定,结合我省实际情况,经广泛调查研究和征求意见,从9月1日起,我省税务部门对豆制品...