Coder Social home page Coder Social logo

jkrumbiegel / chain.jl Goto Github PK

View Code? Open in Web Editor NEW
348.0 6.0 13.0 69 KB

A Julia package for piping a value through a series of transformation expressions using a more convenient syntax than Julia's native piping functionality.

License: MIT License

Julia 100.00%
pipeline macro julia julia-language julia-package data-science data-analysis

chain.jl's Introduction

Chain.jl

A Julia package for piping a value through a series of transformation expressions using a more convenient syntax than Julia's native piping functionality.

Chain.jlBase Julia
@chain df begin
  dropmissing
  filter(:id => >(6), _)
  groupby(:group)
  combine(:age => sum)
end
df |>
  dropmissing |>
  x -> filter(:id => >(6), x) |>
  x -> groupby(x, :group) |>
  x -> combine(x, :age => sum)
Pipe.jl Lazy.jl
@pipe df |>
  dropmissing |>
  filter(:id => >(6), _)|>
  groupby(_, :group) |>
  combine(_, :age => sum)
@> df begin
  dropmissing
  x -> filter(:id => >(6), x)
  groupby(:group)
  combine(:age => sum)
end

Build Status

Run tests

Summary

Chain.jl exports the @chain macro.

This macro rewrites a series of expressions into a chain, where the result of one expression is inserted into the next expression following certain rules.

Rule 1

Any expr that is a begin ... end block is flattened. For example, these two pseudocodes are equivalent:

@chain a b c d e f

@chain a begin
    b
    c
    d
end e f

Rule 2

Any expression but the first (in the flattened representation) will have the preceding result inserted as its first argument, unless at least one underscore _ is present. In that case, all underscores will be replaced with the preceding result.

If the expression is a symbol, the symbol is treated equivalently to a function call.

For example, the following code block

@chain begin
    x
    f()
    @g()
    h
    @i
    j(123, _)
    k(_, 123, _)
end

is equivalent to

begin
    local temp1 = f(x)
    local temp2 = @g(temp1)
    local temp3 = h(temp2)
    local temp4 = @i(temp3)
    local temp5 = j(123, temp4)
    local temp6 = k(temp5, 123, temp5)
end

Rule 3

An expression that begins with @aside does not pass its result on to the following expression. Instead, the result of the previous expression will be passed on. This is meant for inspecting the state of the chain. The expression within @aside will not get the previous result auto-inserted, you can use underscores to reference it.

@chain begin
    [1, 2, 3]
    filter(isodd, _)
    @aside @info "There are \$(length(_)) elements after filtering"
    sum
end

Rule 4

It is allowed to start an expression with a variable assignment. In this case, the usual insertion rules apply to the right-hand side of that assignment. This can be used to store intermediate results.

@chain begin
    [1, 2, 3]
    filtered = filter(isodd, _)
    sum
end

filtered == [1, 3]

Rule 5

The @. macro may be used with a symbol to broadcast that function over the preceding result.

@chain begin
    [1, 2, 3]
    @. sqrt
end

is equivalent to

@chain begin
    [1, 2, 3]
    sqrt.(_)
end

Motivation

  • The implicit first argument insertion is useful for many data pipeline scenarios, like groupby, transform and combine in DataFrames.jl
  • The _ syntax is there to either increase legibility or to use functions like filter or map which need the previous result as the second argument
  • There is no need to type |> over and over
  • Any line can be commented out or in without breaking syntax, there is no problem with dangling |> symbols
  • The state of the pipeline can easily be checked with the @aside macro
  • Flattening of begin ... end blocks allows you to split your chain over multiple lines
  • Because everything is just lines with separate expressions and not one huge function call, IDEs can show exactly in which line errors happened
  • Pipe is a name defined by Base Julia which can lead to conflicts

Example

An example with a DataFrame:

using DataFrames, Chain

df = DataFrame(group = [1, 2, 1, 2, missing], weight = [1, 3, 5, 7, missing])

result = @chain df begin
    dropmissing
    filter(r -> r.weight < 6, _)
    groupby(:group)
    combine(:weight => sum => :total_weight)
