Giter VIP home page Giter VIP logo

hebigo's Introduction

Gitter

Hebigo

蛇語(HEH-bee-go): Snake-speak. Hebigo is an indentation-based Hissp skin designed to resemble Python.

It includes the Hebigo Hissp reader, the Jupyter-console based Hebigo REPL, and the Hebigo basic macros—a collection of Python-like compiler macros, which also function independently of the Hebigo reader (i.e. they work in Lissp or Hissp readerless mode as well).

Hebigo is still in the prototyping phase, so it's not on PyPI yet. Install it directly from GitHub with

pip install git+https://github.com/gilch/hebigo

See the native tests for example Hebigo code.

Hebigo keeps Python's expressions as bracketed expressions, but completely replaces Python's statements with hotword expressions, which have Hissp's literals, semantics, and macros.

Bracketed Expressions

Bracketed expression are called that because they must be "bracketed" somehow in order to be distinguishable from the hotword expressions. Parentheses will always work, but [] or {} are sufficient. Quotation marks also work, even with prefixes, like b'' or f"""""", etc.

Bracketed expressions are mainly used for infix operators, simple literals, and f-strings (things that might be awkward as hotword expressions), but any Python expression will work, even more complex ones like nested comprehensions or chained method calls. It's best to keep these simple though. You can't use macros in them.

Hotword Expressions

Hotword expressions are called expressions because they evaluate to a value, but they resemble Python's statements in form:

word:
   block1
   subword:
       subblock
   block2

etc.

The rules are as follows.

  1. A word ending in a : is a "hotword", that is, a function or macro invocation that can take arguments.
hotword: not_hotword
  1. A hotword with no whitespace after its colon is unary. Otherwise it's multiary.
unary:arg
multiary0:
multiary1: arg
multiary2: a b
multiary3: a b c
  1. Multiary hotwords take all remaning arguments in a line.
hotword: arg1 arg2 arg3: a:0 b: 1 2

Parsed like

hotword(arg1, arg2, arg3(a(0), b(1, 2)))
  1. The first (multiary) hotword of the line gets the arguments in the indented block for that line (if any).
multiary: a b c
    d e f
foo unary:gets_block: gets_line: 1 2
    a b
    c d

Parsed like

multiary(a, b, c, d, e, f)
foo
unary(gets_block(gets_line(1, 2), a, b, c, d))

Another way to think of it is that a unary applied to another hotword creates a compound hotword, which is a composition of the functions. In the example above, foo is not a hotword (no colon), and the compound hotword unary:gets_block: is the first hotword of the line, so it gets the indented block below the line.

  1. The special hotword pass: invokes its first argument, passing it the remainder. This allows you to invoke things that are not words, like lambda expressions:
pass: foo a b
pass: (lambda *a: a) 1 2 3

Parsed like

foo(a, b)
(lambda *a: a)(1, 2, 3)

Style

These indentation rules were designed to resemble Python and make editing easier with a basic editor than for S-expressions. As a matter of style, arguments should be passed in one of three forms, which should not be mixed for function calls.

linear: a b c d
linear_block:
    a b c d
block:
    a
    b
    c
    d
# What NOT to do, although it compiles fine.
bad_mix: a
   b c
   d

compare that to the same layout for Python invocations.

linear(a, b, c, d)
linear_block(
    a, b, c, d
)
block(
    a,
    b,
    c,
    d,
)
# What NOT to do.
bad_mix(a,
    b, c
    d
)  # PEP 8 that. Please.

The above is for function calls only. Macro invocations are not exactly bound by these three layout styles, and may instead have other documented preferred layouts.

You should group arguments using whitespace when it makes sense to do so. Anywhere you'd use a comma (or newline) in Clojure, you add an extra space or newline. This usually after the : in function invocations or parameter tuples, where the arguments are implicitly paired.

linear: x : a b  c d  # Note extra space between b and c.
linear_block:
    x : a b  c d
block:
    x
    : a b  # Newline also works.
    c d

Literals

Literals are mostly the same as Lissp for hotword expressions (and exactly like Python in bracketed expressions).

Hebigo does not munge symbols like Lissp does. Qualified symbols (like builtins..print) are allowed, but not in bracketed expressions, which must be pure Python.

