[译] Golang 语言中的函数类型

OmoruHadden 7年前
   <h2>golang语言中的函数类型(翻译)</h2>    <pre>  <code class="language-go">多数的开发者对ruby,javascript,或者python这种暴露高阶函数的动态语言是熟悉的。对于这些具有脚本背景的开发者,这是很难转换成go的把那些概念,因为go的类型系统似乎就是一个阻碍。另外一面,这些问题也许是相反的,对于哪些具有静态类型背景,原生的面向对象语言比如c++,或者java,静态类型系统是少一些绊脚石的。但是高阶函数的使用也似乎缺少直观性。程序员对函数的经验,大部分的认知留下的印象可能是非常缺乏想象力的。但是非常有益的是,这篇文章至少会阐述如何去使用go的类型系统中有关functions的概念。        这这篇文章中,我们将看几个函数类型在go中非常有用的例子。读者不需要假定是一个经验老道的go开发者,只要大概的一些关于go的知识,在理解这篇文章上就会变的非常有用。</code></pre>    <h2>匿名函数和闭包</h2>    <pre>  <code class="language-go">马上,go支持匿名函数和闭包,对我来说,go对匿名函数的支持是很容易让人联想起javascript对匿名函数的支持,但是它不是python的lambads(仅仅是一条语句)对匿名函数脆弱的支持,或者是ruby的套件那样有很多种方式去创建闭包(我不是ruby的粉丝,因此像这样的 proc/block/lambda/Method 事情对我来说是非常复杂)。不管怎么说,在go中声明匿名函数是非常的轻量:</code></pre>    <pre>  <code class="language-go">func(){  fmt.Println("hello")  }  </code></pre>    <pre>  <code class="language-go">匿名函数只是作为一个表达式,那是非常没有意义的,通常来说你想的是它被存到一个变量中,插入到数据结构里,或者把它传递到另外的函数当中。常常看到以下这种类型的代码出现在go中:</code></pre>    <pre>  <code class="language-go">fn:=func(){  fmt.Println("hello")  }  </code></pre>    <pre>  <code class="language-go">我们现在有一个变量fn,它是一个function;它的类型是func()。它能像其他的任何函数一样被调用,通过说fn(),或者赋值给你感兴趣的其他func()。当然,因为我们有对闭包的支持,我们也能引用一些定义在函数作用域当中的数据。</code></pre>    <pre>  <code class="language-go">x:= 5  fn:=func(){  fmt.Println("x is",x)  }  fn()  x++  fn()  </code></pre>    <p>以上代码片段的输出如下:</p>    <pre>  <code class="language-go">x is 5  x is 6  </code></pre>    <p><a href="/misc/goto?guid=4959725680716618005" rel="nofollow,noindex">run that example here</a> 到目前为止,这些看上去非常像javascript中看到的一样,而不是静态类型。</p>    <h2>函数集合</h2>    <pre>  <code class="language-go">当然,我们能够使用函数在所有我们能使用常规数据类型的地方。我们确实可以这样,创建一个切片函数,随机选取一个函数,然后执行它.我们定义了binFunc类型,它是一个二元一次函数,带有两个参数x,y,然后返回一个整数结果。对于这个例子来说这些都不是非常必要的。但是输入binFunc比一而再到处可以看到的输入func(int,int) int 是更加方便的。</code></pre>    <pre>  <code class="language-go">type binFunc fun(int, int) int  func main() {   // seed your random number generator.  rand.Seed(time.Now().Unix())     // create a slice of functions  fns:= []binFunc{  func(x,yint) int { returnx+y},  func(x,yint) int { returnx-y},  func(x,yint) int { returnx*y},  func(x,yint) int { returnx/y},  func(x,yint) int { returnx%y},   }     // pick one of those functions at random  fn:=fns[rand.Intn(len(fns))]     // and execute it  x,y:= 12, 5  fmt.Println(fn(x,y))  }  </code></pre>    <p>( <a href="/misc/goto?guid=4959725680795013055" rel="nofollow,noindex">run this example</a> )</p>    <p>当然,这个例子是很作的,但是它阐述了go的核心概念:函数是一等公民(first class)</p>    <h2>函数字段</h2>    <pre>  <code class="language-go">同样的,我们可能感兴趣把function类型用在结构体的字段上。这样做允许我们对一个函数加上一些额外的信息,比如运行时可以获取的标签。</code></pre>    <pre>  <code class="language-go">type opstruct {  namestring  fn func(int, int) int  }    func main(){   // send your random number generator  rand.Seed(time.Now().Unix())     //create a slice of ops  ops:= []op{   {"add",func(x,yint) int {returnx+y}},   {"sub",func(x,yint) int {returnx-y}},   {"mul"func(x,yint) int {returnx*y}},   {"div"func(x,yint) int {returnx/y}},   {"mod"func(x,yint) int {returnx%y}},   }     //pick one of these ops at random  o:=ops[rand.Intn(len(ops))]  x,y:= 12, 5  fmt.Println(o.name,x,y)  fmt.Println(o.fn(x,y))  }  </code></pre>    <p>function也能被存在map中,所以你也可以用map[string]binFunc做一些跟上面这个例子相似的事情。</p>    <h2>递归函数类型</h2>    <pre>  <code class="language-go">另外关于函数类型有趣的概念是它允许我们定义递归函数类型,他是一种操作自己本身的函数类型。就是说它既是函数的参数也是函数的返回值。使用这个技术你能做很多事情如果设计这样的函数是困难的,因此为了说明清楚,我已经设计了一种执行“随机步”的递归函数。是的,这是一个经过完整设计的例子,我们将看到一个walk函数,它具有一个整形指针参数,并且返回walk函数。</code></pre>    <pre>  <code class="language-go">type walkFn func(*int)walkFn  </code></pre>    <p>这个函数的预期是去增加这个指针所指向的值。然后返回一个函数描述了下一步要执行的内容。以下的walkEqual是一个有效的walkfn函数的实例:</p>    <pre>  <code class="language-go">func walkEqual(i*int)walkFn{   *i+=rand.Intn(7) - 3  }  </code></pre>    <p>这个函数运行起来的效果会是i所指向的整数会随机的+-3,然后函数的返回值是它自己。我们将会在下面执行这个随机函数。</p>    <p>当然,这是非常有趣的。让我们嵌套两个以上的函数,walkForward和walkBackward,这个会保证我们往前走(正数)的方向,和往后走(负数)的方向。每一个函数都会执行它自己,然后随机返回两者中的一个。我们开启一个小的但是完整的叫做pickRandom的函数。它将随机的选择一个作为输入函数,并且返回它。</p>    <pre>  <code class="language-go">func pickRandom(fns...walkFn)walkFn{   returnfns[rand.Intn(len(fns))]  }     然后我们定义的三个walk函数,如下所示:  packagemain    import (   "fmt"   "math/rand"   "time"  )    type walkFn func(*int)walkFn    func pickRandom(fns...walkFn)walkFn{   returnfns[rand.Intn(len(fns))]  }    func walKEqual(i*int)walkFn{   *i+=rand.Intn(7) - 3   returnpickRandom(walkForward,walkBackward)  }    func walkForward(i*int)walkFn{   *i+=rand.Intn(6)   returnpickRandom(walKEqual,walkBackward)  }    func walkBackward(i*int)walkFn{   *i+= -rand.Intn(6)   returnpickRandom(walKEqual,walkForward)  }    func main() {   // time is frozen on playground, so this is actually always   // the same. The behavior is different when run locally.  rand.Seed(time.Now().Unix())  fn,progress:=walKEqual, 0   fori:= 0;i< 20;i++ {  fn=fn(&progress)  fmt.Println(progress)   }  }    random output:   2   1   6   6   7   2   6   2   7   6   8   11   13   13   11   16   15   17   22   17  </code></pre>    <p>以上这个例子是不太合理的设计,因此我们要做的是关注递归函数的机制,在标准库中,这个技术被用在text/template中生成lexer。</p>    <h2>函数类型作为接口的值</h2>    <pre>  <code class="language-go">在go中函数类型可以有方法,这是很难去看到的,首先,为什么这是有用的。这里有两个方面的影响对于函数类型在go能有方法的事实。第一:因为任意类型能有防范就能满足接口,在go中函数类型也可能封装成有效的接口类型,第二:在go中方法既可以是指针或者值接收者,我们能使用函数指针的方法切换这个函数之乡的方法调用的上下文。这样说是有点让人兴奋的,所以让我们看两个事实。      首先,最明显的做法是,使得一个函数类型满足一个接口是选择一个接口只有一个方法。实现它是简单的。让我们定义一个add function就想我们上面做过的,但是我们也给它一个Error() string方法,在go中任何类型具有Error() string方法就是有效的error类型,所以我们的函数既是一个function也是一个error。你可以运行这个没什么用处的例子在你的终端上:</code></pre>    <pre>  <code class="language-go">packagemain    import(   "fmt"  )    type binFunc func(int, int) int    func add(x,yint) int {returnx+y}    func(f binFunc) Error() string {return "binFunc error"}    func main() {   varerr error  err=binFunc(add)  fmt.Println(err)  }  </code></pre>    <p>以上这个例子真的是没什么用处,我们不得不去执行类型转换,转换类型是func(int, int) int的add函数成binFunc。这个看上去像是繁重的性能损耗在go运行时,但是如果有另外一个有效的方式把 func(int, int) int转换成实现error:</p>    <pre>  <code class="language-go">type loudBinFunc func(int, int) int    func(f loudBinFunc) Error() string{   return "THIS ERROR IS A LOT LOUDER"  }  </code></pre>    <pre>  <code class="language-go">如果binFunc和loudBinFunc都定义了,运行时就不会知道如何去转换我们的add函数。及时接口只有一个有效的实现被给定的类型,运行时也不会自动的执行转换。它看上去是烦恼的,但是也使得写出没有bug的代码变的更容易。在标准库的net/http包中,我们能看到使用函数类型的实现接口的例子,http.Handler类型被用在标准库中注册http.handlers的接口。</code></pre>    <pre>  <code class="language-go">typeHandler interface{   ServerHTTP(ResponseWriter, *Request)  }  </code></pre>    <pre>  <code class="language-go">这里还有一种http.HandlerFunc类型,它只是包装了func(http.ResponseWriter, *http.Request)以满足http,Handler。这就使得我们可以用一个函数或者一个结构体去作为一个web的handler。最终的结果是我们同时获得了http.Handler和http.HandleFunc函数,使得写web的handler是更加的方便。      现在我们知道如何建立函数的方法从而允许我们去实现一个接口,我将展示一点难懂用法也是被证明非常有用的,让我们截取一些在json文档中的函数,在标准库的encoding/json包是一般的方法用来编码和解码json文档。这个包是简历在接口的基础之上,它定义了一个json.Unmarshaler接口,用来去反序列化json文档:</code></pre>    <pre>  <code class="language-go">typeUnmarshaler interface{   UnmarshalJSON([]bye)error  }  </code></pre>    <pre>  <code class="language-go">如果你定义了这个方法,  encoding/json包就会使用它去解析json数据,另外,它也试图去弄清楚自己要做什么(一般都是正确的),让我们拿出binFunc的例子,但是这次,我么要贴一些二元函数在json文档中,我们开始是老的binFunc定义:</code></pre>    <pre>  <code class="language-go">type binFunc func(int, int) int  </code></pre>    <p>因为函数本身没有任何持久化的数据,我们需要一个地方去注册名字。我们用map[string]binFunc去储存和我们函数相关的名字:</p>    <pre>  <code class="language-go">varfnRegistry=map[string]binFunc{   "add":func(x,yint) int { returnx+y},   "sub":func(x,yint) int { returnx-y},   "mul":func(x,yint) int { returnx*y},   "div":func(x,yint) int { returnx/y},   "mod":func(x,yint) int { returnx%y},  }  </code></pre>    <pre>  <code class="language-go">在go中,如果我们试图用不存在的key从map中获得value,这种操作将返回一个“0”值。对于一个函数类型,零值就意味着nil,我们只要做一个测试,赋一个nil去看我们将得到什么,现在我们要在binFunc类型上实现UnmarshalJSON方法。因为我们想要现在调用这个函数的看到一些不一样的。用指针类型实现UnmarshalJSON,并且因为我们知道我们想要一个字符串,我们只要托载一些存在的反序列化去兼容标准的json包。      有了这些定义,我们就能从json文档中反序列化到binFunc。接下来就是一个相对完整的例子。</code></pre>    <pre>  <code class="language-go">packagemain    import(   "encoding/json"   "fmt"   "log"   "math/rand"   "time"  )    varjsonDoc= []byte(`["add", "sub", "mul", "div"]`)    varregistry=map[string]binFunc{   "add":func(x,yint) int { returnx+y},   "sub":func(x,yint) int { returnx-y},   "mul":func(x,yint) int { returnx*y},   "div":func(x,yint) int { returnx/y},  }    type binFunc func(int, int) int    //实现了下面这个方法就可以自己反序列化自己  func(fn*binFunc) UnmarshalJSON(b[]byte)error{   varnamestring   iferr:=json.Umarshal(b, &name);err!= nil {   returnerr   }     // get the function out of our function registry  found:=registry[name]   iffound== nil {   // return a descriptive error if we can't find the function   returnfmt.Errorf("unknow function in (*binFunc)UnmarshalJSON: %s",name)   }   // dereference the pointer receiver, so that the changes are visible to the caller   *fn=found   return nil  }    func main() {  rand.Seed(time.Now().Unix())    varfns[]binFunc   iferr:=json.Unmarshal(jsonDoc, &fns);err!= nil {  log.Fatal(err)   }  fn:=fns[rand.Intn(len(fns))]  x:=fn(12, 5)  fmt.Println(x)  }  </code></pre>    <h2>函数和channel</h2>    <p>最后这个模式在go中是非常的特别的,而且这是你用channel组合函数发生的结果。完整的解释go的并发模型超出本文的范围。但是如果不想要雨里雾里对此,我推荐阅读这篇文章 <a href="/misc/goto?guid=4959725680884417216" rel="nofollow,noindex">the concurrency section in effective go</a> ,如果还有时间,看看这个非常棒的google io, <a href="/misc/goto?guid=4959725680964954269" rel="nofollow,noindex">go concurrency patterns</a> ,从先前的观点,一些关于go的知识还是要假设存在的。</p>    <pre>  <code class="language-go">因为channel是go的原语,并且我们能用其他的类型组成channel,我们甚至能用function组成channel,因为函数可以匿名,并且函数也可以闭包,用这个三个属性我们就可以组合成匿名闭包channel,对于这个channel类型的定义,可以让我们很容易意识到:chan func().保持住这篇文章的主题,让我们创建一个切片函数,但我们会让每一个函数都闭包:</code></pre>    <pre>  <code class="language-go">x:= 10  fns:= []func(){  func() {x+= 1 },  func() {x-= 1 },  func() {x*= 2 },  func() {x/= 2 },  func() {x*=x},  }  </code></pre>    <pre>  <code class="language-go">我们也捕捉我们之前看到过的代码片段,并且给出一种随机获取函数的机制:</code></pre>    <pre>  <code class="language-go">func pickFunc(fns...func())func() {   returnfns[rand.Intn(len(fns))]  }  </code></pre>    <pre>  <code class="language-go">接下来,我们定义一个func()类型的channel:</code></pre>    <pre>  <code class="language-go">c:=make(chan func())  </code></pre>    <pre>  <code class="language-go">并且我们定义了一个拿它自己当参数的函数channel的函数,数量描述了需要写入这个channel的函数的个数,然后就是一组候选函数。      在这个函数开始的时候,我们用defer close(c)确保我们在生产完数据之后关闭channel。在这里我们只使用常规的for循环,使用pickFunc随机获取一个函数,然后将我们给到的function写入channel,在接收端,我们使用range去读取channel中的值,取到值之后就立刻执行这个函数,通过在每一次读取值之后添加休眠函数,我们可以为任意闭包函数实现速率控制。</code></pre>    <pre>  <code class="language-go">forfn:=range c{  fn()  fmt.Println(x)  time.Sleep(delay)  }  </code></pre>    <p>将这些放在一起,我们得到了下面的程序:</p>    <pre>  <code class="language-go">packagemain    import(   "fmt"   "math/rand"   "time"  )  vardelay= 200*time.Millisecond    func pickFunc(fns...func())func(){   returnfns[rand.Intn(len(fns))]  }    func produce(c chan func(),nint,fns...func()){  defer close(c)   fori:= 0;i<n;i++ {  c<-pickFunc(fns...)   }  }    func main() {   // time is varibal  rand.Seed(time.Now().Unix())    x:= 10  fns:= []func(){  func() {x+=1},  func() {x+=1},  func() {x*=2},  func() {x/=2},  func() {x*=x},   }    c:=make(chan func())    go produce(c, 10,fns...)     forfn:=range c{  fn()  fmt.Println(x)  time.Sleep(delay)   }  }    random output:   20   10   11   22   44   1936   1937   1938   3755844   3755845  </code></pre>    <pre>  <code class="language-go">或者你也可以</code></pre>    <pre>  <code class="language-go">packagemain    import (   "fmt"   "math/rand"   "time"  )    vardelay= 200 *time.Millisecond    func pickFunc(fns...binFunc)binFunc{   returnfns[rand.Intn(len(fns))]  }    func produce(c chan binFunc,nint,fns...binFunc) {  defer close(c)   fori:= 0;i<n;i++ {  c<-pickFunc(fns...)   }  }    type binFunc func(int, int) int    func main() {   // time is varibal  rand.Seed(time.Now().Unix())  x:= 10  y:= 2  fns:= []binFunc{  func(x,yint) int { returnx+y},  func(x,yint) int { returnx+y},  func(x,yint) int { returnx+y},  func(x,yint) int { returnx+y},  func(x,yint) int { returnx+y},   }  c:=make(chan binFunc)    go produce(c, 10,fns...)     forfn:=range c{  fn(x,y)  fmt.Println(x)  time.Sleep(delay)   }  }  </code></pre>    <p>完结</p>    <p>原文链接: <a href="/misc/goto?guid=4959725681040362727" rel="nofollow,noindex">golang function type</a></p>    <p> </p>    <p>来自:https://www.zybuluo.com/aliasliyu4/note/567290</p>    <p> </p>