end

The chain block is equivalent to this:

result = begin
    local var"##1" = dropmissing(df)
    local var"##2" = filter(r -> r.weight < 6, var"##1")
    local var"##3" = groupby(var"##2", :group)
    local var"##4" = combine(var"##3", :weight => sum => :total_weight)
end

Nested Chains

The @chain macro replaces all underscores in the following block, unless it encounters another @chain macrocall. In that case, the only underscore that is still replaced by the outer macro is the first argument of the inner @chain. You can use this, for example, in combination with the @aside macro if you need to process a side result further.

@chain df begin
    dropmissing
    filter(r -> r.weight < 6, _)
    @aside @chain _ begin
            select(:group)
            CSV.write("filtered_groups.csv", _)
        end
    groupby(:group)
    combine(:weight => sum => :total_weight)
end

chain.jl's People

Contributors

aviatesk avatar jcunwin avatar jkrumbiegel avatar waldyrious 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

chain.jl's Issues

allowing assignments to variables

It would be nice to allow assignments (intermediate or at the end) of a chain, such that code like this :

julia> using DataFramesMeta;

julia> df = DataFrame(a = [1, 2]);

julia> @chain df begin 
           @transform(y = :a)
           newdata = _
       end;

julia> newdata
ERROR: UndefVarError: newdata not defined

would work. This could also be very helpful for "debugging".

(@pdeffebach, see also here)

Single call inline chain fails

julia> @chain abs(1) 
ERROR: LoadError: Can't insert a first argument into:
1.

I'm interactively building a chain one step at a time, so I want to initially evaluate the first step. The block version works

@chain abs(1) begin
    
end # 1

just not the inline version.

Make default `begin ... end` rather than `let`

I think it's not uncommon to want things created in @aside blocks to be visible in the outer scope of a @chain block.

If we make the default "block" for @chain to be just a begin ... end block without introducing a new scope, the user can make the same behavior with

@chain df let
    ...
end

This is something I considered in AddToField.jl and settled on begin ... end.

Passing an empty args list

This currently fails:

julia> @chain begin
       1
       sin()
       end
ERROR: LoadError: Can't prepend first arg to expression sin() that isn't a call.

It would be useful to have it as now you have to e.g. write:

julia> @chain begin
       [1,2]
       sin.(_)
       end
2-element Array{Float64,1}:
 0.8414709848078965
 0.9092974268256817

instead of just:

julia> @chain begin
       [1,2]
       sin.()
       end
ERROR: LoadError: Can't prepend first arg to expression sin.() that isn't a call.

@chain one-liner nested in an outer @chain may not work?

This might be "overusing" @chain, but I found there was an inconsistency between one-liner and nested blocks with this:

This works:

@chain read(open("Day-8.txt"), String) begin
    strip
    split("\n")
    map(_) do _
        map(split(_, "|")) do _
            @chain _ begin
                strip
                split
            end
        end
    end
end

But making the inner @chain into a one-line version fails with "Malformed nested @chain macro"

@chain read(open("Day-8.txt"), String) begin
    strip
    split("\n")
    map(_) do _
        map(split(_, "|")) do _
            @chain _ strip split
        end
    end
end

FYI I tried using do x rather than do _, but to no avail.

I'm not sure the nesting is a good design — I was attempting to parse a heavily nested string, and was using new indentation to signify each level of nesting — so I'm putting this in for info, rather than something that's important per-se.

Thanks for the excellent package, I'm really enjoying using it

Add a `@stop` or `@exit` flag

One thing that might be nice is to add a flag for exiting the @chain block.

One workflow I use at the REPL is to have a big block that I add to a bunch. But if I wrote a bug that is an an intermediate location, I want to be able to go to that spot.

here is a big block

