Anonymous functions and Reflections in Go

Source: Internet
Author: User
Tags rpn calculator
I recently saw an article that caught my eye when I browsed Hacker News, [Lambdas and Functions in Python] (http://www.thepythoncorner.com/2018/05/ Lambdas-and-functions-in-python.html?m=1), this article--I recommend you read it yourself--explains in detail how to use Python's lambda function, and an example of how Lambda The function implements clean, [DRY] (https://en.wikipedia.org/wiki/Don%27t_repeat_yourself) style code. Reading this article, the part of my brain that likes design patterns is excited about the ingenious design patterns in the article, but at the same time, the part of my brain that hates the dynamic language says, "Uh ~". A short digression to express my aversion to dynamic language (if you don't feel the same, skip it): I used to be a fan of dynamic languages (I still like dynamic languages for some tasks and I use them almost every day). Python is the language I have always chosen for my university, and I use it to do scientific calculations and do small, proof-of-concept projects (my personal site used to flask). But when I started to make a contribution to my first large Python project in the real World ([Qadium] (https://www.qadium.com/)), everything changed. These projects contain system responsibilities for collecting, processing, and enhancing various well-defined data types. At first we chose Python for two reasons: 1) Early employees were accustomed to using 2) it is a fast-developing language. When I started the project, we had just started our first business-to-business product, and several developers who had been using Python very early were involved. There are a few problems with the code left: 1) code difficult to read 2) code difficult to debug 3) code can hardly change/refactor without destroying something. Moreover, the code has been tested very little. These are the costs of quickly building a prototype system to validate the value of our first product. The problems mentioned above are so serious that later most of the development time is used to locate the problem, and there is little time to develop new features or modify the system to meet our growing desire and need to collect and process data. To solve these problems, I and some other engineers began to slow down, using a static type language to re-architect and rewrite the system (for me at the time, the overall experience was like building another new car while driving on a burning car). For the processing system, we chose the Java language, the data collection system, we chose the Go language. Two years later, I can honestlyIn fact, using static language, such as go (go still retains a lot of dynamic language feeling, such as Python). Now, I'm sure a lot of readers are muttering that "dynamic languages like Python are good, so just organize the code and test it," I don't want to be serious about it, but I would say that static language is a great help in solving problems in our system and is better suited to our system. When it comes to supporting and repairing the trouble here, our own Python-based build system is done, and I can say that I don't want to use dynamic language for any big projects in the short term. Well, then, when I saw this article, I saw some great design patterns in it, and I wanted to see if I could easily copy it to go. If you have not finished reading [the above mentioned article] (http://www.thepythoncorner.com/2018/05/lambdas-and-functions-in-python.html?m=1), I will use lambda/ The anonymous function solves the problem quoted below:> Suppose one of your clients asks you to write a program that simulates the "Inverse Polish expression Calculator", and they will install the program on their entire staff's computer. You have accepted this task, and have obtained the requirement of this program to show that the:>> program can do all the basic operations (subtraction), can find square root and square operation. Obviously, you should be able to clear all the stacks of the calculator or just delete the last value in the stack. If you are not familiar with the inverse Polish expression (RPN), you can find the first paper on the wiki. Now that the problem is resolved, the author of the previous article provides a usable but extremely redundant code. Porting it to go is the "Gopackage mainimport (" FMT "" math ") func main () {engine: = Newrpmengine () engine. Push (2) engine. Push (3) Engine.compute ("+") Engine.compute ("^2") Engine.compute ("SQRT") fmt. Println ("Result", engine.) Pop ())}//Rpmengine is a RPN calculation engine type rpmengine struct {stack stack}//newrpmengine returns a Rpmenginefunc newrpmengine () *RPME Ngine {return &rpmengine{stack:make (StACK, 0),}}//a value into the internal stack func (e *rpmengine) Push (v int) {e.stack = E.stack.push (v)}//take a value out of the internal stack func (e *rpmengine) Pop () int {var v inte.stack, V = e.stack.pop () return v}//computes an operation//if this operation returns a value, press the value stack func (e *rpmengine) Compute (Operation Stri NG) Error {switch operation {case ' + ': e.addtwonumbers () Case '-': e.subtracttwonumbers () Case ' * ': e.multiplytwonumbers () Case '/': e.dividetwonumbers () case ' ^2 ': e.pow2anumber () case ' SQRT ': e.sqrtanumber () case ' C ': E.pop () Case "AC": E.stack = Make (stack, 0) Default:return FMT. Errorf ("Operation%s not supported", operation)}return Nil}func (e *rpmengine) addtwonumbers () {op2: = E.pop () OP1: = E.Pop () E.push (OP1 + op2)}func (e *rpmengine) subtracttwonumbers () {op2: = E.pop () OP1: = E.pop () e.push (OP1-OP2)}func (e *rpmen Gine) Multiplytwonumbers () {op2: = E.pop () OP1: = E.pop () e.push (OP1 * op2)}func (e *rpmengine) dividetwonumbers () {op2: = E . Pop () Op1: = E.pop () e.push (OP1 * op2)}func (e *rpmengine) Pow2anumber () {OP1: = E.pop () e.push (OP1 * OP1)}func (e *rpmengine) Sqrtanumber () {OP1: = E.pop () e.push (int (math. Sqrt (Float64 (OP1)))} "> Rpn_calc_solution1.go hosted by GitHub, [view Source] (https://gist.github.com/jholliman/ 3F2461466CA1BC8E6B2D5C497DE6C198/RAW/179966BF7309E625AE304151937EEDC9D3F2D067/RPN_CALC_SOLUTION1.GO) Note: Go And there is not a stack that comes with it, so I created one myself. "' Gopackage maintype Stack []intfunc (s stack) Push (v int) stack {return append (S, v)}func (s stack) Pop () (stack, int) { L: = Len (s) return S[:L-1], S[l-1]} ' > Simple_stack.go hosted by GitHub, [view Source file] (https://gist.github.com/jholliman/ F1C8CE62CE2FBEB5EC4BC48F9326266B/RAW/08E0C5DF28EB0C527E0D4F0B2E85C1D381F7BC7C/SIMPLE_STACK.GO) (in addition, this stack is not thread-safe, And the ' Pop ' operation on the empty stack throws panic, except that the stack works well, but there's a lot of code duplication--especially to get the code that gives the operator the parameter/operation. Python-lambda article has made an improvement on this scheme by writing the arithmetic functions as lambda expressions and putting them into a dictionary so that they can be referenced by name, find the numeric value of an operation at run time, and provide these operands to the arithmetic function with normal code. The final Python code is as follows: "Python" "Engine class of the RPN Calculator" "" Import mathfrom Inspect import Signatureclass rpn_engine: def __init__ (self): "" "Constructor" "" Self.stack = []Self.catalog = Self.get_functions_catalog () def get_functions_catalog (self): "" "Returns the catalog of the functions Supported by the calculator "" "Return {" + ": Lambda x, y:x + y,"-": Lambda x, y:x-y," * ": Lambda x, y:x * y,"/": LA MBDA x, y:x/y, "^2": Lambda x:x * x, "SQRT": Lambda x:math.sqrt (x), "C": Lambda:self.stack.pop (), "AC": lambda:self . Stack.clear ()} def push (self, number): "" "" "" "" "" "" "" "" "" "" "" "" "" "" Internal Stack "" "Self.stack.append (number) def pop : "" "Pop a value from the stack" "" Try:return Self.stack.pop () except Indexerror:pass # don't notify any error if the Stack is empty ... def compute (self, operation): "" "compute an Operation" "" function_requested = Self.catalog[operation] Number_of_operands = 0 Function_signature = signature (function_requested) number_of_operands = Len (function_ signature.parameters) If number_of_operands = = 2:self.compute_operation_with_two_operands (Self.catalog[operation]) if number_of_operands = = 1:self.compute_opEration_with_one_operand (Self.catalog[operation]) if number_of_operands = = 0:self.compute_operation_with_no_ Operands (Self.catalog[operation]) def compute_operation_with_two_operands (Self, operation): "" "exec operations with Operands "" "Try:if Len (self.stack) < 2:raise baseexception (" Not enough operands on the stack ") OP2 = Self.stack. Pop () OP1 = Self.stack.pop () result = Operation (OP1, OP2) self.push (result) except Baseexception as Error:print (Error) de F Compute_operation_with_one_operand (Self, operation): "" "exec operations with one operand" "" TRY:OP1 = Self.stack.pop ( result = Operation (OP1) Self.push (result) except Baseexception as Error:print (error) def Compute_operation_with_no_ope Rands (self, operation): "" "exec operations with no operands" "" try:operation () except baseexception as Error:print (err OR) ' > engine_peter_rel5.py hosted by GitHub [view Source] (https://gist.github.com/mastro35/ 66044197fa886bf842213ace58457687/raw/a84776f1a93919fe83ec6719954384a4965f3788/engine_peter_rel5.py) This solution adds a little more complexity than the original scheme, but adding a new operator now is just as easy as adding a line! The first thought I saw was: how do I do it in Go? I know that in Go there is [function literal] (https://golang.org/ref/spec#Function_literals), which is a very simple thing, just like in a Python scenario, create a map of the operator's name and operator operation. It can be implemented as follows: "' Gopackage mainimport" math "func Main () {catalog: = map[string]interface{}{" + ": Func (x, y int) int {return x + Y}, "-": Func (x, y int) int {return XY}, "*": Func (x, y int) int {return x * y}, "/": Func (x, y int) int {return x /y}, "^2": func (x int) int {return x * x}, "SQRT": func (x int) int {return int (math. Sqrt (float64 (x))}, "C": Func () {/* Todo:need Engine Object */}, "AC": Func () {/* Todo:need Engine Object */},}}view Rawrpn_operations_map.go hosted with by GitHub ' > Rpn_operations_map.go hosted by GitHub [view Source file] (https://gist.github.com /jholliman/9108b105be6ab136c2f163834b9e5e32/raw/bcd7634a1336b464a9957ea5f32a4868071bef6e/rpn_operations_map.go Note: In the Go language, in order to save all our anonymous functions in the same map, we need to use an empty interface type, ' interfa{} '. All types in Go implement an empty interface (it is an interface with no methods; all types have at least 0 functions). On the ground floor, Go represents an interface with two pointers: one pointing to the value and the other pointing to the type. One way to identify the type that the interface actually holds is to use '. (type) ' to make an assertion, for example: ' ' Gopackage mainimport ("FMT" "math") func main () {catalog: = map[string]interface{} {"+": Func (x, y int) i NT {return x + y}, "-": Func (x, y int) int {return XY}, "*": Func (x, y int) int {return x * y}, "/": Func (x, y int) int {return x/y}, "^2": func (x int) int {return x * x}, "SQRT": func (x int) int {return int (math. Sqrt (float64 (x))}, "C": Func () {/* Todo:need Engine Object */}, "AC": Func () {/* Todo:need Engine Object */},}for K, V: = Range Catalog {Switch V. (type) {case func (int, int) int:fmt. Printf ("%s takes-operands\n", K) case func (int) int:fmt. Printf ("%s takes one operands\n", K) case func (): FMT. Printf ("%s takes zero operands\n", K)}} "> Rpn_operations_map2.go hosted by GitHub [view Source] (https://gist.github.com/ Jholliman/497733a937fa5949148a7160473b7742/raw/7ec842fe18026bfc1c4d801eca566b4c0541008b/rpn_operations_map2.go This code produces the following output (please forgive grammatical flaws): "' SQRT takes one Operandsac takes zero operands+ takes TWo Operands/takes, Operands^2 takes one Operands-takes two operands* takes, OPERANDSC takes zero operands ' ' this reveals a method, you can get the number of operands that an operator requires, and how to copy a Python solution. But how can we do better? Can we abstract a more general logic for the parameters required by the extract operator? Can we find the number of operands required for a function without an ' if ' or ' switch ' statement and call it? We can actually do that with the reflection function provided by the ' Relect ' package in Go. A brief description of the reflection in Go is as follows: In go, usually, if you need a variable, type, or function, you can define it and then use it. However, if you find that you need them at run time, or if you are designing a system that requires a variety of different types (for example, functions that implement operators-they accept different numbers of variables and therefore are different types), then you need to use reflection. Reflection gives you the ability to check, create and modify different types at run time. If you need more detailed Go reflection instructions and some basics of using the ' reflect ' package, see the [Reflection rules] (https://blog.golang.org/laws-of-reflection) blog. The following code demonstrates another workaround, using reflection to implement the number of operands needed to find our anonymous function: "' Gopackage mainimport (" FMT "" math "" reflect ") func main () {catalog: = map[ string]interface{}{"+": Func (x, y int) int {return x + y}, "-": Func (x, y int) int {return x-y}, "*": Func (x, y int) i NT {return x * y}, "/": Func (x, y int) int {return x/y}, "^2": func (x int) int {return × * x}, "SQRT": func (x int) in t {return int (math. Sqrt (float64 (x))}, "C": Func () {/* Todo:need EngiNe object */}, "AC": Func () {/* Todo:need Engine Object */},}for k, V: = Range Catalog {method: = reflect. ValueOf (v) Numoperands: = method. Type (). Numin () fmt. Printf ("%s has%d operands\n", K, Numoperands)} "> Rpn_operations_map3.go hosted by GitHub [view Source] (https:// Gist.github.com/jholliman/e3d7abd71b9bf6cb71eb55d49c40b145/raw/ed64e1d3f718e4219c68d189a8149d8179cdc90c/rpn_ OPERATIONS_MAP3.GO) similar and used '. (type) ' To switch the method, the code output is as follows: ' ' ^2 has 1 operandssqrt have 1 Operandsac has 0 operands* have 2 operands/has 2 OPERANDSC has 0 oper Ands+ has 2 Operands-has 2 operands "Now I no longer need to hardcode the number of parameters based on the signature of the function! Note: If the type of the value ([Kind (Kind)] (https://golang.org/pkg/reflect/#Kind) does not mix with the type) is not ' Func ', the call ' Tonumin ' will trigger ' panic ', so be careful to use because Panic occurs only at run time. By checking the Go ' reflect ' package, we know that if a value of type (Kind) is Func, we can do so by calling [' Call '] (https://golang.org/pkg/reflect/#Value. Calls) method, and pass it a slice of a value object to call this function. For example, we can do this: "' Gopackage mainimport (" FMT "" math "" reflect ") func main () {catalog: = map[string]interface{}{" + ": Func (x, y int) int {return x + y}, "-": Func (x, y int) int {return XY}," * ": Func (x, y int) int {return x * y},"/": Func (x, y int) int {return x/y}, "^2": func (x int) int {return x * x}, "SQRT": func (x int) int {return int (math. Sqrt (float64 (x))}, "C": Func () {/* Todo:need Engine Object */}, "AC": Func () {/* Todo:need Engine Object */},}method : = Reflect. ValueOf (catalog["+"]) operands: = []reflect. Value{reflect. ValueOf (3), reflect. ValueOf (2),}results: = method. Call (operands) fmt. PRINTLN ("The result is", int (results[0). Int ()))} "> Rpn_operations_map4.go hosted by GitHub [view Source] (https://gist.github.com/jholliman/ 96bccb0c73c75a165211892da87cd676/raw/e4b420f77350e3f7e081dddb20c0d2a7232cc071/rpn_operations_map4.go) Just like we expected. , this code will output: ' The result is 5 ' ' Cool! Now we can write our ultimate solution: "Gopackage mainimport (" FMT "" math "" reflect ") func main () {engine: = Newrpmengine () engine. Push (2) engine. Push (3) engine. Compute ("+") engine. Compute ("^2") engine. Compute ("SQRT") fmt. Println ("Result", engine.) Pop ())}//Rpmengine is a RPN compute engine type Rpmengine struct {stack stackcatalog map[string]interface{}}//newrpmengine returns a Rpmenginefunc newrpmengine () with the default function directory *rpmengi NE {Engine: = &rpmengine{stack:make (Stack, 0),}engine.catalog = map[string]interface{}{"+": Func (x, y int) int {Retu RN x + y}, "-": Func (x, y int) int {return XY}, "*": Func (x, y int) int {return x * y}, "/": Func (x, y int) int {ret Urn x/y}, "^2": func (x int) int {return x * x}, "SQRT": func (x int) int {return int (math. Sqrt (float64 (x))}, "C": Func () {_ = engine. Pop ()}, "AC": Func () {engine.stack = make (stack, 0)},}return engine}//presses a value into the internal stack func (e *rpmengine) Push (v int) {E.STAC K = E.stack.push (v)}//takes a value from the internal stack, func (E *rpmengine) pops () int {var v inte.stack, V = e.stack.pop () Return v}//calculates an operation//if This operation returns a value that pushes this value to stack func (e *rpmengine) Compute (operation string) error {Opfunc, OK: = E.catalog[operation]if!ok {return FMT. Errorf ("Operation%s not supported", operation)}method: = reflect. ValueOf (opfunc) Numoperands: = method. Type (). Numin () If Len (E.stacK) < Numoperands {return FMT. Errorf ("Too few operands for requested operation%s", operation)}operands: = Make ([]reflect. Value, Numoperands) for I: = 0; i < numoperands; i++ {operands[numoperands-i-1] = reflect. ValueOf (E.pop ())}results: = method. Call (operands)//IF The operation returned a result, put it on the Stackif len (results) = = 1 {result: = Results[0]. Int () E.push (Result)}return nil} ' > Rpn_calc_solution2.go hosted by GitHub [view Source file] (https://gist.github.com/ JHOLLIMAN/C636340CAC9253DA98EFDBFCFDE56282/RAW/B5F80BB79164B5250B088C6DF094CA4EC9840009/RPN_CALC_SOLUTION2.GO) We determine the number of operands (line 64th), get the operand (第69-72 row) from the stack, and then call the desired arithmetic function, and the call to the operands of the number of different arguments is the same (line 74th). And as with the Python solution, add new arithmetic functions, just add an anonymous function entry to the map (line 30th). To summarize, we already know that if you use anonymous functions and reflection to copy interesting design patterns from Python into the Go language. Excessive use of reflection I will be vigilant, reflection increases the complexity of the code, and I often see reflection used to bypass some bad designs. In addition, it will turn some errors that would have occurred during the compilation period into run-time errors, and it will significantly slow down the program (coming soon: Benchmark for both scenarios)-by [checking the source code] (https://golang.org/src/reflect/value.go?s= 9676:9715#l295), looks ' reflect. Value.call ' has done a lot of prep work, and for every callAll for it's ' []reflect. Value ' Returns the result parameter assigned a new slice. That is, if performance doesn't have to be a concern--it's not usually a performance concern in Python, there's enough testing, and our goal is to optimize the length of the code, and to make it easy to add new operations, reflection is a recommended method.

via:https://medium.com/@jhh3/anonymous-functions-and-reflection-in-go-71274dd9e83a

Author: John Holliman Translator: Moodwu proofreading: polaris1119

This article by GCTT original compilation, go language Chinese network honor launches

This article was originally translated by GCTT and the Go Language Chinese network. Also want to join the ranks of translators, for open source to do some of their own contribution? Welcome to join Gctt!
Translation work and translations are published only for the purpose of learning and communication, translation work in accordance with the provisions of the CC-BY-NC-SA agreement, if our work has violated your interests, please contact us promptly.
Welcome to the CC-BY-NC-SA agreement, please mark and keep the original/translation link and author/translator information in the text.
The article only represents the author's knowledge and views, if there are different points of view, please line up downstairs to spit groove

146 Reads
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.