Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Functional style: Lambda functions and maps

2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Internet Technology >

Share

Shulou(Shulou.com)06/03 Report--

First-level functions: what are Lambda functions and Map first-level functions?

You may have heard it say before that a particular language is functional because it has "first-class functionality". As I said in the first article in this series on functional programming, I don't agree with this popular view. I agree that first-class functions are a basic feature of any functional language, but I don't think it's a sufficient condition for a language to be functional. Many imperative languages also have this feature. But what is a first-rate function? The function is described as first class when they can be treated like any other value-that is, they can be dynamically assigned to a name or symbol at run time. They can be stored in data structures, passed in by function arguments, and returned as function return values.

This is not really a novel idea. Function pointers have been a feature of C since 1972. Before that, procedure references were a feature of Algol 68 that was implemented in 1970, when they were considered procedural programming features dating back to a long time ago, and Lisp (first implemented in 1963) was based on the concept that program code and data were interchangeable.

These are not vague characteristics either. In C language, we usually use functions as first-class objects. For example, when sorting:

Char * * array = randomStrings ()

Printf ("Before sorting:\ n"); for (int s = 0; s

< NO_OF_STRINGS; s++) printf("%s\n", array[s]); qsort(array, NO_OF_STRINGS, sizeof(char *), compare); printf("After sorting:\n");for (int s = 0; s < NO_OF_STRINGS; s++) printf("%s\n", array[s]); 这,这个,那,那个stdlibC中的库为不同类型的排序例程提供了一组函数。它们都能够对任何类型的数据进行排序:程序员所需要的唯一帮助就是提供一个比较数据集的两个元素并返回的函数。-1, 1,或0,指示哪个元素大于另一个元素或它们相等。 这本质上就是战略模式! 指向字符串的指针数组的比较器函数可以是: int compare(const void *a, const void *b){ char *str_a = *(char **) a; char *str_b = *(char **) b; return strcmp(str_a, str_b);} 然后,我们将其传递给排序函数,如下所示: qsort(array, NO_OF_STRINGS, sizeof(char *), compare); 控件上没有括号。compare函数名使编译器发出函数指针,而不是函数调用。因此,在C中将函数视为头等对象是非常容易的,尽管接受函数指针的函数的签名非常难看: qsort(void *base, size_t nel, size_t width, int (*compar)(const void *, const void *)); 函数指针不仅用于排序。早在.NET发明之前,就有用于编写MicrosoftWindows应用程序的Win 32 API。在此之前,有Win16API。它自由地使用函数指针作为回调。应用程序在调用窗口管理器时提供了指向其自身函数的指针,当应用程序需要通知某个已经发生的事件时,窗口管理器将调用该窗口管理器。您可以认为这是应用程序(观察者)与其窗口(可观察的)之间的一个观察者模式关系-应用程序接收到了发生在其窗口上的事件的通知,例如鼠标单击和按键盘。在窗口管理器中抽象了管理窗口的工作-移动窗口,将它们堆叠在一起,决定哪个应用程序是用户操作的接收者。这些应用程序对它们共享环境的其他应用程序一无所知。在面向对象的编程中,我们通常通过抽象类和接口来实现这种解耦,但也可以使用一流的函数来实现。 所以,我们使用一流的函数已经有很长时间了。但是,公平地说,没有一种语言比简陋的Javascript更能广泛地推广作为一流公民的功能。 Lambda表达式 在Javascript中,将函数传递给用作回调的其他函数一直是一种标准做法,就像在Win 32 API中一样。这个想法是HTML DOM的一个组成部分,其中第一类函数可以作为事件侦听器添加到DOM元素中: function myEventListener() { alert("I was clicked!")}...var myBtn = document.getElementById("myBtn")myBtn.addEventListener("click", myEventListener) 就像在C中一样,myEventListener函数名时,在调用addEventListener意味着它不会立即执行。相反,该函数与click事件中的DOM元素。当单击元素时,然后将调用该函数并发生警报。 流行的jQuery库通过证明通过查询字符串选择DOM元素的函数简化了流程,并提供了操作元素和向元素添加事件侦听器的有用函数: $("#myBtn").click(function() { alert("I was clicked!")}) 类中使用的第一类函数也是实现异步I/O的方法。XMLHttpRequest对象,它是Ajax的基础。同样的想法在Node.js中也很普遍。当您想要进行一个非阻塞函数调用时,将它传递给一个函数的引用,以便在它完成时调用您。 但是,这里还有别的东西。第二个例子不仅仅是一个一流函数的例子。它也是Lambda函数。具体而言,本部分: function() { alert("I was clicked!");} lambda函数(通常被称为兰卜达)是一个未命名的函数。他们本可以叫他们匿名函数,这样每个人都会立刻知道他们是什么。但是,这听起来不那么令人印象深刻,所以lambda函数就是!lambda函数的要点是在那里只需要一个函数;因为它在任何地方都不需要,所以您只需要在那里定义它。不需要名字。如果你做需要在其他地方重用它,然后考虑将它定义为一个命名函数,然后按名称引用它,就像我在第一个Javascript示例中所做的那样。如果没有lambda函数,使用jQuery和Node进行编程确实会令人厌烦。 LAMBDA函数以不同的方式以不同的语言定义: 在Javascript中:function(a, b) { return a + b } 在Java中:(a, b) ->

