Giter Site home page Giter Site logo

clojang / clojang Goto Github PK

View Code? Open in Web Editor NEW
66.0 5.0 2.0 1.02 MB

Clojure API for Erlang/OTP Communications (built on jiface)

License: Other

Clojure 87.56% Makefile 5.37% Erlang 4.60% LFE 2.47%
erlang lfe otp distributed-systems java jvm jinterface jiface clojure beam

clojang's Introduction

clojang

Build Status Dependencies Status Clojars Project

Erlang/OTP Communications in Clojure (wraps jiface + JInterface)

Clojang logo

Contents

Introduction

This project provides a final solution to the Clojure JInterface Problem. The jiface low-level API solves this to a certain extent, but jiface requires that programmers perform all their own type conversions, manual creation of nodes and mboxes, etc.

The Clojang library, however, provides an interface that is not only syntactically idiomatic Clojure (not unlike the jiface library), but even more so, provides developers with the same level of convenience they have come to expect when using Clojure libraries in general, without the need to perform many manual operations in order to handle Erlang data.

For a comparison of JInterface, the low-level jiface API, and the high-level clojang API, see the APIs summary page.

Dependencies

  • Java
  • Erlang
  • lein
  • rebar3

The default (and tested) version combinations are as follows:

Clojang jiface JInterface Erlang Release Erlang Version (erts)
0.5.0 0.5.0 1.8.1 20.3 9.3
0.4.0 0.4.0 1.7.1 19.2, 19.3 8.2, 8.3
0.3.0 0.3.0 1.7.1 19.2 8.2
0.2.0 0.2.0 1.7.1 19.1, 19.2 8.1, 8.2
0.1.0 0.1.0 1.6.1 18.2, 18.3 7.2, 7.3

While other version combination may work (and existing versions may be updated to work with different onces), those are the only ones which are supported.

Building

rebar3 is used for the top-level builds of the project. It runs lein under the covers in order to build the Clojure code and create the Clojang.jar file. As such, to build everything -- LFE, Erlang, and Clojure -- you need only do the following:

  • rebar3 compile

If you wish to build your own JInterface .jar file and not use the one we've uploaded to Clojars, you'll need to follow the instrucations given in the jinterface-builder Clojang project.

Shells & REPLs

There are three interactive programming environments you may start, each of which will have full access to the project's libraries/dependencies.

LFE:

$ make lfe-repl

Erlang:

$ rebar3 shell

Clojure:

$ lein clj-repl

Documentation

Project documentation, including Clojang API reference docs, Javadocs for JInterface, and the Erlang JInterface User's Guide, is availble here:

Quick links for the other docs:

  • Clojang User Guides:
    • jiface User's Guide - A translation of the JInterface User's Guide (Erlang documantaion) from Java into Clojure; this is refered to as the Clojang low-level API.
    • Clojang User's Guide - An adaptation of the jiface User's Guide which demonstrates the improved API that Clojang offers
  • JInterface User's Guide - The JInterface (Java support for Erlang Ports) documentation provided in Erlang distributions
  • Jinterface Javadocs - Javadoc-generated API documentation built from the JInterface source code

Usage

Using Clojang in a project is just like any other Clojure library. Just add the following to the :dependencies in your project.clj file:

Clojars Project

For the Erlang/LFE side of things, you just need to add the Github URL to your rebar.config file, as with any other rebar-based Erlang VM project.

As for actual code usage, the documentation section provides links to developer guides and API references, but below are also provided some quick examples.

Here, we'll send a message to our own (default) node's message box, and then receive it:

(require '[clojang.core :refer [! receive self]])

(! [(self) :hello-word])
(let [[pid msg] (receive)]
  (println msg))

To show remote usage, from an LFE (Lisp Flavoured Erlang) REPL:

(clojang-lfe@mndltl01)> (! #(default clojang@host) `#(,(self) hej!))
#(<0.35.0> hej!)

Then back in Clojure, the same that we used before, but we'll capture the process id that we sent from LFE and then send another message back to it:

(let [[pid msg] (receive)]
  (println msg)
  (! pid "hello, world!"))

Then, returning to the LFE REPL, we check that we received the message from Clojang:

(clojang-lfe@mndltl01)> (c:flush)
Shell got "hello, world!"
ok

Running Tests

All the tests may be run with just one command:

$ rebar3 eunit

This will not only run Erlang and LFE unit tests, it also runs the Clojure unit tests for Clojang.

Clojure Test Selectors

If you would like to be more selective in the types of Clojang tests which get run, you may be interested in reading this section.

The Clojang tests use metadata annotations to indicate whether they are unit, system, or integration tests. to run just the unit tests, you can do any one of the following, depending upon what you're used to:

$ lein test
$ lein test :unit
$ lein test :default

To run just the system tests:

$ lein test :system

And, similarly, just the integration tests:

$ lein test :integtration

To run everything:

$ lein test :all

This is what is used by the rebar3 configuration to run the Clojang tests.

Donating

A donation account for supporting development on this project has been set up on Liberapay here:

You can learn more about Liberapay on its Wikipedia entry or on the service's "About" page.

License

Copyright © 2018 The Clojang Project

Copyright © 2016-2017 Duncan McGreggor

Distributed under the Apache License Version 2.0.

clojang's People

Contributors

ducky427 avatar oubiwann avatar yurrriq 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

Watchers

 avatar  avatar  avatar  avatar  avatar

Forkers

ducky427 yurrriq

clojang's Issues

Annotate the low-level API

  • clojang.jinterface.erlang.atom
  • clojang.jinterface.erlang.boolean
  • clojang.jinterface.erlang.char
  • clojang.jinterface.erlang.float
  • clojang.jinterface.erlang.int
  • clojang.jinterface.erlang.list
  • clojang.jinterface.erlang.map
  • clojang.jinterface.erlang.object
  • clojang.jinterface.erlang.pid
  • clojang.jinterface.erlang.port
  • clojang.jinterface.erlang.string
  • clojang.jinterface.erlang.tuple
  • clojang.jinterface.erlang.types
  • clojang.jinterface.erlang
  • clojang.jinterface.otp.connection
  • clojang.jinterface.otp.messaging
  • clojang.jinterface.otp.nodes => annotations/otp.nodes
  • clojang.jinterface.otp

Add a splash screen!

Totally useless, but so cool! (who knew the JVM could do this?)

When you add this option to the JVM, the image stays up and never goes away. You have to use the AWT call to close the splash. This will require piggy-packing on a function that gets run when the JVM starts up.

Agent code not found

With the new separation (and potentially other recent changes) of Agent from Clojang and jiface, the agent does not start successfully when Clojang starts:

$ lein repl
Exception in thread "main" java.lang.ClassNotFoundException: clojang.agent.startup
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:425)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:358)
    at sun.instrument.InstrumentationImpl.loadClassAndStartAgent(InstrumentationImpl.java:300)
    at sun.instrument.InstrumentationImpl.loadClassAndCallPremain(InstrumentationImpl.java:397)
FATAL ERROR in native method: processing of -javaagent failed
Exception in thread "Thread-3" clojure.lang.ExceptionInfo: Subprocess failed {:exit-code 134}
    at clojure.core$ex_info.invoke(core.clj:4593)
    at leiningen.core.eval$fn__5830.invoke(eval.clj:255)
    at clojure.lang.MultiFn.invoke(MultiFn.java:233)
    at leiningen.core.eval$eval_in_project.invoke(eval.clj:356)
    at leiningen.repl$server$fn__11848.invoke(repl.clj:243)
    at clojure.lang.AFn.applyToHelper(AFn.java:152)
    at clojure.lang.AFn.applyTo(AFn.java:144)
    at clojure.core$apply.invoke(core.clj:630)
    at clojure.core$with_bindings_STAR_.doInvoke(core.clj:1868)
    at clojure.lang.RestFn.invoke(RestFn.java:425)
    at clojure.lang.AFn.applyToHelper(AFn.java:156)
    at clojure.lang.RestFn.applyTo(RestFn.java:132)
    at clojure.core$apply.invoke(core.clj:634)
    at clojure.core$bound_fn_STAR_$fn__4439.doInvoke(core.clj:1890)
    at clojure.lang.RestFn.invoke(RestFn.java:397)
    at clojure.lang.AFn.run(AFn.java:22)
    at java.lang.Thread.run(Thread.java:745)