julia> df = @chain begin 
       map(1:10) do i
           @addnt begin 
               @add apartment_id = rand(1:4)
               @add move_date = rand(dates)
               @add action_type = rand(["move_in", "move_out"])
           end
       end
       DataFrame
       @aside begin 
           move_ins = DataFrame(apartment_id = [1, 2, 3, 4], move_date = Date(2010, 01, 01), action_type = fill("move_in", 4))
       end
       vcat(move_ins, _)
       @orderby :apartment_id :move_date
       @transform diff_from_action = ifelse.(:action_type .== "move_in", 1, -1)
       groupby(:apartment_id) 
       @transform num_people_in_apartment_after_action = cumsum(:diff_from_action)
       groupby(:apartment_id)
       @transform cols(AsTable) = begin 
           @addnt begin 
               @add start_dates = :move_date
               @add end_dates = lead(:move_date)
           end
       end
       end

What if I think I created diff_from_action wrong? To get to the the chain at exactly that place, I would have to delete or comment out a lot of stuff. Or add an end and face a bunch of errors.

But if I could just add an @exit flag right after the diff_from_action command, I could just forget about all the stuff after.

This would solve one thing that made me hesitant about @chain earlier. One benefit of the %>% in dplyr was that it was easy to break out of the chain and return what you wanted.

.|> ?

What's the equivalent of rand(100) .|> sin |> sum ?

Chain as pipe argument

Parentheses are necessary to put a chain in a pipe. For example the following

@chain DataFrame(a=1:3) begin select(:a) end |> display

fails with

ERROR: MethodError: no method matching select(::Symbol)
Closest candidates are:
  select(::AbstractDataFrame, ::Any...; copycols, renamecols) at ~/.julia/packages/DataFrames/vuMM8/src/abstractdataframe/selection.jl:940
  select(::Union{Function, Type}, ::AbstractDataFrame; renamecols) at ~/.julia/packages/DataFrames/vuMM8/src/abstractdataframe/selection.jl:943
  select(::Union{Function, Type}, ::GroupedDataFrame; copycols, keepkeys, ungroup, renamecols) at ~/.julia/packages/DataFrames/vuMM8/src/groupeddataframe/splitapplycombine.jl:711

Would it be possible to make this work without parentheses? Intuitively it seems that it should be possible since the chained code is already delimited by the begin ... end block.

Odd behavior with anonymous functions

Here is an edge case I think is worth looking into

julia> using Chain

julia> x = 1
1

julia> @chain x begin 
       (t -> t + 1)
       end
ERROR: LoadError: Can't prepend first arg to expression t->begin
        #= REPL[76]:2 =#
        t + 1
    end that isn't a call.
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] insert_first_arg(::Expr, ::Symbol) at /home/peterwd/.julia/packages/Chain/HuSPl/src/Chain.jl:38
 [3] rewrite(::Expr, ::Symbol) at /home/peterwd/.julia/packages/Chain/HuSPl/src/Chain.jl:53
 [4] rewrite_chain_block(::Symbol, ::Expr) at /home/peterwd/.julia/packages/Chain/HuSPl/src/Chain.jl:80
 [5] @chain(::LineNumberNode, ::Module, ::Any, ::Expr) at /home/peterwd/.julia/packages/Chain/HuSPl/src/Chain.jl:113
in expression starting at REPL[76]:1

julia> @chain x begin 
       (t -> t + 1)(_)
       end
2

"Can't insert a first argument into"

julia> 5 |> @chain identity
5

julia> 5 |> @chain (x->x)
ERROR: LoadError: Can't insert a first argument into:
begin
    #= REPL[5]:1 =#
    x
end.

First argument insertion works with expressions like these, where [Module.SubModule.] is optional:

[Module.SubModule.]func
[Module.SubModule.]func(args...)
[Module.SubModule.]func(args...; kwargs...)
[Module.SubModule.]@macro
[Module.SubModule.]@macro(args...)
@. [Module.SubModule.]func

