Coder Social home page Coder Social logo

genawaiter's Introduction

genawaiter

crate-badge docs-badge ci-badge

This crate implements stackless generators (aka coroutines) in stable Rust. Instead of using yield, which won't be stabilized anytime soon, you use async/await, which is stable today.

Features:

  • supports resume arguments and completion values
  • supports async generators (e.g., Streams)
  • allocation-free
  • no runtime dependencies
    • no compile-time dependencies either, with default-features = false
  • built on top of standard language constructs, which means there are no platform-specific shenanigans

Example:

let odd_numbers_less_than_ten = gen!({
    let mut n = 1;
    while n < 10 {
        yield_!(n); // Suspend a function at any point with a value.
        n += 2;
    }
});

// Generators can be used as ordinary iterators.
for num in odd_numbers_less_than_ten {
    println!("{}", num);
}

Result:

1
3
5
7
9

And here is the same generator, this time without macros. This is how you do things with default-features = false (which eliminates the proc macro dependencies).

let odd_numbers_less_than_ten = Gen::new(|co| async move {
    let mut n = 1;
    while n < 10 {
        co.yield_(n).await;
        n += 2;
    }
});

See the docs for more.

Development

Install prerequisites

Install the pre-commit hook

pre-commit install

This installs a Git hook that runs a quick sanity check before every commit.

Run the app

cargo run

Run the tests

cargo test

genawaiter's People

Contributors

devinr528 avatar lovasoa avatar thomaseizinger avatar whatisaphone avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

genawaiter's Issues

Convenience macros and 0.99 prerelease

I think we should:

  • Write a set of convenience macros, so you don't need two steps to create a generator. These don't have to be proc macros, they can be simple macro_rules! macros that use the proc macros internally. (So they'll need to be gated on the proc_macros feature.)
  • Turn on the proc_macro feature by default
  • Steer users towards the new macros in the README and docs.

I envision something like this:

use genawaiter::sync::gen;

let generator = gen!({
    let mut n = 1;
    while n < 10 {
        yield_!(n);
        n += 2;
    }
});

I'm not sure off the top of my head if that syntax will also work for the stack generators, if not we might unfortunately have to mirror generator_mut! and do something like genawaiter::stack::gen_mut!(binding, {}).

And after this, write docs and release a new version!

Investigate trybuild

Our compile-fail doctests are terrible and janky

trybuild, first seen here, looks much better. If it's really that easy, we should migrate all the compile-fail tests

Stack generator safety suggestion

I think it is possible to make stack generator safe. The Co object is not Clone, therefore the only possibility to outlive the Gen is to return it from the coroutine, am I right? It is even impossible to yield it. Type system can require the coroutine return the Co object along with its return value.

pub struct Gen<'s, Y, R, F: Future> {

will be

pub struct Gen<'s, Y, R, Ret, F: Future<Output = (Co<'s, Y, R>, Ret)>> {

and

    pub fn resume_with(&mut self, arg: R) -> GeneratorState<Y, F::Output> {

will be

    pub fn resume_with(&mut self, arg: R) -> GeneratorState<Y, Ret> {

The user is forced to return the Co, and they cannot clone it, so it will be the same Co, and Ret cannot contain one more.

Forcing Send on a guard when using genawaiter

This is more of a question rather than an issue, we're using parking_lot with genawaiter to get an iterator without lifetime from a struct.

   use genawaiter::sync::{gen, GenBoxed};
   use genawaiter::yield_;

    struct Example<T>(Arc<parking_lot::RwLock<Vec<T>>>);

    impl<T: Clone> Example<T> {
        fn iter(&self) -> impl Iterator<Item = T> {
            let tgshard = self.0.clone();
            let iter: GenBoxed<T> = GenBoxed::new_boxed(|co| async move {
                let g = tgshard.read();
                let iter = (*g).iter();
                for t in iter {
                    co.yield_(t.clone()).await;
                }
            });

            iter.into_iter()
        }
    }

this is not allowed by the rust compiler because the guard is not Send.

However, is forcing the guard in this scenario (using genawaiter ) to be Send an actual problem?
I don't think the guard will be moved onto another thread. (that being said I have no clue how genawaiter works)

Basically is this workable?

   use genawaiter::sync::{gen, GenBoxed};
   use genawaiter::yield_;
   
    struct MyLock<T>(parking_lot::RwLock<T>);
    struct MyGuard<'a, T>(parking_lot::RwLockReadGuard<'a, T>);

    impl<T: Clone> MyLock<T> {
        fn read(&self) -> MyGuard<T> {
            MyGuard(self.0.read())
        }
    }

    impl<T> Deref for MyGuard<'_, T> {
        type Target = T;
        fn deref(&self) -> &Self::Target {
            self.0.deref()
        }
    }

