39 | | OK, when we create the closure in main, notice the extra parameter we're passing: an array of MDValues. These are the upvalues, and we just have one upvalue for this function. In the function 'count', we get the upvalue as an int, increment it, and then store it back in the upvalue. Don't forget to set the upvalues back |
---|
| 39 | OK, when we create the closure in main, notice the extra parameter we're passing: an array of MDValues. These are the upvalues, and we just have one upvalue for this function. In the function 'count', we get the upvalue as an int, increment it, and then store it back in the upvalue. Don't forget to set the upvalues back after you've modified them. |
---|
| 40 | |
---|
| 41 | Upvalues work, but they can be a little unwieldy. You have to get them and then set them after you modify them. Furthermore you can't store anything but convertible types in them, meaning you can't make a closure which keeps some D object or so as state. Fortunately, there's a simple solution. D delegates have a context pointer, which can be used to create closures, although they have to be made explicitly. All we have to do is make a struct which has the function as a member, and keep the persistent state in an instance of that struct. |
---|
| 42 | |
---|
| 43 | {{{ |
---|
| 44 | #!d |
---|
| 45 | void main() |
---|
| 46 | { |
---|
| 47 | MDContext ctx = NewContext(); |
---|
| 48 | ctx.globals["count"d] = ctx.newClosure(&(new Count).count, "count"); |
---|
| 49 | loadStatementString(ctx.mainThread, |
---|
| 50 | `writefln(count()); |
---|
| 51 | writefln(count()); |
---|
| 52 | writefln(count()); |
---|
| 53 | ` |
---|
| 54 | ); |
---|
| 55 | } |
---|
| 56 | |
---|
| 57 | struct Count |
---|
| 58 | { |
---|
| 59 | int val = 0; |
---|
| 60 | |
---|
| 61 | int count(MDState s, uint numParams) |
---|
| 62 | { |
---|
| 63 | val++; |
---|
| 64 | s.push(val); |
---|
| 65 | return 1; |
---|
| 66 | } |
---|
| 67 | } |
---|
| 68 | }}} |
---|
| 69 | |
---|
| 70 | Our counter example has been rewritten to use a struct to hold the persistent state instead. The count function is now simpler (and on a side note, more efficient), and we don't have to associate any upvalues with the closure when it's created. And just like the upvalue method, as long as we use a new instance of the Count struct for each closure, each closure will keep its own state. |
---|
| 71 | |
---|
| 72 | == Coroutines == |
---|
| 73 | |
---|
| 74 | The [LanguageSpec/Functions Functions] section of the language specification tells all about how to create and use coroutines from within MiniD. Now you'll learn how to do so from D, and it's really not all that different. |
---|
| 75 | |
---|
| 76 | A coroutine is represented by the MDState class. I guess you could say that there are two kinds of MDStates: "main threads" and "coroutines". "Main threads" are constructed without any coroutine function, and "coroutines" are. The equivalent of the MiniD "coroutine" expression is then just the construction of a new MDState with a closure as a parameter to the constructor. You also have to provide a context which will serve as the parent context of the coroutine. |
---|
| 77 | |
---|
| 78 | The "yield" expression in MiniD is replaced by MDState.yield in D. It's just as easy to use as it is in MiniD, although it follows the same kind of calling conventions that MDState.call, easyCall, and callMethod follow. |
---|
| 79 | |
---|
| 80 | Finally to resume a coroutine in MiniD, you just call it like a function. And that's exactly what you do in native code too -- just call it using another state as if it were a function. When you call a coroutine for the first time (when it leaves the initial state), the context is significant and becomes the context to the top-level coroutine function for the life of the coroutine, just like in MiniD. Subsequent calls to resume the coroutine ignore the context, again just like in MiniD. |
---|
| 81 | |
---|
| 82 | {{{ |
---|
| 83 | #!d |
---|
| 84 | import minid.minid; |
---|
| 85 | import minid.types; |
---|
| 86 | import tango.io.Stdout; |
---|
| 87 | |
---|
| 88 | void main() |
---|
| 89 | { |
---|
| 90 | MDContext ctx = NewContext(); |
---|
| 91 | MDState s = ctx.mainThread; |
---|
| 92 | MDState co = new MDState(ctx, ctx.newClosure(&coro, "co")); |
---|
| 93 | |
---|
| 94 | Stdout.formatln("Going to call the coroutine."); |
---|
| 95 | s.easyCall(co, 0, MDValue(5)); |
---|
| 96 | Stdout.formatln("Back in main, giving a value to the coroutine."); |
---|
| 97 | s.easyCall(co, 2, MDValue.nullValue, "hey there"); |
---|
| 98 | MDValue r2 = s.pop(); |
---|
| 99 | MDValue r1 = s.pop(); |
---|
| 100 | Stdout.formatln("Back in main, got values from the coroutine: {}, {}", r1.toUtf8(), r2.toUtf8()); |
---|
| 101 | s.easyCall(co, 1, MDValue.nullValue); |
---|
| 102 | r1 = s.pop(); |
---|
| 103 | Stdout.formatln("In main, coroutine is now in the '{}' state. Got the value '{}'.", co.stateString(), r1.toUtf8()); |
---|
| 104 | co.reset(); |
---|
| 105 | Stdout.formatln("Reset the coroutine, back in the '{}' state. Going to restart it with a new context..", co.stateString()); |
---|
| 106 | s.easyCall(co, 0, MDValue(10)); |
---|
| 107 | } |
---|
| 108 | |
---|
| 109 | int coro(MDState s, uint numParams) |
---|
| 110 | { |
---|
| 111 | Stdout.formatln("Entered the coroutine. Context is {}. About to yield.", s.getContext().toUtf8()); |
---|
| 112 | s.yield(1); |
---|
| 113 | MDValue x = s.pop(); |
---|
| 114 | Stdout.formatln("Back in the coroutine, got the following result from yield: '{}', going to yield some values.", x.toUtf8()); |
---|
| 115 | s.yield(0, MDValue("hi"), MDValue(1)); |
---|
| 116 | Stdout.formatln("Back in the coroutine, about to return a value."); |
---|
| 117 | s.push("Finished!"); |
---|
| 118 | return 1; |
---|
| 119 | } |
---|
| 120 | }}} |
---|
| 121 | |
---|
| 122 | This example shows both writing and calling a coroutine in native code. It's a bit more verbose than the corresponding MiniD code would be, but just as powerful. Notice that there's not even a single line of MiniD code used, it's all native! |
---|
| 123 | |
---|
| 124 | The MDState.yield method is very straightforward and follows the same kind of convention as easyCall and methodCall. The first parameter to MDState.yield is the number of returns you want, and the rest of the parameters are a variadic list of MDValues (slight difference from the other two). Pass -1 to mean "as many values as I get" and then get the return value of yield. When the yield returns, as many values as you requested are on the stack, and you pop them off. |