6

I'm trying to implement the C# keyword yield with in JavaScript/TypeScript (doesn't matter which): For example, I would like to implement the code:

//using System.Collections;  
//using System.Diagnostics; 
public static void Process()
{
    // Display powers of 2 up to the exponent of 8:  
    foreach (int number in Power(2, 8))
    {
        Debug.Write(number.ToString() + " ");
    }
    // Output: 2 4 8 16 32 64 128 256
}


public static IEnumerable Power(int baseNumber, int highExponent)
{
    int result = 1;

    for (int counter = 1; counter <= highExponent; counter++)
    {
        result = result * baseNumber;
        yield return result;
    }
}

in JavaScript.

The end goal is to implement a function written in C# from another question I asked about on stackoverflow, in JavaScript:

public static IEnumerable<string> SplitByCharacterType(string input)
{
    if (String.IsNullOrEmpty(input))
        throw new ArgumentNullException(nameof(input));

    StringBuilder segment = new StringBuilder();
    segment.Append(input[0]);
    var current = Char.GetUnicodeCategory(input[0]);

    for (int i = 1; i < input.Length; i++)
    {
        var next = Char.GetUnicodeCategory(input[i]);
        if (next == current)
        {
            segment.Append(input[i]);
        }
        else
        {
            yield return segment.ToString();
            segment.Clear();
            segment.Append(input[i]);
            current = next;
        }
    }
    yield return segment.ToString();
}

Any ideas?

Community
  • 1
  • 1
thed0ctor
  • 1,350
  • 4
  • 17
  • 34

5 Answers5

6

I don't think there's a reasonable way to make this work in the context of a for loop that preserves the C# semantics of lazy evaluation during the "move next" operation. You can simulate this reasonably with closures, though.

(TypeScript code):

function getPowers(base: number, maxExponent: number) {
    var currentExponent = 1;
    return function() {
        if(currentExponent > maxExponent) {
            return undefined;
        } else {
            return Math.pow(base, currentExponent++);
        }
    }
}

// Simple test
var p = getPowers(2, 8);
var n: number;
while((n = p()) !== undefined) {
    console.log(n);
}

// Demonstrate that multiple instances work
var p2 = getPowers(2, 3);
var p3 = getPowers(3, 3);
while(true) {
    var n2 = p2();
    var n3 = p3();
    if((n2 || n3) === undefined) break;

    console.log(n2 + ", " + n3);
}
Ryan Cavanaugh
  • 209,514
  • 56
  • 272
  • 235
2

I know it's many years later, but generators & iterators now exist in TypeScript, so this is now possible. You can read more of the details here: https://github.com/Microsoft/TypeScript/wiki/What%27s-new-in-TypeScript#typescript-23

I'm the original author of the SplitByCharacterType, so I figured I'd also give it a stab to try and re-implement in JS. The hardest problem is the lack of a native ability to differentiate character type (ex: GetUnicodeCategory). unicode-categories looks like it could be used to identify the character category. There is also an answer here for this. I'm going to use the second option as it looks more comprehensive. Note, that the getType() method used here is coming from that answer.

function* splitByCharacterType(input) {
    if (!input || !input.length) 
        return;

    var segment = [input[0]];
    var current = getType(input[0]);

    for (var i = 1; i < input.length; i++) {
        var item = input[i];
        var next = getType(item);
        if (next == current) {
            segment.push(item);
        } else {
            yield segment.join("");
            segment = [item];
            current = next;
        }
    }
    yield segment.join("");
}

This was also an interesting page on the subject: http://inimino.org/~inimino/blog/javascript_cset

