Skip to content
Scroll to top↑

Generator自动执行器 ​

今天也是炫技的一天呢~原意是设法将异步回调变成瀑布执行,然后写出了一个比较奇怪的模式。

Js版本 ​

Promise、async和await不过是Generator的语法糖。

js
const autoRun = (gen) => {
  const g = gen();

  const run = () => {
    const result = g.next();
    if (result.done) return;
    result.value(run);
  };

  run();
};

autoRun(function* () {
  yield (next) => setTimeout(() => { // await new Promise((next) => setTimeout(next, 1000))
    console.log(2);
    next();
  }, 1000);
  yield (next) => setTimeout(() => {
    console.log(3);
    next();
  }, 1000);
});

Lua版本: ​

lua
local co = coroutine 

function setTimeout(callback, timeout)
  local start = os.clock()
  while os.clock() - start <= timeout do end
  callback()
end

local autoRun = function(gen)
  local g = co.create(gen)

  function run() 
    local _,result = co.resume(g)
    if result == nil then return end
    result(run)
  end

  run()
end

autoRun(function() 
  co.yield(function(next) 
    setTimeout(function() 
      print(2)
      next()
    end, 1)
  end)
  co.yield(function(next)
    setTimeout(function() 
      print(3)
      next()
    end, 1)
  end)
end)

Python版本 ​

python
import time

def setTimeout(callback, timeout):
    time.sleep(timeout)
    callback()

def autoRun(gen):
    g = gen()

    def run():
        result = next(g)
        if result != None:
            result(run)

    run()


autoRun(lambda: [
    (yield (lambda n: setTimeout(lambda: [print(2), n()], 1))),
    (yield (lambda n: setTimeout(lambda: [print(3), n()], 1))),
    (yield None)])

Racket版本 ​

用continuation实现。

scheme
#lang racket

(define (setTimeout callback timeout)
  (sleep timeout)
  (callback))
     
(define (autoRun g)
  (let [(next (call/cc (lambda (yield) (g yield))))]
    (if (eq? #f next)
      (void)
      (next))))

(autoRun (lambda (yield)
           (setTimeout (lambda () (println 2) (call/cc (lambda (next) (yield next)))) 1)
           (setTimeout (lambda () (println 3) (call/cc (lambda (next) (yield next)))) 1)
           (yield #f)))

Go版本 ​

用channel模拟yield。这种模拟给了我们一种很微妙的感觉,如果站在整个程序执行流的视角,写入channel和读取channel的两个线程没有任何同时执行的契机,名为多线程,其实还是单线程模型。

go
package main

import (
	"fmt"
	"time"
)

type Yield chan func(func())

func setTimeout(callback func(), timeout time.Duration) {
	time.Sleep(timeout * time.Millisecond)
	callback()
}

func autoRun(gen func(Yield)) {
	yield := make(Yield)
	
	var run func()

	run = func() {
		result := <- yield
		if result != nil {
			result(run)
		}
	}

	go gen(yield)
	run()
}

func main() {
	autoRun(func (yield Yield) {
		yield <- func(next func ()) {
			setTimeout(func () {
				fmt.Println(2)
				next()
			}, 1000)
		}
		yield <- func(next func ()) {
			setTimeout(func () {
				fmt.Println(3)
				next()
			}, 1000)
		}
		yield <- nil
	})
}

Rust版本 ​

也基于channel。对Rust的lifetime还不够了解,见注释。

rust
#![allow(non_snake_case)]
use std::{
    sync::mpsc::{sync_channel, SyncSender},
    thread,
    time::Duration,
};

fn setTimeout(callback: &dyn Fn(), timeout: u64) {
    std::thread::sleep(Duration::from_millis(timeout));
    callback();
}

type Lambda<Param> = Box<dyn Fn(Param) + Send + Sync>;

// Rust does not support recursive lambda function
struct Closure<'a> {
    lambda: &'a dyn Fn(&Closure),
}

// How to convert to SyncSender<Lambda<&Closure>> with appropriate lifetime?
type Yield = SyncSender<Box<dyn Fn(&Closure) + Send + Sync>>;

fn autoRun(gen: Lambda<Yield>) {
    let (tx, rx): (Yield, _) = sync_channel(1);

    let run = Closure {
        lambda: &|this| {
            if let Ok(result) = rx.recv() {
                result(this);
            }
        },
    };

    thread::spawn(move || gen(tx));
    (run.lambda)(&run);
}

pub fn test_yield() {
    autoRun(Box::new(|tx| {
        tx.send(Box::new(|next| {
            setTimeout(
                &|| {
                    println!("2");
                    (next.lambda)(next);
                },
                1000,
            );
        }))
        .unwrap();

        tx.send(Box::new(|next| {
            setTimeout(
                &|| {
                    println!("3");
                    (next.lambda)(next);
                },
                1000,
            );
        }))
        .unwrap();
    }));
}

C++版本 ​

channel不过是信号量的语法糖。

cpp
#include <future>
#include <thread>
#include "example/semaphore.h"

void setTimeout(std::function<void()> const& callback, std::chrono::milliseconds const& timeout) {
  std::this_thread::sleep_for(timeout);
  callback();
}

using Yield = std::function<void(std::function<void()>)>;
// simulate one size channel
class Channel {
 public:
  // write to channel
  friend void operator<<(Channel& chan, Yield const& data) {
    chan.slot.P();
    chan.data = data;
    chan.item.V();
  }

  // read from channel
  friend Yield const& operator>>(Channel& chan, void*) {
    chan.item.P();
    auto data = new Yield(chan.data);
    chan.slot.V();
    return *data;
  }

 private:
  Yield     data;
  Semaphore slot{1};
  Semaphore item{0};
};

void autoRun(std::function<void(Channel&)> const& gen) {
  Channel yield;

  std::function<void()> run = [&]() {
    auto result = yield >> 0;
    if (result == nullptr) return;
    result(run);
  };

  auto p = std::async([&]() { gen(yield); });
  run();
  p.wait();
}

int main() {
  using namespace std;
  using namespace std::chrono_literals;

  autoRun([](Channel& yield) {
    yield << [](std::function<void()> const& next) {
      setTimeout(
          [&next]() {
            cout << 2 << endl;
            next();
          },
          1000ms);
    };

    yield << [](std::function<void()> const& next) {
      setTimeout(
          [&next]() {
            cout << 3 << endl;
            next();
          },
          1000ms);
    };

    yield << nullptr;
  });

  return 0;
}