    unsafe impl<T> Send for MyGuard<'_, T> {}

    struct Example2<T>(Arc<MyLock<Vec<T>>>);

    impl<T: Clone + std::marker::Send + std::marker::Sync + 'static> Example2<T> {
        fn iter(&self) -> impl Iterator<Item = T> {
            let tgshard = self.0.clone();
            let iter: GenBoxed<T> = GenBoxed::new_boxed(|co| async move {
                let g = tgshard.read();
                let iter = (*g).iter();
                for t in iter {
                    co.yield_(t.clone()).await;
                }
            });

            iter.into_iter()
        }
    }

Maintainership status of genawaiter

Should this library be used in real projects or is it unmaintained?

There seems to be no activity recently and there is one unattended pull request.

Can't figure out why this code won't compile

I have the following bit of code and it won't compile:
use {serde::{de::DeserializeOwned}, std::io::Read, genawaiter::{yield_, stack::let_gen}};

fn stream_iter<T: DeserializeOwned, U: Read>(reader: U) -> impl Iterator<Item=serde_json::Result> {
let_gen!(output, {
loop {
yield_!(serde_json::from_reader<U, T>(reader));
}
});
return output.into_iter();
}
fn main() {
}
The generator is supposed to read values of type T from an std::io::Read by using serde_json to deserialize the data.
The error I am getting is this:
error: no rules expected the token @
--> src\bin\stdin_json.rs:4:2
|
4 | / let_gen!(output, {
5 | | loop {
6 | | yield_!(serde_json::from_reader<U, T>(reader));
7 | | }
8 | | });
| |_______^ no rules expected this token in macro call
|
= note: this error originates in a macro (in Nightly builds, run with -Z macro
-backtrace for more info)

error: aborting due to previous error

Thanks for the library BTW. Generators are cool!

Is it possible to make a higher-order stack-based producer?

I'd like to make a reusable stack-based producer that's parameterized by some value. For example, as a modification of one of the examples from the docs:

use genawaiter::stack::{Co, Gen, Shelf};

pub fn main() {
    let check_numbers_2 = |offset: i32| {
        |co: Co<'_, (), i32>| async move {
            let num = co.yield_(()).await;
            assert_eq!(num, 1 + offset);

            let num = co.yield_(()).await;
            assert_eq!(num, 2 + offset);
        }
    };

    let mut shelf = Shelf::new();
    let mut gen = unsafe { Gen::new(&mut shelf, check_numbers_2(0)) };

    gen.resume_with(0);
    gen.resume_with(1);
    gen.resume_with(2);
}

The trouble with this (I think) is that the stack version of Co has a reference to the Airlock:

error: borrowed data cannot be stored outside of its closure
  --> src/main.rs:5:42
   |
5  |           |co: Co<'_, (), i32>| async move {
   |  _________---------------------____________^
   | |         |
   | |         ...because it cannot outlive this closure
6  | |             let num = co.yield_(()).await;
7  | |             assert_eq!(num, 1 + offset);
8  | |
9  | |             let num = co.yield_(()).await;
10 | |             assert_eq!(num, 2 + offset);
11 | |         }
   | |_________^ cannot be stored outside of its closure
...
15 |       let mut gen = unsafe { Gen::new(&mut shelf, check_numbers_2(0)) };
   |           ------- borrowed data cannot be stored into here...

Is there some other way to go about this, or am I missing some obvious way to plumb the lifetimes through?