caesay
  • 16,932
  • 15
  • 95
  • 160
  • The answer here is indeed the generator; but the async generator is a) overkill and b) not relevant to the original question. – Meirion Hughes May 15 '17 at 06:58
  • 1
    @MeirionHughes: This is definitely applicable to the question, it's literally titled "IEnumerable in JavaScript / Typescript", and as of 2.3.2 this is supported - so this is now the correct answer. Do you really have nothing better to do than to criticize valid and accurate answers that include a little extra information about the topic? – caesay May 15 '17 at 07:23
  • I'm curious about the performance implications ; is this something that can be used indiscriminately for any purpose where it makes sense (i.e negligible or non-existent performance impact), or should it be used only where performance does not matter? (I'm aware that in some cases the lazy nature of generators can actually improve performances, but there are cases where it doesn't) – youen Sep 09 '17 at 10:39
  • @youen: TypeScript compiles to native ES6 generators. ES5 generators (with the help of babel) can be quite a bit slower - up to 750x slower than a regular loop. However, native ES6 generators are supported in everything except for IE and provide near native performance in all cases. It's worth noting that TypeScript async/await functionality relies on ES6 generator support as well. Feel free to fact check me here: http://incaseofstairs.com/2015/06/es6-feature-performance/#user-content-generators – caesay Sep 11 '17 at 02:03
  • @caesay thanks for the pointer, very interesting. What I'm not sure to understand, is what happens if you target ES6 for browsers that don't support it? When targeting web browsers, are we not forced to output ES5 code so that it works everywhere? (unless you can afford to drop IE support, but not for large audience websites... ?). Also note that typescript async/await can generate ES5 code since typescript 2.1. – youen Sep 12 '17 at 07:29
  • @youen: If you target ES6 and want to support older browsers you should run your code through babel after typescript. It has support for converting the features into older (slower) code. – caesay Sep 12 '17 at 08:31
0

Some versions of JavaScript include yield but it's not widely supported (although Firefox does support it).

You might be able to improvise an alternative, although the hallmark of true 'yield' is multiple active callstacks, which requires threads (which is why yield can be simulated in Java) but standard JS doesn't have threads either.

You could look at Web Workers http://en.wikipedia.org/wiki/Web_worker but I suspect this will be far too heavyweight and restrictive for most applications benefitting from yield.

Jim Blackler
  • 22,946
  • 12
  • 85
  • 101
  • Threads are not required. Many languages implement multiple callstacks without threads (example: [lua coroutines](https://www.lua.org/pil/9.1.html)). At any given time, only one is being executed, and control flows from one to the other *explicitly*, such as on `yield` keywords. Threads on the other hand execute concurrently, and can switch to something else after any instruction, not even speaking about multi-CPU or multi-core hardware. This requires more work for programmers to get it right. I agree that a generator *can* be emulated with threads, but that would be a poor performance choice. – youen Sep 09 '17 at 10:49
  • You've read my (five year old) comment as claiming that threads imply concurrency. If 'controls throws from [multiple callstacks]' you have threads. But, as you rightly point out, some languages allow this but do not allow concurrency. – Jim Blackler Sep 23 '17 at 22:23
0

@Lajos Arpad: Having an array is not the same as having an IEnumerable. The core of this interface is on demand evaluation of the next element within an iteration.

+1 for Ryan.

Maybe the example could have been better in a sense that the call to the getPowers function should not have a maxExponent parameter. That is simply not required for a lazy evaluating IEnumerable. The consumer can pick as many powers as it wants to have.

Ryans example would also work in an unbounded scenario whereas any implementation with arrays must fail.

CSharper
  • 524
  • 3
  • 9
  • Actually, it seems that I cannot post comments on other posts. There just is no command link. I see it only on my own answers. And the important thing is a discussion getting to the core of what IEnumerable is. Programmers should get an understanding of this interface's semantics. So I don't understand why I was flagged negatively. – CSharper Dec 03 '12 at 21:13
  • You should be able to comment if you have 50+ reputation. So creating a new answer is fine till then. (I really think the entry for commenting should be lower) – Mene Dec 03 '12 at 22:14
0

So here is an example showing how an enumeration can be unbounded in its definition but bounded on the consumer side by reading only several elements:

        function getPowers(base) {
            var currentExponent = 1;
            return function () {
                return Math.pow(base, currentExponent++);
            }
        }

        function test() {
            // Simple test
            var p = getPowers(2);
            var elem = document.getElementById("a");
            elem.innerHTML = "<p>" + p() + "<p>" + p() + "<p>" + p() + "<p>" + p() + "<p>" + p() + "<p>" + p() + "<p>" + p();
        }
CSharper
  • 524
  • 3
  • 9