Epic: Create native Clojure core.async Erlang Ports Interface

This one's a biggie.

Sadly, the JInterface library, as it stands, locks us into an old-school threading model and doesn't take advantage of Java's newest concurrency primitives. Clojure's core.async provides an elegant Lisp interface to these (and more), and may be ideally suited for creating light-weight (non-OS) processes that can communicate with Erlang via the Erlang ports specification.

This would be much easier if message parsing in JInterface is separated from communications (not sure if this is the case or not ... kind of doubt it), since that would mean we'd have to write less code.

This epic may depend upon the following yak (herds) to be shaved:

  • Create (or use) a Clojure library that implements basic actors on top of core.async
    • Qusar/Pulsar provide a core.async wrapper, but those libraries are way too much for what we need
    • is it possible to only use part of them?
    • are there other libraries available which provide basic actor model abstractions on top of core.async?
    • if we need to write this in Clojure, it should go in its own project
  • Create (or use) a Clojure/Java for simply parsing and transforming "external" Erlang messages into Clojure data structures
    • we may be able to re-purpose JInterface for this, as long as we don't have to instantiate communication objects in order to do so ...
    • if that's the case, we may want to re-implement the parsing functionality in Clojure
    • if we need to write this in Clojure, it should go in its own project

Once these questions have been answered, and the dependencies are met, we'll have a clear idea of the tasks necessary to actually code up this epic. At that point, this ticket description should be updated with the high-level tasks needed to develop this feature. Those should then be created as tickets (and linked to the bullet list tasks). Each of those tickets should be broken down into actual tasks that can map to feature branches.

Low-level Wrapper: nodes, peers, and self

Tasks

Infrastructure

  • Create defotp macro
  • Create defotp-protected macro

Protocols

  • Create protocol for OtpAbstractlNode
  • Create protocol for OtpLocalNode
  • Create protocol for OtpNode
  • Create protocol for OtpMbox
  • Create protocol for OtpSelf

Implementation

Note that some of these may need special constructors

  • Create defotp for OtpLocalNode
  • Create defotp for OtpNode
  • Create defotp for OtpMbox
  • Create defotp for OtpSelf
  • Create defotp for OtpPeer

Example simple Clojure server locks up REPL after one use

When the following code is run in the REPL and is asked to stop with a client message, it becomes unresponsive on restart and doesn't seem to see client messages anymore. Furthermore, when the server function is ^Ced, subsequent function calls (seemingly any function call) locks up the REPL.

I suspect the problem is with the connection object not being properly destroyed when stopping the server ...

Here's the code:

(require '[clojure.core.match :refer [match]])

(defn png-png
  []
  (let [init-state 0
        self (node/self :srvr)
        _ (node/publish-port self)
        cnnx (node/accept self)]
    (loop [png-count init-state]
      (match [(conn/receive cnnx)]
        [[:ping caller]] (do (conn/! cnnx caller :pong)
                             (recur (inc png-count)))
        [[:get-count caller]] (do (conn/! cnnx caller png-count)
                                  (recur png-count))
        [[:stop caller]] (do (conn/unlink cnnx caller)
                             (node/unpublish self)
                             :stopped)))))

NullPointerException when calling `(self)`

Hi!

I'm probably doing something wrong, but I immediately run into errors when I try to use this library.

My Clojure source only contains:

(ns my-project.core
  (:require [clojang.core :refer [! receive self]]))

Trying to call (self) I get a NullPointerException.

I've tried both version 0.6.0 and 0.7.0-SNAPSHOT. Same behavior.

Here is a dump of the stack trace.

  Show: Project-Only All 
  Hide: Clojure Java REPL Tooling Duplicates  (11 frames hidden)

1. Unhandled java.lang.NullPointerException
   (No message)

         AbstractNode.java:  189  com.ericsson.otp.erlang.AbstractNode/<init>
         AbstractNode.java:  162  com.ericsson.otp.erlang.AbstractNode/<init>
         OtpLocalNode.java:   39  com.ericsson.otp.erlang.OtpLocalNode/<init>
              OtpNode.java:   99  com.ericsson.otp.erlang.OtpNode/<init>
NativeConstructorAccessorImpl.java:   -2  sun.reflect.NativeConstructorAccessorImpl/newInstance0
NativeConstructorAccessorImpl.java:   62  sun.reflect.NativeConstructorAccessorImpl/newInstance
DelegatingConstructorAccessorImpl.java:   45  sun.reflect.DelegatingConstructorAccessorImpl/newInstance
          Constructor.java:  423  java.lang.reflect.Constructor/newInstance
            Reflector.java:  294  clojure.lang.Reflector/invokeConstructor
                  core.clj:   47  jiface.core/dynamic-init
                  core.clj:   42  jiface.core/dynamic-init
                   otp.clj:   13  jiface.otp/create
                   otp.clj:    7  jiface.otp/create
               RestFn.java:  137  clojure.lang.RestFn/applyTo
                  core.clj:  665  clojure.core/apply
                  core.clj:  660  clojure.core/apply
                  core.clj:  248  dire.core/supervised-meta
                  core.clj:  243  dire.core/supervised-meta
               RestFn.java:  464  clojure.lang.RestFn/invoke
                  core.clj: 2626  clojure.core/partial/fn
                  AFn.java:  160  clojure.lang.AFn/applyToHelper
               RestFn.java:  132  clojure.lang.RestFn/applyTo
                  core.clj:  667  clojure.core/apply
                  core.clj:  660  clojure.core/apply
                 hooke.clj:   40  robert.hooke/compose-hooks/fn
               RestFn.java:  137  clojure.lang.RestFn/applyTo
                  core.clj:  665  clojure.core/apply
                  core.clj:  660  clojure.core/apply
                 hooke.clj:   46  robert.hooke/run-hooks
                 hooke.clj:   45  robert.hooke/run-hooks
                 hooke.clj:   54  robert.hooke/prepare-for-hooks/fn/fn
               RestFn.java:  137  clojure.lang.RestFn/applyTo
            AFunction.java:   31  clojure.lang.AFunction$1/doInvoke
               RestFn.java:  137  clojure.lang.RestFn/applyTo
                  Var.java:  705  clojure.lang.Var/applyTo
                  core.clj:  665  clojure.core/apply
                  core.clj:  660  clojure.core/apply
                 nodes.clj:   44  jiface.otp.nodes/node
                 nodes.clj:   21  jiface.otp.nodes/node
               RestFn.java:  410  clojure.lang.RestFn/invoke
                  AFn.java:  154  clojure.lang.AFn/applyToHelper
               RestFn.java:  132  clojure.lang.RestFn/applyTo
                  core.clj:  665  clojure.core/apply
                  core.clj:  660  clojure.core/apply
               memoize.clj:  104  clojure.core.memoize/through*/fn
                 cache.clj:   55  clojure.core.cache/through/fn
               memoize.clj:  103  clojure.core.memoize/through*/fn/fn
               memoize.clj:   66  clojure.core.memoize.RetryingDelay/deref
                  core.clj: 2320  clojure.core/deref
                  core.clj: 2306  clojure.core/deref
               memoize.clj:  210  clojure.core.memoize/build-memoizer/fn
               RestFn.java:  137  clojure.lang.RestFn/applyTo
            AFunction.java:   31  clojure.lang.AFunction$1/doInvoke
               RestFn.java:  408  clojure.lang.RestFn/invoke
                  mbox.clj:   54  clojang.mbox/get-default
                  mbox.clj:   43  clojang.mbox/get-default
                  mbox.clj:   60  clojang.mbox/self
                  mbox.clj:   57  clojang.mbox/self
                  Var.java:  380  clojure.lang.Var/invoke
                      REPL:   44  exogenous-abs.core/eval14227
                      REPL:   44  exogenous-abs.core/eval14227
             Compiler.java: 7177  clojure.lang.Compiler/eval
             Compiler.java: 7132  clojure.lang.Compiler/eval
                  core.clj: 3214  clojure.core/eval
                  core.clj: 3210  clojure.core/eval
                  main.clj:  437  clojure.main/repl/read-eval-print/fn
                  main.clj:  437  clojure.main/repl/read-eval-print
                  main.clj:  458  clojure.main/repl/fn
                  main.clj:  458  clojure.main/repl
                  main.clj:  368  clojure.main/repl
               RestFn.java:  137  clojure.lang.RestFn/applyTo
                  core.clj:  665  clojure.core/apply
                  core.clj:  660  clojure.core/apply
                regrow.clj:   18  refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn
               RestFn.java: 1523  clojure.lang.RestFn/invoke
    interruptible_eval.clj:   79  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:   55  nrepl.middleware.interruptible-eval/evaluate
    interruptible_eval.clj:  142  nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn
                  AFn.java:   22  clojure.lang.AFn/run
               session.clj:  171  nrepl.middleware.session/session-exec/main-loop/fn
               session.clj:  170  nrepl.middleware.session/session-exec/main-loop
                  AFn.java:   22  clojure.lang.AFn/run
               Thread.java:  745  java.lang.Thread/run

Low-level Wrapper: create types

Tasks

Infrastructure

  • Create deferltype macro

Protocols

  • Create protocol for OtpErlangObject subclasses
  • Create protocol for OtpErlangAtom subclasses
  • Create protocol for OtpErlangBitstr subclasses
  • Create protocol for OtpErlangDouble subclasses
  • Create protocol for OtpErlangList subclasses
  • Create protocol for OtpErlangLong subclasses
  • Create protocol for OtpErlangMap subclasses
  • Create protocol for OtpErlangPid subclasses
  • Create protocol for OtpErlangPort subclasses
  • Create protocol for OtpErlangRef subclasses
  • Create protocol for OtpErlangString subclasses
  • Create protocol for OtpErlangTuple subclasses

Implementation

Note that some of these may need special constructors

  • Create deferltype for OtpErlangAtom
  • Create deferltype for OtpErlangBinary
  • Create deferltype for OtpErlangBitstr
  • Create deferltype for OtpErlangBoolean
  • Create deferltype for OtpErlangByte
  • Create deferltype for OtpErlangChar
  • Create deferltype for OtpErlangDouble
  • Create deferltype for OtpErlangExternalFun
  • Create deferltype for OtpErlangFloat
  • Create deferltype for OtpErlangFun (will need special constructor)
  • Create deferltype for OtpErlangInt
  • Create deferltype for OtpErlangList (will need special constructor)
  • Create deferltype for OtpErlangList$SubList
  • Create deferltype for OtpErlangLong
  • Create deferltype for OtpErlangMap (will need special constructor)
  • Create deferltype for OtpErlangPid (will need special constructor)
  • Create deferltype for OtpErlangPort (will need special constructor)
  • Create deferltype for OtpErlangRef (will need special constructor)
  • Create deferltype for OtpErlangShort
  • Create deferltype for OtpErlangString
  • Create deferltype for OtpErlangTuple (will need special constructor)
  • Create deferltype for OtpErlangUInt
  • Create deferltype for OtpErlangUShort

Epic: Major Refactor

Been living in JVM a lot lately, and am ready to revisit this project. Here are my plans:

  • Move existing master to clojure-erlastic-fork
  • Create a new master (orphan branch, totally new code) that:
    1. provides an idiomatic Clojure wrapper for JInterface (probably in clojang.jinterface)
    2. provides a not-so-low level wrapper for this which reduces boiler plate -- hopefully significantly (probably in clojang.erlang and clojang.otp)
    3. a high-level collection of namespaces that makes creating Clojure/Erlang services a snap (not sure about namespace here just yet -- that will have to wait for now)
  • Move this puppy into the lfex Github org
  • Start building apps/libraries against it!

Step 0 will actually be creating an example app that uses JInterface as-is and exercises as much of it as possible (maybe even converting this to a set of system or integration tests).

Once that's working, I will begin on Step 1. The example from Step 0 will be copied and converted to use the new idiomatic Clojure wrapper for JInterface.

For interested parties, this work should be fairly easy to split up. I think that there will be basically one major namespace per JInterface class (maybe some closely related class wrappers will end up in the same namespace), so it will be fairly easy to avoid stomping on someone else's work.

Low-level Wrapper Tasks

  • #2 - Create types
  • #3 - Wrap constants
  • #4 - Wrap connection and empd objects
  • #5 - Wrap streams and messaging
  • #6 - Wrap nodes, peers, and self
  • #7 - Wrap servers and sockets

Mid-level Wrapper Tasks

  • - TBD

High-level Additions

  • - TBD

Link code references in mid-level API docstrings

Right now the docstrings in the mid-level API have the following issues:

  • Some object names are just code text instead of being links to the documentation for that object
  • Some linked code points to the javadocs, some to the low-level API docs
  • Some linked code points to the low-level API Clojure wrapper
  • parameters and return values are not discussed

These all need to be fixed:

  • Ensure most code text points to the mid-level documentation for the given namespace or function
  • Where the low-level API functions get mentioned, but sure they are linked
  • In the mid-level API, references to JInterface objects should be zero of few;
    • where the references are supposed to be there, ensure that the referenced JInterface objects are linked to the javadocs
    • where the references should instead be to the low-level API docs, add/update the links to those docs
  • Ensure that docstrings have notes about the (new, Erlang-inspired) return values (see issue #9) and the possible types for function arguments

Link code references in low-level API docstrings

Right now the docstrings in the low-level API have the following issues:

  • Some object names are just code text instead of being links to the documentation for that object
  • Some linked code points to the javadocs, some to the low-level API docs
  • parameters and return values are not discussed

These all need to be fixed:

  • Ensure all code text points to the low-level documentation for the given namespace or function
  • Ensure that the JInterface objects are referenced only when saying what is wrapped (and then, link to the javadocs)
  • Ensure that docstrings have notes about the return value and the possible types for function arguments)

Set up documentation site

  • API reference documentation
  • WONTDO: Slate-based user-guide documentation
  • Slate-alternative (markdown integrated with codox)

Support default node per JVM instance

The Erlang docs for JInterface recommend that only one node be created per JVM. This makes sense for several reasons:

  • every node has a port associated with it, and thus a file descriptor; FDs don't grow on trees
  • creating a node also creates a thread to manage incoming connections (Acceptor) and data structures for tracking connections and mailboxes
  • the Erlang VM does the same thing: e.g., starting up the LFE REPL in distribution mode creates a node for that Erlang VM, allowing it to receive messages from other nodes.

This opens up a whole can of cats:

  • how do you get the JVM to do anything on startup?
  • how to you create a reference to the node object, once created? (you're going to need it to pass into functions)
  • if we have a default node object, why can't we support all the node functions, but with decreased arity? (passing the default node in the case of arity - 1) Why not? Let's go nuts.

The answer to the first bullet: create an agent and set the agent option the project.clj file.

The answer to the second bullet is simple: create a closure. But what's the impact? We won't know until we try it and someone reports a bug. Let's go nuts.

The answer to the third bullet has already been given multiple times, now: Let's go nuts.

I think we've got a mascot for this ticket:

l3s6wjf

Overhaul function returns

  • Functions from the mid-level API with nil (void in Java) return values should instead return :ok
  • Functions in the mid-level API that are sending data should return the sent data, not nil or :ok
  • Documentation needs to be updated with this change:
  • Update README
  • Update mid-level API user guide
  • Update "Talking to Servers"
  • Functions in the low-level API shouldn't be changed, but should be as close to the Java return values as possible

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.