storing generators inside structs

Hi there. Is it possible to store generators as a field of a struct? I try to implement a parser state machine using generators. I need to perform operations whenever a specific pattern is parsed through a ParserListener trait. In order to consume input I would like to control this generator using an outer struct. Here is the link to related code I wrote. https://github.com/orhanbalci/memterm/blob/e5e4ed04cf288fa1c9590250210312281b34c648/src/parser.rs#L19C27-L19C37

Easy error propagation?

Currently if I want to propagate errors I have to do:

move |co| {
  async move {
    if let Err(e) = async {
      co.yield_(my_fallible_op()?).await;
      Ok(())
    }.await {
      co.yield_(Err(e)).await;
    }
  }
}

This is not very ergonomic.

Suggestion for small performance improvement

Hi,
nice project!

I just did a small prototype before knowing of this crate, which ended up implementing the same thing as you did.
Although mine is just a prototype, I spotted a simple change you could do to improve your speed a tiny bit:

How

Replace your create function in waker.rs with:

pub fn create() -> impl std::ops::Deref<Target = Waker> {
    // Safety: The waker points to a vtable with functions that do nothing. Doing
    // nothing is memory-safe.
    std::mem::ManuallyDrop::new(unsafe { Waker::from_raw(RAW_WAKER) })
}

Nothing else would have to change because you only ever use the waker as reference and ManuallyDrop implements Deref.

Why

Your waker's drop method is a noop because it has no data to clean up.
However the Waker instance doesn't know this and will always call drop resulting in a virtual function call whose body does nothing.
By wrapping the Waker in ManuallyDrop you avoid this single unnecessary function call which is done in each advance call.

Add a convenience trait for Generator + Iterator?

I found myself writing a generator for an impl IntoIterator type---I had originally used impl Generator before realizing that it wasn't what I wanted because I wanted synchronous usage only. But in theory there's no reason not to expose in my API that the same result could be used as an async Generator. So perhaps there should be a convenience trait that implements both?

trait GenIter : IntoIterator + Generator<Yield = <Self as IntoIterator>::Item, Return = ()> {}

impl<T: IntoIterator + Generator<Yield = <Self as IntoIterator>::Item, Return = ()> GenIter for T {}

How to handle resume types with lifetimes

Just something I've run into and have been thinking about. I'm not precisely sure how much it would impact the API (probably too much!), and what the advantages and disadvantages are, or any of the workarounds, but I figured the maintainer might have some thoughts to add on the matter.


Basically: What might it take to be able to get the "resume" type (R) to support short-lived, non-overlapping lifetimes?

Consider the following program, which implements an event loop as an internal iterator.

pub fn main() {
    event_loop(event_handler());
}

pub struct Knob;
impl Knob {
    pub fn get_from_somewhere() -> Self { Knob }
    pub fn frobnicate(&mut self) { println!("tweaking knob"); }
    pub fn use_somehow(&self) { println!("using knob somehow"); }
}

pub struct Event<'a> { knob: &'a mut Knob }
pub enum LoopStatus<T> { Continue, Break(T) }

pub fn event_loop<T>(
    mut callback: impl FnMut(Event<'_>) -> LoopStatus<T>,
) -> T {
    loop {
        let mut knob = Knob::get_from_somewhere();
        match callback(Event { knob: &mut knob }) {
            LoopStatus::Continue => {},
            LoopStatus::Break(x) => return x,
        }
        knob.use_somehow();
    }
}

pub fn event_handler() -> impl FnMut(Event<'_>) -> LoopStatus<()> {
    // Do something, I dunno, implement a finite state machine or something.
    // Here's a very not interesting example.
    let mut times_to_frobnicate = 3i32;
    move |Event { knob }| {
        if times_to_frobnicate > 0 {
            times_to_frobnicate -= 1;
            knob.frobnicate();
            LoopStatus::Continue
        } else {
            LoopStatus::Break(())
        }
    }
}

Of course, the manually writing a state machine is a pain. So, coroutines to the rescue!... right?

use genawaiter::{GeneratorState, yield_};
use genawaiter::stack::{Co, Gen, let_gen_using};
use std::future::Future;

pub fn main() {
    let_gen_using!(gen, |co| event_handler(co));
    event_loop(gen);
}

pub struct Knob;
impl Knob {
    pub fn get_from_somewhere() -> Self { Knob }
    pub fn frobnicate(&mut self) { println!("tweaking knob"); }
    pub fn use_somehow(&self) { println!("using knob somehow"); }
}

pub struct Event<'a> { knob: &'a mut Knob }

