Recently saw a new JavaScript applet--streams, originally thought to be a common JavaScript class library, but after reading about it, I found that this is not a simple class library, and the author's focus is not the function of this class library, but-- Borrow a passage from the article: if you are willing to spend 10 minutes reading this article, your knowledge of programming may be completely changed (unless you have experience in functional programming!). )。 Tahe County Xianqing Machinery
Also: Streams is actually not a new idea. This feature is supported by a number of functional programming languages. The so-called ' stream ' is the term in the scheme language, and scheme is a dialect of LISP language. The Haskell language also supports an unlimited list of large lists. These ' take ', ' tail ', ' head ', ' map ' and ' filter ' names are all derived from the Haskell language. Python and many other languages also have different but similar concepts, which are called "generators" (generators). These ideas have been circulating for a long time in the functional programming community. However, it is a very new concept for most Javascript programmers, especially those who have no functional programming experience.
Stream.js
Stream.js is a small, completely standalone JavaScript class library (2k only), which provides you with a new JavaScript data structure: streams.
<script src= ' stream-min.js ' ></script>
What is streams? Streams is an easy-to-manipulate data structure, much like an array or linked table, but with some extraordinary capabilities attached.
What's so special about them? Unlike arrays, streams is a magical data structure. It can load an infinite number of elements. Yes, you heard right. His magic comes from the ability to delay (lazily) execution. This simple term completely indicates that they can load an infinite number of elements.
Download stream
Entry
If you are willing to spend 10 minutes reading this article, your knowledge of programming may be completely changed (unless you have the experience of functional programming!). )。 Just a little patience, let me introduce the basic functions that streams supports similar to arrays or linked tables. Then I'll introduce you to some of the very interesting features it has.
Stream is a container. It can accommodate elements. You can use Stream.make to have a Stream load some elements. Just pass in the desired element as a parameter:
Simple enough, now S is a stream:10 with 3 elements, and 30; In order. We can use S.length () to see the length of the stream, and use S.item (i) to take an index out of an element. You can also get the first element of the stream by calling S.head (). Let's actually take a look at the following:
var s = stream.make (ten, +); Console.log (S.length ()); Outputs 3 Console.log (S.head ()); Outputs Console.log (S.item (0));//exactly equivalent to the line above Console.log (S.item (1));//O Utputs Console.log (S.item (2));//Outputs 30
We can also use the new stream () or use Stream.make () directly to construct an empty stream. You can use the S.tail () method to get all the remaining elements in the stream except the first element. If you raise the S.head () or S.tail () method on an empty stream, an exception is thrown. You can use S.empty () to check if a stream is empty, which returns TRUE or false.
var s = stream.make (ten, +); var t = s.tail (); Returns the stream that contains, items:20 and Console.log (T.head ()); Outputs var u = t.tail (); Returns the stream that contains one item:30 console.log (U.head ()); Outputs var v = u.tail (); Returns the empty stream Console.log (V.empty ());//Prints True
This will print out all the elements in a stream:
var s = stream.make (ten, +); while (!s.empty ()) { Console.log (S.head ()); s = S.tail (); }
We have an easy way to achieve this: S.print () will print out all the elements in the stream.
What else can you do with them?
Another handy feature is the Stream.range (min, max) function. It returns a stream containing the natural number from min to Max.
var s = stream.range (ten); S.print (); Prints the numbers from ten to 20
On this stream, you can use features such as map, filter, and walk. S.map (f) accepts a parameter F, which is a function that all elements in the stream will be processed by F, and whose return value is a stream that has been processed by this function. So, for example, you can use it to double the number of numbers in your stream:
function Doublenumber (x) { return 2 * x; } var numbers = Stream.range (ten); Numbers.print (); Prints, one, one, and ten, and the var doubles = Numbers.map (doublenumber); Doubles.print (); Prints 20, 22, 24, 26, 28, 30
It's cool, isn't it? Similarly, S.filter (f) also accepts a parameter F, which is a function that all elements in the stream will be processed by this function, and its return value is a stream, but contains only elements that allow the F function to return true. So you can use it to filter some specific elements into your stream. Let's use this method to build a new stream with just an odd number based on the previous stream:
function checkifodd (x) { if (x% 2 = = 0) { //even number return false; } else { //odd number return true; } } var numbers = Stream.range (ten); Numbers.print (); Prints, one, one, and ten, and the var Onlyodds = Numbers.filter (checkifodd); Onlyodds.print (); Prints 11, 13, 15
It works, doesn't it? The last S.walk (f) method, which also accepts a parameter F, is a function that all elements in the stream are processed by this function, but it does not have any effect on the stream. The idea of printing all the elements in the stream has a new way of implementing it:
function Printitem (x) { console.log (' The element is: ' + x); } var numbers = Stream.range (ten, a); Prints: //The element is:10 //The element is:11 //The element is:12 Numbers.walk (Printitem) ;
There is also a useful function: S.take (n), which returns a stream containing only the first n elements of the original stream. This is useful when you use the stream to intercept it:
var numbers = Stream.range (10, 100); Numbers 10...100 var fewernumbers = Numbers.take (ten);//Numbers 10...19 fewernumbers.print ();
Other useful things: S.scale (factor) multiplies all the elements in the stream with factor (factor), and S.add (t) adds each element of stream s to the corresponding element in stream T, returning the result after the addition. Let's look at a few examples:
var numbers = Stream.range (1, 3); var multiplesoften = Numbers.scale (ten); Multiplesoften.print (); Prints, numbers.add (Multiplesoften). print ();//Prints 11, 22, 33
Although all we see now is manipulating numbers, the stream can load anything: strings, Booleans, functions, objects, and even other arrays or streams. Note, however, that some special values cannot be loaded in the stream: null and undefined.
Want me to show you the magic!
Now, let's deal with infinity. You don't need to add an infinite number of elements to the stream. For example, in the Stream.range (low, high) method, you can ignore its second parameter, written as Stream.range (low), in which case the data is not capped, so the Stream loads all the natural numbers from low to infinity. You can also ignore the low parameter, the default value of this parameter is 1. In this case, Stream.range () returns all the natural numbers.
Does this require you to use an infinite amount of memory/time/processing power? No, not at all. This is the most exciting part. You can run the code, they run very fast, just like an ordinary array. Here is an example of printing from 1 to 10:
var naturalnumbers = Stream.range (); Returns the stream containing all natural numbers from 1 and up var onetoten = Naturalnumbers.take (ten);//Return s the stream containing the numbers 1...10 onetoten.print ();
You're a liar! Yes, I was lying. The point is that you can think of these structures as infinity, which introduces a new paradigm of programming that is dedicated to simple code that makes your code easier to understand and closer to natural math programming paradigms than usual imperative programming. The JavaScript library itself is very short, and it is designed according to this programming paradigm. Let's use it more, we construct two streams, each loading all the odd numbers and all the even number.
var naturalnumbers = Stream.range (); Naturalnumbers is now 1, 2, 3, ... var evennumbers = Naturalnumbers.map (function (x) { return 2 * x; });//Evennumbers is now 2, 4, 6, ... var oddnumbers = Naturalnumbers.filter (function (x) { return x% 2 = 0; });//Oddnumbers is now 1, 3, 5,.. . Evennumbers.take (3). print (); Prints 2, 4, 6 oddnumbers.take (3). print ();//Prints 1, 3, 5
It's cool, isn't it? I'm not bragging, stream is more powerful than array. Now, please bear with me for a few minutes, let me introduce a bit more about the stream. You can use new stream () to create an empty stream, using the new stream (head, Functionreturningtail) to create a non-empty stream. For this non-empty stream, the first argument you pass into is the head element of the stream, and the second argument is a function that returns the end of the stream (a stream containing all the remaining elements), most likely an empty stream. Are you confused? Let's look at an example:
var s = new Stream (Ten, function () { return new stream (); }); The head of the S stream is 10; The tail of the S stream is the empty stream s.print ();//Prints var t = new Stream (Ten, function () { ret Urn New stream (function () {return new stream (+ , function () { return new stream (); }); }); } ); The head of the T stream is 10; Its tail have a head which is and a tail which //have a head which is an and a tail which is the empty stream.
t.print (); Prints 10, 20, 30
Nothing to do? This can be done directly with Stream.make (10, 20, 30). However, please note that this way we can easily build our infinite stream. Let's do a stream that's infinite:
function ones () { return new Stream ( //The first element of the stream of ones is 1 ... 1,//And the rest of the elements of this stream is given by calling the function ones (this same function!) Ones ); } var s = ones (); Now s contains 1, 1, 1, 1, ... S.take (3). print (); Prints 1, 1, 1
Note that if you use S.print () on an infinitely large stream, it will print endlessly and eventually drain your memory. So, you'd better S.take (n) before using S.print (). Using S.length () on an infinite stream is also meaningless, all, do not do these operations; it will cause an endless loop (trying to reach the end of an endless stream). But for infinite stream, you can use S.map (f) and S.filter (f). However, S.walk (f) is also bad for infinite stream. All, some things you have to remember; For the infinite stream, be sure to use S.take (n) to remove the finite portion.
Let's see if we can do something more interesting. There is also an interesting way to create a stream that contains natural numbers:
function ones () { return new Stream (1, ones); } function Naturalnumbers () { return new stream ( //The natural numbers is the stream whose first element is 1...
1, function () {//And the rest is the ' natural numbers all ' incremented by one //which was obtained by add ing the stream of natural numbers ... 1, 2, 3, 4, 5, ... to the infinite stream of ones ... 1, 1, 1, 1, 1, ... yielding ... 2, 3, 4, 5, 6, ... Which indeed is the REST of the natural numbers after one return ones (). Add (Naturalnumbers ()); } ); } Naturalnumbers (). Take (5). print (); Prints 1, 2, 3, 4, 5
The careful reader will find out why the second parameter of the newly constructed stream is a function that returns the tail, not the tail itself. This approach prevents an endless execution cycle by delaying the tail-intercept operation.
Let's look at a more complex example. The following is an exercise for the reader, please indicate what the following code is for?
function sieve (s) { var h = s.head (); return new Stream (H, function () { return sieve (S.tail (). filter (function (x) { return x% h! = 0; })); } ); } Sieve (Stream.range (2)). Take (). print ();
Make sure that you take the time to understand the purpose of this piece of code. Unless you have a functional programming experience, most programmers will find this code difficult to understand, so if you can't see it right away, don't feel depressed. Give you a hint: Find out what the head element of the printed stream is. Then find out what the second element is (the head element of the remaining element), then the third element, and then the fourth one. The name of this function can also give you some hints. If you're interested in this kind of puzzle, here are some:
var sequence = new Stream (1, function () { return new stream (1, function () { return Sequence.add (Sequence.tail ( ) ); } );} ); Sequence.take (). print ();
If you really can't figure out what this piece of code does, run it and take a look! So you can easily understand how it's done.
Salute
Streams is actually not a new idea. This feature is supported by a number of functional programming languages. The so-called ' stream ' is the term in the scheme language, and scheme is a dialect of Lisp language. The Haskell language also supports an unlimited list of large lists. These ' take ', ' tail ', ' head ', ' map ' and ' filter ' names are all derived from the Haskell language. Python and many other languages also have different but similar concepts, which are called "generators" (generators).
These ideas have been circulating for a long time in the functional programming community. However, it is a very new concept for most JavaScript programmers, especially those who have no functional programming experience. Many of the examples and ideas here are from structure and interpretation of computer programs. If you like these ideas, I highly recommend you read it; This book is available for free online. It is also the source of my creativity in developing this JavaScript class library.
If you like other syntax-style streams, you can try Linq.js, or, if you use node. js, Node-lazy might be better for you. If you like Coffeescript, Michael Blume is transplanting stream.js to coffeescript and creating Coffeestream.
Thank you for reading!
New JavaScript data structure streams