Stacktrace:
  [1] error(s::String)
    @ Base ./error.jl:33
  [2] insertionerror(expr::Expr)
    @ Chain ~/.julia/packages/Chain/wUcPF/src/Chain.jl:13
  [3] insert_first_arg(e::Expr, firstarg::Symbol)
    @ Chain ~/.julia/packages/Chain/wUcPF/src/Chain.jl:89
  [4] rewrite(expr::Expr, replacement::Symbol)
    @ Chain ~/.julia/packages/Chain/wUcPF/src/Chain.jl:104
  [5] rewrite_chain_block(block::Expr)
    @ Chain ~/.julia/packages/Chain/wUcPF/src/Chain.jl:211
  [6] var"@chain"(__source__::LineNumberNode, __module__::Module, block::Expr)
    @ Chain ~/.julia/packages/Chain/wUcPF/src/Chain.jl:245

The docs say

the result of the previous expression is used as the first argument in the current expression, as long as it is a function call, a macro call or a symbol representing a function

(x->x) is not a function call, macro call, or symbol representing a function, so an error is ok. But maybe it could support this anyway, expanding the API from "symbol representing a function" to "expression representing a function".

If you don't like it I can always go (x->x)(_), so it's not a very big problem, but it might be good.

Do we really need `begin` and `end` in the expression?

In Lazy's @> you can do

@> rand(2, 3) DataFrame(:auto)

But in Chain you need

julia> @chain rand(2, 3) DataFrame(:auto)
ERROR: LoadError: Second argument of @chain must be a begin / end block
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] rewrite_chain_block(::Expr, ::Expr) at /home/peterwd/.julia/packages/Chain/HuSPl/src/Chain.jl:66
 [3] @chain(::LineNumberNode, ::Module, ::Any, ::Expr) at /home/peterwd/.julia/packages/Chain/HuSPl/src/Chain.jl:113
in expression starting at REPL[22]:1

Is there a technical reason for this? Maybe it's not necessary?

Potential incompatibility with Julia LSP?

This is another one "I'm not sure where the bug lies" — I hope these are helpful regardless — please feel free to close if it's not an issue with this library.

It seems that Julia LSP misunderstands @chain with a user function. Here's an example:

using Chain, Underscores, StatsBase, Base.Iterators, DataStructures

input = @chain begin
    read(open("Day-6.txt"), String)
    strip
    split(",")
    @. parse(Int, _)
end

start = @chain input countmap DefaultDict(0, _)

function run(start, n)
    current = copy(start)
    for i = 1:n
        next = DefaultDict(0, Dict(i - 1 => current[i] for i in keys(current) if i > 0))
        next[6] += next[8] = current[0]
        current = next
    end
    current
end

@chain start run(256) values sum  # run returns "Possible method call error"

Here's a screenshot:

image

Incorrect handling of qualified function calls

This works:

julia> @chain df begin
       DataFrames.transform(_)
       end
1×2 DataFrame
 Row │ a      b     
     │ Int64  Int64 
─────┼──────────────
   1 │     1      2

julia> @chain df begin
       transform
       end
1×2 DataFrame
 Row │ a      b     
     │ Int64  Int64 
─────┼──────────────
   1 │     1      2

but this fails:

julia> @chain df begin
       DataFrames.transform
       end
ERROR: Can't prepend first arg to expression DataFrames.transform that isn't a call.

and clearly DataFrames.transform is a qualified call.

Is this documented? (I could not find a reference)

Line numbers aren't propagating in errors

When using @chain, line numbers are given at the level of the @chain command, not sub-commands. This makes it hard to debug. Consider the following script

using Chain

foo(x, y) = x * y

x = [1, 2]

@chain x begin
    identity
    identity
    identity
    foo([3, 4])
end

The stacktrace says the error occurs starting on line 7. This makes it hard to debug long @chains. We should try to ensure line number propogation within the chain block.

Empty chain returns nothing

julia> @chain 1 begin; end

julia> 

I was expecting it to return the argument, in this case, 1.


[8be319e6] Chain v0.4.3

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Add one more argument to motivation

When you use Pipe.jl there is a small problem that Pipe is a name defined and exported in Julia Base. Maybe also this could be mentioned in the list of motivations?

Macro to pass last argument