pub fn event_loop<T>(
    gen: &mut Gen<'_, (), Option<Event<'_>>, impl Future<Output=T>>,
) -> T {
    gen.resume_with(None);
    loop {
        let mut knob = Knob::get_from_somewhere();
        match gen.resume_with(Some(Event { knob: &mut knob })) {
            GeneratorState::Yielded(()) => {},
            GeneratorState::Complete(x) => return x,
        }
        knob.use_somehow();
    }
}

pub async fn event_handler(co: Co<'_, (), Option<Event<'_>>>) {
    let times_to_frobnicate = 3i32;
    for _ in 0..times_to_frobnicate {
        let Event { knob } = co.yield_(()).await.unwrap();
        knob.frobnicate();
    }
    co.yield_(()).await.unwrap(); // make sure the last knob is used
}

Unfortunately, this fails to build:

error[E0597]: `knob` does not live long enough
  --> src\bin\event-loop.rs:25:50
   |
20 |     gen: &mut Gen<'_, (), Option<Event<'_>>, impl Future<Output=T>>,
   |                                        -- let's call this `'1`
...
25 |         match gen.resume_with(Some(Event { knob: &mut knob })) {
   |               -----------------------------------^^^^^^^^^----
   |               |                                  |
   |               |                                  borrowed value does not live long enough
   |               argument requires that `knob` is borrowed for `'1`
...
30 |     }
   |     - `knob` dropped here while still borrowed

Knowing how Fn traits desugar, the reason why the latter fails while the former succeeds is fairly evident:

  • The original event handler was impl for<'a> FnMut(Event<'a>). Thus, every call of the function could use a distinct lifetime.
  • The new event handler is Gen<'_, (), Option<Event<'a>>> for a single lifetime 'a. This single lifetime is forced to encompass all calls to gen.resume_with.

