クロージャー、クロージャーは、完全に言語で実装されています。
たとえば、次のように遅延再帰シーケンスを取得できます
func produce(source int, permutation func(int) int) func() int { return func() int { // lambda source = permutation(source) // source return source } }
単純な擬似乱数バリエーター
func mutate(j int) int { return (1664525*j + 1013904223) % 2147483647 }
そして、ここに乱数ジェネレーターがあります
next := produce(1, mutate) next()
実施例
サンドボックスで試す
package main import ( "fmt" ) func produce(source int, permutation func(int) int) func() int { return func() int { // lambda source = permutation(source) // source return source } } // func mutate(j int) int { return (1664525*j + 1013904223) % 2147483647 } func main() { next := produce(1, mutate) // fmt.Println(next()) fmt.Println(next()) fmt.Println(next()) fmt.Println(next()) fmt.Println(next()) }
カレー。 カリー化、引数の1つに関数を適用することは一般に実装されていません。 ただし、プライベートタスクは解決されます。 たとえば、標準ライブラリ時間の遅延呼び出し関数にはシグネチャfunc AfterFunc(d Duration、f func())があります* Timerは引数func()を受け取り、さらにパラメータ化されたfunc(arg MyType)を渡します。 そして、私たちはそのようにすることができます
type MyType string // func (arg MyType) JustPrint(){ // fmt.Println(arg) }
Goのメソッドは、受益者の最初の引数を取る関数です
式MyType.JustPrintは、この関数に署名func(arg MyType)を与えます。これは、引数MyType.JustPrint(“ Eat me”)に適用できます
それどころか、式arg.JustPrintは、目覚まし時計に渡すことができる署名func()を持つargに適用されるJustPrint関数を提供します
timer := time.AfterFunc(50 * time.Millisecond, arg.JustPrint)
実施例
サンドボックスで試してください。
package main import ( "fmt" "time" ) type MyType string // func (arg MyType) JustPrint() { // fmt.Println(arg) } func main() { arg := MyType("Hello") // time.AfterFunc(50*time.Millisecond, arg.JustPrint) arg = "By" time.AfterFunc(75*time.Millisecond, arg.JustPrint) time.Sleep(100 * time.Millisecond) }
一流の施設としての継続である継続は、スネムの優雅さでは実現しません。 一方、組み込み関数panic() long計算を中断し、呼び出し元の場所に結果(エラーなど)を返すことができるlong_jumpのおおよその類似物があります。 例外の処理に加えて、panic()、defer recover()の構築を使用して、たとえば、深すぎた再帰をパススルーすることができます(これはencoding.jsonパッケージで確認されています)。 この意味で、デザインは一流であり、排他的ではありません。 不要な再帰から抜け出す方法は、強調する価値があり、継続の古典的なアプリケーションです。
これは単純な、最適化されていない(プロダクションでは使用されない!!)再帰関数で、n番目のフィボナッチ数を前の
func Fib(n int) int { if n == 0 { return 0 } if n == 1 { return 1 } first := Fib(n - 1) second := Fib(n - 2) if first > max { // panic(second) // } return first + second }
したがって、最大値より大きくない限り、n番目のフィボナッチ数を取得したい継続(call / cc)で呼び出します。
var max int = 200 func CallFib(n int) (res int) { defer func() { if r := recover(); r != nil { // res = r.(int) // } }() res = Fib(n) return }
実用的な例。
サンドボックスで試してください。
package main import "fmt" var max int = 1000 func Fib(n int) int { if n == 0 { return 0 } if n == 1 { return 1 } first := Fib(n - 1) second := Fib(n - 2) if first > max { // panic(second) // } return first + second } func CallFib(n int) (res int) { defer func() { if r := recover(); r != nil { // res = r.(int) // } }() res = Fib(n) return } func main() { fmt.Println(CallFib(10)) // fmt.Println(CallFib(100000)) // fmt.Println(" ") }
Haskellは手続き言語のモナドを必要としません。 一方、Goでは、再帰的な型宣言が完全に許可されており、多くの人がモナドを構造的再帰の一種と見なしています。 ロブパイクは、ステートマシン、ステートマシンの次の定義を提案しました。
type stateFn func(Machine) stateFn
ここで、状態は、アクションを生成して新しい状態を返すマシンの機能です。
このようなマシンの操作は簡単です。
func run(m Machine) { for state := start; state != nil; { state = state(m) } }
Haskell State Monadに似ていません。
最小限のパーサーを作成します。他に何をするために、着信ストリームから数値を選択するステートマシンが必要です。
type stateFn func(*lexer) stateFn type lexer struct { *bufio.Reader // }
必要な状態は2つだけです
func lexText(l *lexer) stateFn { for r, _, err := l.ReadRune(); err != io.EOF; r, _, err = l.ReadRune() { if '0' <= r && r <= '9' { // l.UnreadRune() return lexNumber // } } return nil // . } func lexNumber(l *lexer) stateFn { var s string for r, _, err := l.ReadRune(); err != io.EOF; r, _, err = l.ReadRune() { if '0' > r || r > '9' { // num, _ := strconv.Atoi(s) return lexText // } s += string(r) } num, _ := strconv.Atoi(s) return nil // . }
実用的な例。
サンドボックスで試してください。
package main import ( "bufio" "fmt" "io" "strconv" "strings" ) type stateFn func(*lexer) stateFn func run(l *lexer) { for state := lexText; state != nil; { state = state(l) } } type lexer struct { *bufio.Reader // , } var output = make(chan int) // func lexText(l *lexer) stateFn { for r, _, err := l.ReadRune(); err != io.EOF; r, _, err = l.ReadRune() { if '0' <= r && r <= '9' { // l.UnreadRune() return lexNumber // } } close(output) return nil // . } func lexNumber(l *lexer) stateFn { var s string for r, _, err := l.ReadRune(); err != io.EOF; r, _, err = l.ReadRune() { if '0' > r || r > '9' { num, _ := strconv.Atoi(s) output <- num // return lexText // } s += string(r) } num, _ := strconv.Atoi(s) output <- num close(output) return nil // . } func main() { var sum int a := "hell 3456 fgh 25 fghj 2128506 fgh 77" // , fmt.Println(" : ", a) rr := strings.NewReader(a) // lexy := lexer{bufio.NewReader(rr)} go run(&lexy) // for nums := range output { fmt.Println(nums) sum += nums } fmt.Println(" : ", sum) }
リアクティブプログラミングを正式に記述することは困難です。 これは、ストリームと信号に関するものです。 Goには両方があります。 標準のioライブラリはio.Readerおよびio.Writerインターフェイスを提供します。これらのインターフェイスにはそれぞれRead()およびWrite()メソッドがあり、ストリームの概念を調和的に反映しています。 たとえば、ファイルとネットワーク接続は両方のインターフェイスを実装します。 インターフェイスは、データソースに関係なく使用できます。
Decoder = NewDecoder(r io.Reader) err = Decoder.Decode(Message)
ファイルやネットワーク接続などを均一にエンコードします。
信号の概念は、言語の構文に組み込まれています。 chan (チャネル)タイプには<-メッセージ転送演算子が装備されており、一意のselect {case <-chan}構造により、複数のチャネルから送信可能なチャネルを選択できます。
非常に単純なストリームミキサーを作成しましょう。
入力として、文字列を使用します(サンドボックスでサンプルをすぐに実行できるようにすることに同意しました。これにより、選択が制限されます。ネットワーク接続からの読み取りはさらに興味深いでしょう。
reader1 := strings.NewReader(" ") reader2 := strings.NewReader(" ")
出力は標準出力ストリームを受け入れます。
writer := os.Stdout
タイマーチャネルを制御信号として使用します。
stop := time.After(10000 * time.Millisecond) tick := time.Tick(150 * time.Millisecond) tack := time.Tick(200 * time.Millisecond)
ミキサー全体
select { case <-tick: io.CopyN(writer, reader1, 5) case <-tack: io.CopyN(writer, reader2, 5) case <-stop: return }
実用的な例。
サンドボックスで試してください。 package main import ( "io" "os" "strings" "time" ) func main() { stop := time.After(10000 * time.Millisecond) tick := time.Tick(150 * time.Millisecond) tack := time.Tick(200 * time.Millisecond) reader1 := strings.NewReader(" ") reader2 := strings.NewReader(" ") writer := os.Stdout for { select { case <-tick: io.CopyN(writer, reader1, 5) case <-tack: io.CopyN(writer, reader2, 5) case <-stop: return } } }