For functions like map, filter, accumulate that take the data as the last argument, it would be convenient to have another chain macro that passes the _ as last argument instead of first argument. Perhaps rchain/chainr for "right", fchain/chainf for "final" or something.

Auto-gen release notes

Hi @jkrumbiegel , I just wanted to ask how you generate the release notes with the closed issues and MRs. Is there automation? I suspected an aciton but didn't find something related.

pass variable as initial symbol instead of underscore

Hello,

I apologize if this has been addressed before. I like the syntax of Chain.jl but I was thinking it would be nice to be able to use the original variable name rather than only underscores. The front-page example

@chain df begin
  dropmissing
  filter(:id => >(6), _)
  groupby(:group)
  combine(:age => sum)
end

Could be something like

@chain df begin
  dropmissing
  filter(:id => >(6), df)
  groupby(:group)
  combine(:age => sum)
end

Let me know what you think.

Nested chains ?

I am currently doing day 4 of adventofcode.com

Much of the job is just text parsing and I managed to do it with a nice "one-liner" thanks to this very neat package!

is_valid(pass) = (length(pass) == 8) | ((length(pass) == 7) & ("cid"  pass))

sol = @chain "./data/passport.txt" begin
    read(_, String)
    split(_, "\n\n")
    map(x -> replace(x, "\n" => " "), _)
    map(x -> split(x, " "), _)
    map(x -> map(y -> split(y, ":")[1], x), _)
    filter(is_valid, _)
    length
end

However, I found the maps and the map of a map difficult to read and I was unable to use nested chains (and the documentation).

Is it possible to do it with nested chains?

Some example string:

ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
byr:1937 iyr:2017 cid:147 hgt:183cm

iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
hcl:#cfa07d byr:1929

hcl:#ae17e1 iyr:2013
eyr:2024
ecl:brn pid:760753108 byr:1931
hgt:179cm

hcl:#cfa07d eyr:2025 pid:166559648
iyr:2011 ecl:brn hgt:59in

It's not really a bug so I would understand if you just close the issue.

Use first item of chain block as input

Just a thought that it might look a little neater if the first item of the chain block could be taken as the input to the chain, in the case where no input is supplied.

This is useful where you're assigning the result of the block to a variable, and have a long function call or variable name as the input.

Compare:

some_output_variable_name = @chain some_long_variable_name begin
    do_something()
	do_something_else(parameter)
    do_other_thing(parameter, _)
end
some_output_variable_name = @chain begin
    some_long_variable_name
    do_something()
	do_something_else(parameter)
    do_other_thing(parameter, _)
end

Easier way to make anonymous functions

I'm imagining somehtin like @aside but which takes in _ and returns whatever the expression returns.

@chain 1 begin 
    @anon begin 
        _ + 2 * 1000
     end
     mean()
end

I've been thinking of doing an @anon macro in DataFramesMeta, since the infrastructure is all there to do it with Symbols. However this would need to be different, special-casing _.

I introduced some ugly behavior with multi-arg version

consider

julia> @chain 1 begin 
           first
           last
       end == 1
block = :(begin
          #= REPL[22]:2 =#
          first
          #= REPL[22]:3 =#
          last
      end == 1)
ERROR: MethodError: no method matching ==(::Int64, ::typeof(last), ::Int64)
Closest candidates are:

This error didn't exist until #24. Adding a multi-argument version of the macro means that it picks up the ending == at the end.

I don't know if there is a way around this, but I ran into it when writing tests for DataFramesMeta's block PR here.

Approach for nesting a map

Related to #42 (comment) & #44

Is there a recommended approach for nesting a map? This would allow arbitrary nesting in a single point-free expression — something that's really difficult in languages I've explored.

For example, if we wanted to use @chain for the whole expression, it would currently look something like:

@chain "a|x\nb| y z " begin
    strip
    split("\n")
    map(_) do x
        @chain x begin
            split("|")
            map(_) do y
                @chain y strip split
            end
        end
    end
end