Typically, the solution to this is to try to either introduce some trait with a lifetime parameter (so that we can have for<'a> Trait<'a>), or to introduce a trait with a method that has a lifetime parameter (trait Trait { fn method<'a>(...) { ... }}). But that's only a very vague plan... I'm not entirely sure where either of these techniques could be applied to the design of genawaiter!

Add procedural macro for simpler yield syntax

Thank you for writing this!

The current yield syntax is a bit clunky and in my opinion a procedural macro could improve this.

It could look something like this:

#[sync::generator]
fn my_gen() -> impl Generator<Yield = u32, Return = bool> {
    yield 1;
    yield 2;
    yield 3;
    true
}

This would be translated into something like:

fn my_gen() -> genawaiter::sync::Gen<u32, bool, Future<...>> {
    Gen::new(|co| async move {
        co.yield_(1).await;
        co.yield_(2).await;
        co.yield_(3).await;
        true
    })
}

Suggestion: `yield_` should take a mutable reference

Currently, yield_ takes &self: https://github.com/whatisaphone/genawaiter/blob/master/src/core.rs#L131

I would like to start a discussion about changing it to &mut self. We ran into a bug in our codebase where we shared a reference of the Co object across several futures. Even though we always directly called .await on the future returned by yield_, we still experienced the panic that tells you to do otherwise.

Turns out that sharing that reference was the problem! I assume what happened was that the future of yield_ did not completely finish before we called yield_a second time from a different future (we joined them together so the executed concurrently).

I think, changing the function to take &mut self would solve this because the returned future is already tagged with the lifetime, meaning the Rust compiler should complain about calling yield a 2nd time before the first future is dropped.

If I am not mistaken, this might actually completely remove the possibility of misusing the API as in calling yield a 2nd time before awaiting it.

What do you think?

Create a generator with a producer function without macros with types different to Unit

TLDR: I want to create the same generators as the macros using the low level api, specifically with a named function, but I can't

Note: All of this is using the sync module

I've been poking around this library for a bit and found out that creating a genawaiter::sync::Gen<u32, u32, impl Future> is easy with macros as it's only a little modification from the example of the docs

fn main() {
    let mut example_generator = gen!({
        let num = yield_!(42_u32);
        let num = yield_!(42);
    });
    
    example_generator.resume_with(0_u32);
    example_generator.resume_with(1);
    example_generator.resume_with(2);
}

Checking the type is easy. By adding a resume() this will not compile and the error will tell the type of the generator

example_generator.resume_with(0_u32);
example_generator.resume_with(1);
example_generator.resume_with(2);
example_generator.resume();
error[E0599]: no method named `resume` found for struct `genawaiter::sync::Gen<u32, u32, impl Future>` in the current scope
  --> src\main.rs:13:23
   |
13 |     example_generator.resume();
   |                       ^^^^^^ method not found in `genawaiter::sync::Gen<u32, u32, impl Future>`

Then I played with the low level api as I like to see how would it be to create the same generator without macros. I follow the docs for the sync low level api and so I define my async producer function and use it like so.

async fn producer(n: u32, co: Co<u32>) {
    let num = co.yield_(n).await;
    let num = co.yield_(n).await;
}

fn main() {
    let mut example_generator = Gen::new(|co| producer(42, co));
    example_generator.resume_with(0_u32);
    example_generator.resume_with(1);
    example_generator.resume_with(2);
}

But I got errors

error[E0308]: mismatched types
  --> src\main.rs:13:35
   |
13 |     example_generator.resume_with(0_u32);
   |                                   ^^^^^ expected `()`, found `u32`

error[E0308]: mismatched types
  --> src\main.rs:14:35
   |
14 |     example_generator.resume_with(1);
   |                                   ^ expected `()`, found integer

error[E0308]: mismatched types
  --> src\main.rs:15:35
   |
15 |     example_generator.resume_with(2);
   |                                   ^ expected `()`, found integer

error: aborting due to 3 previous errors

Looking around I found this definition

type Co<Y, R = ()> = Co<Airlock<Y, R>>;

To do the same as the macros my Co needs to be Co<u32, u32> but instead is Co<u32, ()> but I can't change it and I didn't found another public and more flexible Co. If I want to use return_with with a type different to () I simply can't use named functions?

PD: This also works and don't use macros but I can't use a named function

fn main() {
    let mut example_generator = Gen::new(|co| async move {
        let num = co.yield_(42_u32).await;
        let num = co.yield_(42).await;
    });
    example_generator.resume_with(0_u32);
    example_generator.resume_with(1);
    example_generator.resume_with(2);
}

Including license info

Hello @whatisaphone,

I see that the Cargo.toml specifies the license as MIT. Would you be willing to add a LICENSE file to the repo that matches the license in the Cargo.toml?

Thanks!

Nice crate btw, stable aync generators!

Manually type the outputs of a generator using `gen!`

Hi there,

I am wanting to have a generator that will yield values that are an Rc<dyn T>. Currently I have to do it like so:

async fn produce_pages(mut co: Co<'_, Rc<dyn GeneratedPage>>) {
    co.yield_(Rc::new(HomePage {})).await;
    co.yield_(Rc::new(ContactPage {})).await;
}

fn foo() -> () {
    let_gen_using!(gen_all_pages, produce);
    gen_all_pages.into_iter().collect();
}

This means that foo cannot return the iterator sadly because of the use of let_gen_using, which leaks the shelf value which is owned by the foo function.

The whole reason I need to use the producer pattern is because gen!() doesn't accept a type argument (which is needed because of the dyn); would it be possible to have it allow a field that specifies what type the Rust compiler should know the generator results to be?

Gobbled value in generator

Thank you for making genawaiter!

Configuration

  • rustc 1.59.0 (9d1b2106e 2022-02-23)
  • genawaiter v0.99.1

Code

use genawaiter::{
    rc::gen,
    yield_,
};

fn main() {
    let mut printer = gen!({
        loop {
            let string = yield_!(());
            println!("{}", string);
        }
    });
    printer.resume_with("0");
    printer.resume_with("1");
    printer.resume_with("2");
    printer.resume_with("3");
}

Expected output:

0
1
2
3

or at least,

0
1
2

Observed output

1
2
3

It looks like the first value passed to the generator is never printed? I'm not sure why that is.

Is there something I'm misunderstanding?

Guidance for recursive generators

I tried and failed to write a recursive generator today. I understand that this relates to the inherent problem of recursive async functions in Rust, but it's not clear how to write a recursive generator; documentation on that would be appreciated especially if it ends up being useful in the future when generators make it to the stdlib.

How to abstract generators in associated types?

Hi! I'm trying to write a library where I associate custom structs to generators via a trait with an associated type, however I don't know how to restrict the type to accept only generators. I tried restricting the type to those who implement Coroutine but I still don't have access to resume() or resume_with(). I am choosing the wrong form to associated them or is a way to do this? I need this because I'm saving these structs as Box<dyn Any> in a structure where they get pulled after, then downcasted using a different trait with a blanked implementation for this trait and depending on which one I get I run its generator once if it yields then is push back to the structure if it completes then is removed from the structure. Even if I end up choosing another way to accomplish this I'm quite interested to know if is possible to restrict associated types to generators in such a way that I have access to resume() or at least resume_with().
Any help would be very much appreciated.

Road to 1.0

You might be wondering why the version number jumped straight from 0.2 to 0.99. I'm treating 0.99 as a prerelease for 1.0. Now that DevinR528's proc macros have landed, I consider this library feature-complete. Since proc macros were a huge new feature, I want to let it simmer for a bit and see some use in the wild before committing to 1.0. Barring any catastrophes, I plan on releasing 1.0 with no further changes except for removing the macros which are currently deprecated.

Can't compile syn dependency due to missing feature

[package]
name = "gen-example"
version = "0.1.0"
authors = ["Michael Lamparski <[email protected]>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
genawaiter = "0.99.0"
C:/Users/diago/.cargo/bin/cargo.exe run --color=always
   Compiling genawaiter-proc-macro v0.99.0
error[E0432]: unresolved imports `syn::FnArg`, `syn::ItemFn`
  --> C:\Users\diago\.cargo\registry\src\github.com-1ecc6299db9ec823\genawaiter-proc-macro-0.99.0\src\lib.rs:20:5
   |
20 |     FnArg,
   |     ^^^^^ no `FnArg` in the root
21 |     Ident,
22 |     ItemFn,
   |     ^^^^^^ no `ItemFn` in the root

It seems to me that genawaiter-proc-macro requires the "full" feature of syn, but does not enable it. (How is this not failing on CI testing?)

IntoIterator not working

Probably just missing some Rust chops here, but the following doesn't work for me:

use genawaiter::rc::Gen;

fn count() -> Gen<i32, (), impl std::future::Future> {
    use genawaiter::rc::Co;
    async fn gen(co: Co<i32>) {
        let mut n = 1;
        while n < 10 {
            co.yield_(n).await;
            n += 2;
        }
    }
    Gen::new(gen)
}

fn main() {
    for i in count() {
        println!("{}", i);
    }
}

I get the following error:

error[E0277]: the trait bound `genawaiter::rc::generator::Gen<i32, (), impl std::future::Future>: std::iter::IntoIterator` is not satisfied
  --> src/main.rs:21:14
   |
21 |     for i in count() {
   |              ^^^^^^^ the trait `std::iter::IntoIterator` is not implemented for `genawaiter::rc::generator::Gen<i32, (), impl std::future::Future>`
   |
   = help: the following implementations were found:
             <genawaiter::rc::generator::Gen<Y, (), F> as std::iter::IntoIterator>
   = note: required by `std::iter::IntoIterator::into_iter`

But my understanding from the docs is that this should work.

My cargo dependency looks like this:

genawaiter = { version = "0.99.1", features = ["proc_macro"] }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.