Control words are words that start with a :. These are not allowed in bracketed expressions either (although they're just compiled to strings, which are). You'll need these for paired arguments, same as Lissp. These two expressions are normally equivalent in Hebigo.

print: 1 2 3 : :* 'abc'  sep "/"  # Hotword expression.
(print(1, 2, 3, *'abc', sep="/"))  # Bracketed expression.

However, if a macro named print were defined, then the hotword version would invoke the macro, but the bracketed version would still invoke the builtin, because macros can only be invoked in hotword expressions.

Control words may also be used as hotwords, in which case they both begin and end with a colon. This makes no sense at the top level (because strings are not callable), but macros do use them to group arguments.

Unlike Lissp, normal string literals cannot contain literal newlines. Use \n or triple quotes like Python instead. (Recall that strings count as bracketed expressions.)

Hotword expressions may contain bracketed expressions, but not the reverse, since bracketed expressions must be valid Python, just like how the statements that the hotword expressions replace may contain expressions, but Python expressions may not contain Python statements.

And finally, the ! is an abbreviation for hebi.basic.._macro_., the qualifier for Hebigo's included macros. (This can't work in bracketed expressions either.) Hebigo has no other "reader macros".

Examples

(Also see Hebigo's native tests.)

Obligatory factorial example.

In basic Lissp. (Prelude assumed.)

(define factorial
  (lambda n
    (if-else (eq n 0)
      1
      (mul n (factorial (sub n 1))))))

Literal translation of the above to Hebigo.

define: factorial
  lambda: n
    ifQz_else: eq: n 0
      1
      mul: n factorial: sub: n 1

Note the munged name.

In more idiomatic Hebigo with statement macros and bracketed expressions.

def: factorial: n
  if: (n == 0)
    :then: 1
    :else: (n * factorial(n - 1))

Literal translation of the above to Lissp. (Statement macros required.)

(def_ (factorial n)
  (if_ .#"n == 0"
    (:then 1)
    (:else .#"n * factorial(n - 1)")))

Note the injections.

Finally, in idiomatic Lissp with Hebigo's macros.

(def_ (factorial n)
  (if-else (eq n 0)
    1
    (mul n (factorial (sub n 1)))))

Fibonacci

In Python.

from functools import lru_cache

@lru_cache(None)
def fibonacci(n):
    if n <= 1:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

In Hebigo.

def: fibonacci: n
  :@ functools..lru_cache: None  # Qualified identifier in decorator.
  if: (n <= 1)
    :then: n
    :else: (fibonacci(n - 1) + fibonacci(n - 2))

Literal translation to Lissp.

(def_ (fibonacci n)
  :@ (functools..lru_cache None) ; Qualified identifier in decorator.
  (if_ .#"n <= 1"
    (:then n)
    (:else .#"fibonacci(n - 1) + fibonacci(n - 2)")))

In basic Lissp.

(define fibonacci
  ((functools..lru_cache None) ; Callable expression.
   (lambda n
     (if-else (le n 1)
       n
       (add (fibonacci (sub n 1))
            (fibonacci (sub n 2)))))))

Literal translation to Hebigo

define: fibonacci
  pass: functools..lru_cache: None  # Callable expression.
    lambda: n
      ifQz_else: le: n 1
        n
        add:
          fibonacci: sub: n 1
          fibonacci: sub: n 2

hebigo's People

Contributors

brandonwillard avatar gilch avatar

Stargazers

 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

hebigo's Issues

Add native tests

That is, tests written in actual .hebi files.
We can test the parser just by the Hissp output, but we should compile the macros and run the helper functions.

We do need some minimal bootstrap machinery working just to write the tests though. I can maybe get away with using the basic Lissp macros, since they're included.

Fix relative imports

There's no __package__ at compile time. This might require a Hissp-level fix. But runtime-only relative imports also need to work.

implement if/elif*/else

How should these look in Hegibo?
Given the Python examples,

if a < b:
    print('less')
    return a  # Assume we're in a function.
elif a > b:
    print('more')
    return b
elif a == b:
    print('equal')
    return a
else:
    print 'nan'

and

if case == x:
    print(x, a)
else:
    print(b)

Naiively, one might think

if: (case == x)
    print: x, a
else:
    print: b

This is gramatically valid Hebigo, but then the implied brackets would look like this

{if: (case == x)
    {print: x, a}}
{else:
    {print: b}}

The if and else are completely separate forms. Not going to work. They need a surrounding form.

Consider allowing control words to take arguments

I'm renaming :keywords to "control words". "Keyword" means something else in Python. In the Hissp documentation I was calling them "key symbols", I think I'll change that as well for consistency.

:controlwords are used by some of the little DSLs created by macros, sometimes to label what the next argument is for (e.g. :as in from:/import: and try:.)

But there are cases when I want blocks to have these labels attached. I think it's more natural to just allow the control word to take arguments like symbols do than to use :foo pass: or pass: :foo.

Obviously this makes no sense at the top level. Python identifiers can't have :. (That doesn't stop you from putting a non-identifier string in a __dict__ though.) So I don't think Hissp would compile it into anything sensible. But inside a macro, the code would get rewritten anyway, and this can make sense, for example

try:
    !begin:
        print: "It's dangerous!"
        something_risky: thing
    :except: LikelyProblemError
        print: "Oops!"
        fix_it:
    :except: Exception :as ex
        do_something: ex
    :else:
        print: "Hooray!"
        thing
    :finally:
        .close: thing

or

if: (a<b)
    :then:
        print: "less"
    :elif: (a>b)
        print: "more"
    :elif: (a==b)
        print: "equal"
    :else:
        print: "nan"

Because of automatic qualification of reserved words and of normal unqualified symbols in masks, using symbols here is kind of more difficult than it should be. Control word wouldn't have this issue. And because control words can have special characters, the !mask: macro can look a little more lispy and use :,:/:,@: instead of _:/__: like before.

On the downside, we're using even more colons than before. Unfortunately, Hissp had to special case :keywords at the compiler level to make them work, and Python used colon to start blocks for completely unreleated reasons. Maybe I could change the :keyword symbol to $keyword or something. The code examples in docstrings also look funny if the IDE thinks it's supposed to be reStructuredText, because having a word beginning and ending with a colon (like :foo:) means something in that language.

Implement template/quasiquote macros

Lissp has template quotes, but Hebigo doesn't. I think Scheme's quasiquote reader macros just expand to normal macros, so I think this should be possible.

Hebigo so far has no munging like Lissp does, so unary hotwords must be valid Python identifiers. `: wouldn't work. quasiquote: is maybe too long. Even template: is not much better. I'm thinking mask:.

Implement replacements for Python statements.

I've already done most of the work in Drython. I think I'll just copy the relevant bits over, but this may become a separate package later.

These will be special cased in the Hebigo reader because they're Python reserved words, e.g. the symbol def would munge as hebi.basic.._macro_.def_.

def is an especially important one. I'm thinking roughly Scheme semantics:

So in Hebigo,

def: key value

assigns a simple global.

But

def: name: arg1 arg2 : kw1 default1
    "docstring"
    body...

defines a function. It should compile to Hissp like:

('hebi.basic.._macro_.def_', ('name', 'arg1', 'arg2', ':', 'kw1', 'default1'),
    ('quote', 'docstring', {'str': True}),
    body...

Extra reserved words

Hebigo will automatically expand Python reserved words to qualified macros. But we'll want some more "reserved" words than this. We also don't want them to conflict with normal Python identifiers. Thus far, Hebigo does not allow special characters in its symbols. So we can special case symbols that start with ! for this purpose.

At minimum, I think we'll need

  • !let for locals
  • !mask for quasiquotes
  • !require for macro imports

Perhaps we should think of these as Hebigo's macro "builtins", in which case, we might want a lot more of them than this.

Add match/case

3.10 got a new statement type for pattern matching. Too bad it wasn't an expression or we could just put it in brackets. But Lisp macros are easily powerful enough to do this. Like the other statement macros, this should follow Python's form and semantics as closely as reasonably possible.

Functions made with def are still named lambda?

Even after it updates the __name__ and __qualname__, functions made with the def macro are still getting called "lambda" in the tracebacks. It seems to be part of the code object itself.

With Python 3.8's new types.CodeType.replace(), the code object itself might be easy to update with a new name.

But, if we use 3.8 features, that means that Hebigo's def macro would only work on 3.8 or later.

Consider allowing hotword expressions nested in bracketed expressions

While parsing a bracketed expression, we could recursively switch back to the base Hebigo parser when encountering a macro character that Python doesn't use, like !, until we finish the next Hebigo expression, then place the result of compiling that back into the Python string we're building. This would effectively be a builtin reader macro. For example,

def: fibonacci: n
  :@ functools..lru_cache: None  # Qualified identifier in decorator.
  if: (n <= 1)
    :then: n
    :else: (!fibonacci:(n - 1) + !fibonacci:(n - 2))

Maybe a bad example, since Python's expression syntax can handle this part fine.

    :else: (fibonacci(n - 1) + fibonacci(n - 2))

But suppose we needed a macro.

(!macro:spam + !macro:eggs)

We'd currently have to do something like

!let: :,: s e
  :be hebi.bootstrap..entuple: macro:spam macro:eggs
  (s + e)

or

operator..add: macro:spam macro:eggs

On the other hand, we might want to encourage using hotword expressions instead of Python expressions, because it's much easier to write macros to work with those.

Python 3.10 broke the lexer

Hebigo's lexer relies on Python to determine when a bracketed expression is complete. It reads up to the first matching ending bracket, and asks Python to parse it. Brackets can be nested and escaped in things like string literals, meaning the first one encountered might not be the right one, so on certain SyntaxErrors it reads up to the next matching bracket and tries again. Other SyntaxErrors are unrecoverable and should propagate out. Unfortunately, the only way to distinguish these cases is by the error message, and those changed in 3.10.

I could include the new messages, but this seems just as brittle, and I feel like I could easily miss one. Another option would be to assume all SyntaxErrors are recoverable. This is probably good enough in the REPL, but it will result in poorer feedback in case of unrecoverable errors, as the kernel will simply keep asking for more lines, rather than pointing at the problem. In a .hebi file, an unrecoverable error would result in the remainder of the file lexing as one bracketed expression.

Consider an EBNF grammar, at least in docs

Some notes from a private chat with @brandonwillard who seems to think this would help with tooling.

Hebigo uses Python's expression syntax (but not statements), so that would have to be copied for a complete grammar. Note that Python expressions in Hebigo must be simple literals or "bracketed" somehow. Parentheses work for anything, but if it already has {}, [], or valid Python quotation marks, that's also enough. (Including things like f"", b"", etc.)

Hebigo translates very directly to Hissp tuples.

In Lisp, the head of the list is the function/macro, and the rest are arguments. The head is almost always a symbol.

In Hebigo, you make a "list" (tuple) with a hotword: a symbol ending in a colon, like foo:.
The hotword is the "head" of the "list". The expressions following that are the arguments. The layout determines where the "list" ends, much like Python.

I designed the layout rules to make Hebigo easy to edit with a minimal editor. Hotwords can be either unary or polyary. Unary has no whitespace after the colon and attaches directly to its single argument, like a tag. The hotword becomes the "head" of the tuple. A polyary tuple extends to the end of the line, or in the case of the first polyary hotword of the line, it also includes the indented block below it. (As a matter of style, simple function invocations would use either arguments on the same line as the hotword or in the block beneath it, but not both. Macros, on the other hand, may use both.)

There's also an "empty" hotword ::. It uses its first argument as its "head". You can use this to make an empty tuple, or a tuple that has a complex expression as its head (instead of a simple symbol.) You can also use :: stylistically in cases when the tuple is just grouping things and isn't an invocation where the head is special.

Unary hotwords fill in for tag macros and such. (There are no reader macros in Hebigo.) A common use would be quote:

There are a lot of examples in the tests. Any grammar would, at minimum, have to pass those tests. But I wasn't really testing the Python expression grammar there. Any grammar would pretty much have to be machine-checked before I'd trust it to be accurate. Lark is an option. But indented blocks aren't even context-free without some preprocessing. This doesn't look easy.

No error for unexpected indent

If you forget a colon, but indent a block more you should get an error.

Recent example from tests:

test_default_strs lambda self:
    self.assertEqual:
        ['ab', 22, 33]
        !let
            :=: :strs: a b c
                :default: a ('a'+'b')
            {'b':22,'c':33}
            [a, b, c]

Note that lambda and the !let were intended as hotwords and should have colons. The former is valid, since self: gets the block. (Maybe a good reason to spell it as pass:self instead.) The latter should be an error (but isn't), because it indents the block of self.assertEqual: more.

class macro and decorator syntax

How should the class: macro look/work? I want Hebigo to look and feel familiar to Pythonistas. But at the same time, Hissp is targeting a functional subset of Python to reduce incidental complexity. It can't be simpler if it works exactly like Python.

installation with pipx fails

I installed hebigo this way without any errors:
pipx install --verbose git+https://github.com/gilch/hebigo.git
but when running hebi I get this error message:

; hebi                                                                     
/usr/local/bin/python: Error while finding module specification for 'hebi.kernel' (ModuleNotFoundError: No module named 'hebi')
[ZMQTerminalIPythonApp] CRITICAL | Could not find existing kernel connection file kernel-6944.json

pipx is the most comfortable way to experiment with commandline tools, so that's why it would be great, if hebigo would support it. Thanks

REPL brittleness

See #2.

Jupyter makes this weirdly awkward in a venv. It wants global kernelspecs. Install that if you want, but that seems like overkill when trying out a REPL in a venv. You can start a kernel without installing it, and connect to the most recently started one. But getting these to connect up reliably and automatically in one command is surprisingly difficult. The current solution works, but not reliably. Sometimes it starts the kernel but fails to connect. Sometimes the first error crashes the console. Can't seem to reproduce that one.

Quitting is also awkward. I'm worried about accidentally leaving kernels running in the background, using up resources, and possibly stealing connections later. It also seems to be possible to exit the kernel without quitting the console, which leaves you with a broken console.

No showstoppers, but no easy fixes, and it just doesn't work as well as it should.

Consider renaming the empty hotword from :: to !:

The empty hotword is currently a double colon, like ::. A triple colon is not that rare, like quote::: ... (for '(...)), which gets harder to read, and a quintuple colon, like quote::::: ... (for '((...))) is probably possible. Triple is maybe OK, but quintuple is nuts.

The natural empty hotword would have been :, but that's already a :keyword due to an unfortunate historical collision. And Hissp already uses it to separate single from paired parameters in lambdas, and single from paired arguments in normal calls, so we can't just make it a special case.

Now that we have the !macros, !: would be an option. quote:!:!: ... looks much less bad. I could maybe even implement it as a macro defined in readerless mode, which would simplify the parser.

loops and yield (and async?)

Hissp's target Python subset only has expressions. Python's comprehension syntax (being expressions) already works in theory.

Loop-equivalents (for/while) are fairly easy to implement using higher-order functions, though while gets awkward without assignment statements. (It may be less of a problem when we get the walrus in Python 3.8.)

Technically, yield is considered an expression in Python, but it does something weird to its surrounding function: It makes it return a generator instead, so they compose differently. Hebigo control-statement macros expand to thunks to be able to delay, repeat, or avoid evaluation, so any yield expression in an if or loop (the normal case) would transform the wrong function's return: the inner thunk, instead of the surrounding definition. Higher-order functions would now require a yield from to propagate this behavior. Even if we could add that everywhere it's needed, how do you tell if the thunk has a yield in it?

One possible solution is to have a parallel version of each control statement that has the yield from, and then the user has to pick the right one (or uses a macro that does it for them). I think this could get awkward fast. And then what about new user macros?

This lack of natural composability makes me think that yield expressions are the wrong approach.

Drython's solution was to create a background thread to save the execution state. This works, but has greater overhead than a native yield would. Perhaps that is acceptable, since using higher-order functions everywhere has a higher overhead to begin with. Performance was never really the point of Hissp. I'm also not sure if I ever got them garbage collected properly. Threaded code is notoriously hard to test due to nondeterministic behavior (race conditions).

Scheme's call/cc or Haskell's IO monad also look promising. They seem a lot more naturally composable than Python's yield. Unfortunately, I don't think I understand them well enough to tell, much less implement yield this way.

Another option might be to not support yield at all. Given itertools, genexprs, and statement-equivalent macros that work in lambdas, you'll probably rarely need them. But not having them means adding friction to the use of Python libraries. There are cases even in the standard library that require yield, like @contextlib.contextmanager.

A class with the right dunder methods can be a generator, though implementing them seems more difficult generally. This goes for awaitables as well.

Consider semicolons for one-liners

Python allows a semicolon for joining statements on one line, so there's precedent. It's mostly considered bad style in production code, but is useful for shell commands, although the inability to join certain statement types limits its usefulness.

for i in range(3): print(i); print(i*i)  # Python allows this.
print('hi'); for i in range(3): print(i)  # SyntaxError
for i in range(3): for j in range(3): print(i, j)  # SyntaxError
class Foo(object): def: foo(self, x): print(x)  # SyntaxError

Hebigo can do some of these already.

for: i :in range:3 print:i print: (i*i)
print:'hi' for: i :in range:3 print:i
for: i :in range:3 for j :in range:3 print: i j

But the class doesn't quite work:

class: Foo:object def: foo: self x print:x  # print in params
class: Foo:object def: foo: self x
  print:x  # print in class

But with a semicolon acting like a Lisp closing parenthesis,

class: Foo:object def: foo: self x; print:x

It's almost never used at the end of a line in Python (although this is technically allowed) and it would tend to be a long train in a Lisp, so I'd rather not allow it in Hebigo. But even internal-only, you can get doubled semicolons for reasonable use cases:

class: Foo:object
 def: foo: self x y
  print: x y
 def: bar: self a b
  print: a b

# one-liner version
class: Foo: object; def: foo: self x y; print: x y;; def: bar: self a b; print: a b

Maybe this is OK for shell commands, but it does require more careful thinking than the usual indentation-based notation.
If you think of x:/; as parentheses, the Lispiness becomes more apparent. Both forms at once to show where the double came from:

class:
  Foo:
    object;
  def:
    foo:
      self x y;
    print:
      x y;;
  def:
   bar:
     self a b;
   print:
     a b;;;

You can see a train of three at the end. This wouldn't be allowed of course, but it does show the structure.

It could also make normal code more compact, but I find this less readable:

class: Foo:object
 def: foo: self x y; print: x y
 def: bar: self a b; print: a b

So, as in Python, I think they should be considered bad style in source code. I can't think of a case where it reads better, but it would make any expression possible as a one-liner, a capability that Python lacks but has some compromise support for. You can theoretically do all of your one-liners in Lissp anyway, so I'm not sure if this is worth it.

implement !let

How should it look in Hebigo? In Scheme it's like

(let ((foo 1)
      (bar 2))
  (frobnicate foo bar))

The same structure wrritten in Hebigo would be

!let:
    pass:
        foo: 1
        bar: 2
    frobnicate: foo bar

That doesn't look too bad, but I'd prefer to avoid using pass:. We could use a meaningful control word with arguments instead.

!let:
    :def:
        foo: 1
        bar: 2
    frobnicate: foo bar

Arc Lisp's let only binds one name.

!let: foo 1
    frobnicate: foo 2

This looks a lot cleaner, until you have to start nesting them.

!let: foo 1
    !let: bar 2
        frobnicate: foo bar

Still not that bad when there's only two, but indents can add up fast. I'd like to flatten them for the same reason we have elif in Python. Arc has with for this.

With multiple variables, there's also let* and letrec variants to consider. letrec looks hard without mutation. Clojure doesn't have it, but has letfn instead. But at that point you might just want a class.

Clojure has a powerful recursive destructuring syntax built into its let. Even with one binding pair, you could still bind multiple variables this way. Python has sequence destructuring only, but requires statements to do it. An example like

(x1, y1), (x2, y2) = rect
frobnicate(x1, x2, y1, y2)

Might look like

!let:
    pass:
        pass: x1 y1
        pass: x2 y2
    rect
    frobnicate: x1 x2 y1 y2

It's not so pretty without the brackets. With more meaningful control words,

!let: rect :as
    :,: :,: x1 y1
        :,: x2 y2
    frobnicate: x1 x2 y1 y2

More Clojurelike options

!let:
    foo :as
    :,: :,: x1 y1 :& rest
        :,: x2 y2
        :as all
    bar :as
    :=: "spam" food1  # associative destructuring
        "eggs" food2
    frobnicate: ...

Implement a REPL

Basing one on IPython might be a good idea. We'd also get a Jupyter kernel out of it.

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.