📜 ⬆️ ⬇️

About Go functionality

How object oriented Go has been repeatedly and emotionally discussed. Now let's try to evaluate how functional it is. Note immediately that the compiler does not optimize tail recursion. Why? “It is not necessary in a language with cycles. When a programmer writes a recursive code, he wants to represent the call stack or he writes a cycle. ”Notes Russ Cox in his correspondence. In the language, however, there are full-fledged lambda, closure, recursive types and a number of features. Let's try to apply them in a functional manner. The examples seem synthetic because firstly they are written immediately executed in the sandbox and written in a procedural language, secondly. It is supposed to be familiar with both Go and functional programming, explanations are few but the code is commented.

Closure, closure implemented in the language in the classical and full.
For example, a lazy recursive sequence can be obtained as
func produce(source int, permutation func(int) int) func() int { return func() int { // lambda source = permutation(source) // source return source } } 

Simple variator for pseudo-random numbers
 func mutate(j int) int { return (1664525*j + 1013904223) % 2147483647 } 

And here is our random number generator
 next := produce(1, mutate) next() 

Working example
 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()) } 

Try in sandbox
Currying. currying, applying a function to one of the arguments is not generally implemented. Particular problems are solved, however. For example, the deferred call function of the standard library time has the signature func AfterFunc (d Duration, f func ()) * Timer takes the argument func (), and we would like to pass something more parameterized to func (arg MyType) . And we can do it like this.
 type MyType string //  func (arg MyType) JustPrint(){ //  fmt.Println(arg) } 

a method in Go is a function that takes the first argument of its beneficiary
The expression MyType.JustPrint will give us this function with the signature func (arg MyType) , which we can apply to the argument MyType.JustPrint ("Eat me")
In contrast, the expression arg.JustPrint will give us the JustPrint function applied to arg with the signature func () which we can pass to our alarm clock
 timer := time.AfterFunc(50 * time.Millisecond, arg.JustPrint) 

Working example
 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) } 

Try in the sandbox.
Continuation, continuation as a first-class object is not implemented with the elegance that scneme has. Meanwhile, the built-in function panic () is an approximate analogue of long_jump capable of interrupting calculations and returning the achieved result (for example, an error) to the place from which the call was made. The construction of panic (), defer recover (), in addition to exception handling, can be used, for example, for end-to-end output from recursion that has gone too deep (as noted and done in the encoding.json package). In this sense, the design is first class, not exclusive. Exiting unnecessary recursion, it is worth emphasizing, this is a classic application of continuation.
Here is a straightforward, not optimized (not to be used in production !!) recursive function giving n-th Fibonacci number as the sum of the previous ones.
 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 } 

So we will call it with the continuation (call / cc) wanting to get the n-th Fibonacci number, unless it is more than max
 var max int = 200 func CallFib(n int) (res int) { defer func() { if r := recover(); r != nil { //  res = r.(int) //  } }() res = Fib(n) return } 

A working example.
 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(" ") } 

Try in the sandbox.
Monads in the understanding of Haskell procedural language is simply not needed. In Go, meanwhile, recursive type declarations are completely allowed, and many just think of monads as structural recursion. Rob Pike proposed the following definition of a state machine, a finite state machine.
 type stateFn func(Machine) stateFn 

where state is a function of the machine that produces actions and returns a new state.
The work of such a machine is simple
 func run(m Machine) { for state := start; state != nil; { state = state(m) } } 

Doesn't remind Haskell State Monad.
Let's write the minimum parser, and for what else the state machine is needed, which selects numbers from the incoming stream.
 type stateFn func(*lexer) stateFn type lexer struct { *bufio.Reader //   } 

We need only two states
 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 //  . } 

A working example.
 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) } 

Try in the sandbox.
Reactive programming is difficult to formally describe. This is something about streams and signals. Go has both. The standard library io offers interfaces io.Reader and io.Writer having the methods Read () and Write (), respectively, and fairly harmoniously reflecting the idea of ​​threads. The file and network connection for example implement both interfaces. You can use interfaces regardless of the data source, say
 Decoder = NewDecoder(r io.Reader) err = Decoder.Decode(Message) 

will uniformly encode a file or for example a network connection.
The idea of ​​signals is embodied in the syntax of the language. The chan (channel) type is equipped with the <- message transfer operator, and the select {case <-chan} unique construction allows you to select a channel that is ready for transmission from several.
Let's write a very simple stream mixer.
We take just strings as input streams. (We agreed to make the examples immediately executable in the sandbox, which limits the choice. It would be more interesting to read from the network connection. And the code can be almost unchanged.)
 reader1 := strings.NewReader("      ") reader2 := strings.NewReader("      ") 

Weekend we take the standard output stream
 writer := os.Stdout 

As control signals we use the timer channel.
 stop := time.After(10000 * time.Millisecond) tick := time.Tick(150 * time.Millisecond) tack := time.Tick(200 * time.Millisecond) 

And our whole mixer
 select { case <-tick: io.CopyN(writer, reader1, 5) case <-tack: io.CopyN(writer, reader2, 5) case <-stop: return } 

A working example.
 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 } } } 

Try in the sandbox.

')

Source: https://habr.com/ru/post/280210/


All Articles