A + b

In C #: (a, b) = > a + b

In Clojure: (fn [a b] (+ a b))

In Clojure-shorthand version: # (+ 1 2)

In Groovy: {a, b-> a + b}

In F#: fun a b-> a + b

In Ruby, the so-called "stable" syntax:-> (a, b) {return a + b}

As we have seen, most languages tend to express lambdas in a more concise way than Javascript.

Map

You may have used the word "map" in your programming to refer to a data structure that stores an object as a key-value pair (if your language calls it a "dictionary," then there's no problem). In functional programming, the word has an extra meaning. In fact, the basic concept is the same. In both cases, one group of things is mapped to another. In the sense of data structures, mapping is a noun-key mapped to a value. In a programmatic sense, map is a verb-a function that maps one array of values to another.

Suppose you have a function f and a series of values A = [A1, A2, A3, A4] Map f pass A means to apply each element An in f:

A1 → f (A1) = a1'

A2 → f (A2) = a2'

A3 → f (A3) = A3'

A4 → f (A4) = A4'

Then, assemble the resulting array in the same order as the input:

Aids = map (f, A) = [A1, A2, A3, A4']

One by one

Okay, this is interesting, but bit math. How often do you do this? In fact, it's much more frequent than you think. As always, there is an example that explains things best, so let's take a look at a simple exercise I listed below, exercism.io, when I was learning Clojure. This movement is called "RNA transcription", and it is very simple. We will take a look at the input string that needs to be transcribed into an output string. These foundations are translated as follows:

C → G

G → C

→ U

T → A

Any input except C, G, A, T is invalid. The tests in JUnit 5 might look like this:

Class TranscriberShould {

@ ParameterizedTest @ CsvSource ({"Cpime G", "GMague C", "A Magi U", "TMagi A", "ACGTGGTCTTAA,UGCACCAGAAUU"}) void transcribe_dna_to_rna (String dna, String rna) {var transcriber = new Transcriber (); assertThat (transcriber.transcribe (dna), is (rna));}

@ Test void reject_invalid_bases () {var transcriber = new Transcriber (); assertThrows (IllegalArgumentException.class, ()-> transcriber.transcribe ("XCGFGGTDTTAA");}}

Also, we can pass the test with this Java implementation:

Class Transcriber {

Private Map pairs = new HashMap ()

Transcriber () {pairs.put ('cased,' G'); pairs.put ('Globe,' C'); pairs.put ('Atoll,' U'); pairs.put ('Tunable,' A');}

String transcribe (String dna) {var rna = new StringBuilder (); for (var base: dna.toCharArray ()) {if (pairs.containsKey (base)) {var pair = pairs.get (base); rna.append (pair);} else throw new IllegalArgumentException ("Not a base:" + base) } return rna.toString ();}}

The key to programming in a function style is, not surprisingly, to convert everything that might be represented as a function into a function. So, let's do this:

Char basePair (char base) {if (pairs.containsKey (base) return pairs.get (base); else throw new IllegalArgumentException ("Not a base" + base);}

String transcribe (String dna) {var rna = new StringBuilder (); for (var base: dna.toCharArray ()) {var pair = basePair (base); rna.append (pair);} return rna.toString ();}

Now, we can use map as a verb. In Java, a function is provided in Streams API:

Char basePair (char base) {if (pairs.containsKey (base) return pairs.get (base); else throw new IllegalArgumentException ("Not a base" + base);}

String transcribe (String dna) {return dna.codePoints () .mapToObj (c-> (char) c) .map (base-> basePair (base)) .map (StringBuilder::new, StringBuilder::append, StringBuilder::append) .toString ();}

Yeah

So, let's criticize this solution. The best thing to say is that the cycle is gone. If you think about it, looping is a clerical activity, and we really shouldn't pay attention to it most of the time. Usually, we loop because we want to do something for each element in the collection. What we really need to do here is to take the input sequence and generate an output sequence from it. Stream handles the basic management of iterations for us. In fact, it's a design pattern-a functional design pattern-but I don't want to mention its name yet. I don't want to scare you away yet.

I have to admit that the rest of the code is not that good, mainly because the primitives in Java are not objects. The first point that is not great is:

MapToObj (c-> (char) c)

We have to do this because Java handles primitives and objects differently, and although the language does set wrapper classes for primitives, it cannot get a collection of character objects directly from a string.

Another less awe-inspiring place is:

.clients (StringBuilder::new, StringBuilder::append, StringBuilder::append)

It's not clear why I called append twice. I will explain later, but now is not the right time.

I'm not going to defend this password.-that's too bad. If there is a convenient way to get a character stream object or even an array of characters from a string, there will be no problem, but we haven't got one yet. In Java, handling primitives is not the highlight of FP. Come to think of it, it's not even good for OO programming. So maybe we shouldn't be so obsessed with primitives. What if we design them outside the code? We can create an enumeration for the foundation:

Enum Base {C, G, A, T, U;}

Also, we have a class as a first-class collection of a series of bases:

Class Sequence {

List bases

Sequence (List bases) {this.bases = bases;}

Stream bases () {return bases.stream ();}}

Now, Transcriber looks like this:

Class Transcriber {

Private Map pairs = new HashMap ()

Transcriber () {pairs.put (C, G); pairs.put (G, C); pairs.put (A, U); pairs.put (T, A);}

Sequence transcribe (Sequence dna) {return new Sequence (dna.bases () .map (pairs::get) .map (toList ();}}

This is much better. This, this, that, that pairs::get is a method reference; it refers to an instance of the get method assigned to the pairs variable. By creating a type for the base, we design the possibility of invalid input, so we need the basePair method to disappear and the exception to disappear. This is one of the advantages of Java, which itself cannot enforce types in function contracts. More importantly, StringBuilder has also disappeared. Java flows are good when you need to iterate over a collection, process each element in some way, and build a new collection that contains the results. This may account for a large proportion of the cycles you write in your life. Most of the housework, not part of the real job at hand, is done for you.

In Clojure

Input deficiencies aside, Clojure is a little cleaner than the Java version, and it gives us the difficulty of mapping on the characters of a string. The most important abstraction in Clojure is the sequence; all collection types can be treated as sequences, and strings are no exception:

(def pairs {\ C, "G",\ G, "C",\ A, "U",\ T, "A"})

(defn- base-pair [base] (if-let [pair (get pairs base)] pair (throw (IllegalArgumentException. (str "Not a base:" base)

(defn transcribe [dna] (map base-pair dna))

The business side of this code is the last line. (map base-pair dna)-this is worth pointing out, because you may have missed it. It means map this, this, that, that base-pair function pairs dna strings (represented as sequences). If we want it to return a string instead of a list, that's what map gives us, and the only change we need is:

(apply str (map base-pair dna))

In C#

Let's try another language. The imperative approach to the solution in C# is as follows:

Namespace RnaTranscription {public class Transcriber {private readonly Dictionary _ pairs = new Dictionary {{'C','G'}, {'G','C'}, {'A','U'}, {'T','A'}}

Public string Transcribe (string dna) {var rna = new StringBuilder (); foreach (char b in dna) rna.Append (_ pairs [b]); return rna.ToString ();}

Similarly, C # does not tell us about the problems we encountered in Java, because strings in C # can be enumerated, and all "primitives" can be treated as objects with behavior.

We can rewrite the program in a more practical way, like this, and it turns out to be much less than the JavaStreams version. For "map" in the Java stream, change to "select" in C #:

Public string Transcribe (string dna) {return String.Join ("", dna.Select (b = > _ pairs [b]));}

Or, if you prefer, you can use LINQ as its syntax sugar:

Public string Transcribe (string dna) {return String.Join ("", from b in dna select _ pairs [b]);}

Why are we cycling?

You might know the idea. If you think about the time it took to write a loop, you usually try to do one of the following:

Map one type of array to another.

Filter by finding all the items in the array that satisfy a predicate.

Determines whether any item in the array satisfies certain predicates.

Accumulate counts, and, or other types of cumulative results from an array.

Sorts the elements of the array in a specific order.

The functional programming features available in most modern languages allow you to do all of these functions without having to write loops or create collections to store results. Functional style allows you to save the housework and focus on the real work. In addition, feature styles allow you to link actions together, for example, if necessary:

Map the elements of an array to another type.

Filter out some mapped elements.

Sort the filtered elements.

In an imperative style, this requires multiple loops or a loop, which contains a lot of code. Either way, it involves a lot of administrative work that obscures the true purpose of the project. In the functional style, you can distribute and manage the work and express yourself directly. Later, we will see more examples of functional styles that can make your life easier.

Next time

When I learn functional programming and get used to JavaStreams API, every time I write a loop, the next thing I do is think about how to rewrite it as a stream. This is usually possible. In C #, the ReSharper VisualStudio plug-in automatically recommends this kind of refactoring. Now that I've internalized the functional style, I'll go straight to the process, and unless I really need a loop, I don't need a loop. In the next article, we will continue to explore first-class functions and how to use function styles to make your code more expressive. Filter and reduce. Continue to follow!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Internet Technology

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report