But the x and y aren't needed here! Assuming there isn't an existing recommended approach — could we extent Chain to handle those too, with feature for reducing map(_) do x; @chain x begin down to something like map @chain do, so we could have:

@chain "a|x\nb| y z " begin
    strip
    split("\n")
    map @chain do
        split("|")
        map @chain do 
            strip
            split
        end
    end
end

I'm not sure of the exact form that's both concise and idiomatic — I don't have a view on the exact syntax. And feel free to say this is a bad idea... Thanks again.

Using anon functions in a nested @. function

Another one from me — I'm adding these because I think this library is superb and I'm exploring how far it can go — I realize the past few issues have been corner cases.

Building on #42, this works:

s = "dbaf fag bdfgea ecbgfa dgbfe fa edacg agcfdbe cefdbg fdgea | fgbaed cdegbf dacfbge gdcfbe
abc acefbd gacedbf gbcde dgabf ca fgcedb cega dgbca cgeadb | ca bcged abgfd bca"

function inner(x)
    (x -> split(x, "|"))(x)
end

input = @chain s begin
    strip    

    split("\n")
    @. inner
end

But if we replace inner with the function, it fails:

input = @chain s begin
    strip    

    split("\n")
    @. (x -> split(x, "|")) # (or with (_) at the end)
end

# You can only use the @. macro and automatic first argument insertion if what follows is of the form `[Module.SubModule.]func`

My understanding from the docs is that that should be syntactic sugar for this, which also works:

input = @chain s begin
    strip    

    split("\n")
    (x -> split(x, "|")).(_)
end

Potential conflict with Pluto?

I'm new to Julia and so I might be making a naive mistake.

In Pluto, running something like this prints the docs! This may be because in pluto, the docs are offered in a pop-up on the bottom right.

@chain begin "3,4,3,1,2"
	strip
end

image

and running

@chain begin "3,4,3,1,2"
	strip
	split(",") 
end

returns an error

MethodError: no method matching split(::Base.Docs.Binding, ::String)

Closest candidates are:

split(!Matched::T, ::Any; limit, keepempty) where T<:AbstractString at /Applications/Julia-1.7.app/Contents/Resources/julia/share/julia/base/strings/util.jl:417

    top-level scope@Local: 3[inlined]

It's not purely a Pluto issue though — @_ from Underscores.jl doesn't have this problem, maybe because it doesn't use multiple lines?

Thank you!

Equivalent of `.|>` for array of functions

With the .|> operator, I can apply arrays of functions to arrays of values
I’m new to julia but I guess that it is possible because it uses broadcast ?

functions = [(a -> a +1), (a -> a + 2)]
values = [0, 0]

r1 = values .|> functions
functions = [(a -> a +1) (a -> a + 2); (a -> a +3) (a -> a + 4)]
values = [0 0; 0 0]

r1 = values .|> functions

Is it possible to do the same with chain.jl ? I tried the @. syntax but it doesn’t work.

Using with DataFramesMeta.@transform macro

I found the following example does not work:

using DataFrames, DataFramesMeta
using Chain

df = DataFrame(A = 1:3, B = [2, 1,2])
@chain df begin @transform(a = 2 * :A, b = :A .+ :B) end # does not work
@transform(df, a = 2 * :A, b = :A .+ :B) # does work
@chain df begin @transform(_, a = 2 * :A, b = :A .+ :B) end # works, but I don't understand why

Are there any demos of how to use Chain.jl with DataFramesMeta.jl?

@aside cuts the chain if the last statement

If @aside is the last last statement in a chain then it is cut. Consider:

using DataFrames, DataFramesMeta (v0.8.0)

df = @chain DataFrame(a = 1:4) begin
    @aside println(nrow(_))
end

This just prints out 4 and assigns nothing to df. This is unexpected and unfortunate because one sometimes wants to know how many rows are left (or something similar) after some data crunching steps.

A possible (but unattractive) workaround is to place an identity in the last line:

df = @chain DataFrame(a = 1:4) begin
    @aside println(nrow(_))
    identity
end

This now assigns df to be the expected 4 row DataFrame.

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.