Hacker News new | past | comments | ask | show | jobs | submit login
Gravity – An embeddable programming language (gravity-lang.org)
234 points by creolabs on June 22, 2018 | hide | past | web | favorite | 172 comments



"The VM code is about 2K lines long, the multipass compiler code is about 3K lines and the shared code is about 2K lines long."

I was surprised by these numbers so I checked, and this is highly misleading (or very much out of date). The parser alone (https://github.com/marcobambini/gravity/blob/master/src/comp...) is over 2K lines, and with the lexer and the AST definitions the frontend of the compiler is already comfortably above 3K lines. Also, the VM (https://github.com/marcobambini/gravity/blob/master/src/runt...) is about 2K lines, but then there is another 3K lines in another "runtime" file (https://github.com/marcobambini/gravity/blob/master/src/runt...).

Still, it's not a huge project, and it might be interesting as a point of study for people interested in compilers (to bytecode) or bytecode interpreters. The input syntax looks pretty as well.

Interestingly, https://github.com/marcobambini/gravity/blob/master/src/runt... also serves as a nice illustration of the fact that if tabs are allowed in source code, people will mix them with spaces, resulting in code that looks bad unless you have the exact same tab width preferences as the author.


> the fact that if tabs are allowed in source code, people will mix them with spaces, resulting in code that looks bad unless you have the exact same tab width preferences as the author.

Every bit of go code shows you are wrong. If a codebase uses tabs for indentation (which is the only place they should ever be allowed) then it's a mistake to use spaces for indentation. My first ever contribution to a free software project (when I was about 15) used the wrong whitespace for indentation. They made me fix the patch and that is how I learnt about whitespace. The example you've shown is just an example of bad code standards, not tabs being bad.


All the world is not golang.

Go is actually one of the few exceptions in the world due to having had "go fix" baked into the compiler/ecosystem from the start. That makes a huge difference.

It's also quite a simple language and is pretty easy to auto-format reasonably. Few other languages are that easy to auto-format or have that kind of culture where auto-formatting is accepted as a matter of course.


Nit: you probably mean "go fmt".

"go fix" is a different tool. (Though "go fix" does also run the formatter.)


Go is actually extremely hard to format automatically:

http://journal.stuffwithstuff.com/2015/09/08/the-hardest-pro...


That article is about Dart, not Go.


Furthermore, the difficulties it mentions are pretty much universal across languages.


Rust is working on this:

    $ rustup component add rustfmt-preview
    $ cargo fmt


(And rustfix for the “go fix” equivalent.)


The problem with the code in question is a problem of alignment, not indentation.

Edit: Ah, no, there are some indentation problems too. But it's the alignment that I first noticed because it's so jarring.


If you open it in an editor, you'll see no indentation problems: https://monosnap.com/direct/cSBUU3ik31upjfdxpc8p9RJjLyVtda

This is a result of using 'elastic tabs' which GitHub is not able to display correctly (and also the reason people will use spaces for alignment in a tab-indented project).


If you open it in an editor and have the same tab width configured as the author, you'll see no indentation and alignment problems. If you set a different tab width (the purported advantage of tabs), the code will be messed up (the actual disadvantage of tabs).

Not sure why you blame this on GitHub. Emacs with a tab width of 2 or vim with a tab width of 8 will also show this code with broken formatting.


Yep. It's not a github problem, it is a problem with the code mixing tabs and spaces for indentation.

It never ceases to amaze me just how many programmers don't understand tabs. I guess it's because they look like spaces Or because every programmer at some point got bitten by the issue but instead of learning how to use tabs correctly learnt that tabs are evil.

The tabs vs spaces war is all just a massive misunderstanding due to some people (the spaces people) not understanding tabs (and not understanding that nobody is advocating using tabs for alignment purposes).


> not understanding that nobody is advocating using tabs for alignment purposes

It's not about advocating, it's about actual use as in the example above. Tab fans get confused about what they are doing and put hard tabs where there shouldn't be any.

If tab users were actually perfectly consistent about using only tabs only for indentation but not alignment, things would be fine. In practice most of them don't understand the issues and even those that do understand them apparently fail to be consistent in practice. Which is not surprising, since tabs are just as invisible as spaces in most environments.

I've said it before, I'll say it again: I've worked on several projects that were halfway through a painful tabs-to-spaces transition period, and they all said "no tabs in new code, never again a project where we allow tabs". I've never had the opposite experience. In practice, as used by actual humans that need to work together, tabs don't work well.


Yeah, I guess that is also my experience and it correlates with what I do. I think that tabs for indentation and spaces for alignment is correct, but in practice I use spaces and have done for most of my career (apart from go).


Tabs polute an otherwise perfectly simple concept, fixed width fonts. If they had't existed out of the gate, no one would have ever considered asking for it, especially if the reason was "I want to be able to pick my own indentation level". They'd be laughed out of the industry. You want the whole world to worry about your prefered indentation level every single time they start a new line? Thats never going happen. Take the indentaion level your given and burden yourself with the task of writting a feature for your editor that lets you pick what you like instead. Oh, and have fun fighting with it every time you open a file you didn't write.


This is easily solved with something like .editorconfig.

I personally use four spaces. What I was trying to point out is that this is not necessarily "bad code standards" from the author. As long as everyone in the project is onboard, and tooling support means the code is perfectly readable, I really don't care if you would use seven poop emojis for indentation.


    uint32_t<3 tabs>pc;
    double<4 tabs>time;
That's not how elastic tabstops work. With elastic tabs you only put one tab between each and every field.

(Besides, not all editors support elastic tabstops, not even with plugins.)


Perl best practices understandably has a problem with this type of tab usage. It only allows for tabs after a newline and before any actual code but not inside code (for alignment).


Wow, yeah, that's why should never change tab width to anything other than 8 unless you're very sure that you've got things set up exactly right tooling-wise.


> also serves as a nice illustration of the fact that if tabs are allowed in source code, people will mix them with spaces

This seems disingenuous, I could just as well point to a piece of code and say "if spaces are allowed in source code, people will mix them with tabs". People should get over themselves and accept some people prefer a single character to specify tabbed-space and other people want the exact number of spaces. I find space characters cumbersome to work with, so I use tabs myself.


In addition, most editors these days should be able to fix this as you go so you can use whatever your preference is and comply with the projects standards.


I'm not sure how the parser and lexer/etc. line count matters for the VM line count. shrug

Definitely something I'll be looking at for my embedded work; it may fill a gap in my toolchain. And boo-hiss tabs.


> And boo-hiss tabs.

At the risk of starting a flame-war: boo-hiss not using a formatter.


Orthogonal. Once you have tabs, any viewing of that file will rely on the tab size set by whatever viewer you're using: mixing tabs and spaces basically never works. At least if it's just tabs everything will be seen wrong consistently.

The only reliable mechanism is spaces-only.

:p


But if you're using a formatter then it doesn't matter whether you use tabs, spaces, minimal whitespace, or any mix thereof because that's what the formatter is for.

Also if you're using a formatter then you can do the correct thing and use tabs for indentation and spaces for alignment, because the formatter will ensure that the output is consistent.


The output is consistent if your tab settings are always the same when you look at your code, and you use the same tab size across all languages.

I view code in multiple ways, have different indentations for different languages (not many different now), and have clients that enforce difference indentation settings. It just doesn't work.


> The output is consistent if your tab settings are always the same when you look at your code, and you use the same tab size across all languages.

I don't really understand what you're getting at here to be honest, surely if you change your tab settings it's because you want the code to look different?

> I view code in multiple ways, have different indentations for different languages (not many different now),

This is exactly what tabs are great for! You can set the tab width based on language without having to force that choice on anyone else.

> clients that enforce difference indentation settings

Again, formatter.


I'm not sure if you're being deliberately obtuse, or just never look at files in only one way. I don't have that luxury.

Say I have a project that has Java and Rhino (JavaScript) files. My IDE/editor is configured for Java with TAB==4, JavaScript TAB==2. Everything looks great in my editor, because it knows things.

Now I'm using `less` or `cat` in my shell. Or ssh-ing to a remote box because of an emergency production issue. Or whatever. Now when I look at the file things no longer line up as originally intended, because when I view them outside of the (relatively) smarter IDE the TAB characters are at the mercy of whatever I am using to view the file.

You cannot simply rely on local tab width settings if files use a mix of tabs and spaces for indentation and alignment: if you change the tab width then alignment breaks. If you never align anything, great, tabs work great for indentation (except for when you can't trivially change it, then they suck). But indentation isn't the only thing tabs are used for.

There's the rub: TAB characters will always be at the mercy of how I'm viewing the file, which I do not always have control over, and certainly not (trivially) based on the type of the file.


> You cannot simply rely on local tab width settings if files use a mix of tabs and spaces for indentation and alignment: if you change the tab width then alignment breaks.

Ah I think you've misunderstood what I meant by tabs for indentation and space for alignment (something which you will be controlling with a formatter because that's the entire premise of my original comment). I'm not sure how to make it more clear, so I'll give you an example.

  class SomeClass {
  [tab]functions some_method(with, arguments) {
  [tab][tab]return a_function_call(with,
  [tab][tab]                       lots,
  [tab][tab]                       and,
  [tab][tab]                       lots,
  [tab][tab]                       of,
  [tab][tab]                       arguments);
  [tab]}
  }
Is that clearer?


Yup! Now view that across multiple languages, viewers, and tab sizes.


Parser and lexer matter for the multipass compiler.


Which isn't the VM, right?


No, it's the compiler. Are you still overlooking the phrase "the multipass compiler code is about 3K lines" in my original post above?


For anyone who considers this interesting and/or looking for alternatives: apart from the well-known Lua there's also MicroPython [1] whos specs actually read a lot like the specs of Gravity but then some more (well, it's Python 3.5 minus a couple of features, also compiles with C99 compilers, is fairly small for being a Python, embeddable, more mature at this point, been tested on more platforms/architectures, ...).

[1] http://micropython.org/ https://github.com/micropython/micropython/


MicroPython is a subset of Python intended for resource-constrained embedded devices, unlike Gravity which is designed to be embedded in applications. This is a significant difference - a while back I considered embedding MicroPython in an application (the Lua/Gravity/Wren scenario), but the Python programmers I discussed this with generally disliked the idea.

It seems those who already know Python want either the whole Python language, or not Python at all. I guess it's easier to learn a whole new language, than to use a language you already know but where some parts are suddenly off-limits?


It seems those who already know Python want either the whole Python language, or not Python at all

Depends on whom you ask I guess. The subset implemented is fairly big imo. Maybe I just got lucky but I never really missed something, and looking at the list of differences [1] that might be because most of them are rather obscure/fancy features which I usually tend to avoid anyway or don't even know exist (multiple inheritance, attributes for functions just to name some) or else can be worked around.

[1] http://docs.micropython.org/en/latest/pyboard/genrst/index.h...


You might not use those features directly, but they're often used in library code. For example, attributes on functions are common technique for function decorators to store some associated data.


Fair point. Porting existing libraries is usually a bit of a struggle. Luckily for the particlar applications we use we get along just fine with the standard library subset, everything else is a mix of custom C++ and Python modules.


Just a nitpick... but I’ve seen people who aren’t working on what i would consider typical embedded recommend eLua and MicroPython readily without ever mentioning the downsides that mPy needs 128k+ ROM and includes a ridiculous standard lib most of which you would never use on embedded, and eLua supports some Cortex m3 and is a dead project. These two you mentioned are more of people using Rasp Pi syyle embedded (Cortex m-series vs Cortex A/R series... small devices vs phone processors).

The issue is everyone has this “great idea” for an embedded language to replace C which means device headers in the Lang, and that’s way too much to support.

If the goal is to replace C imo it’s a waste of time. But there is a market IMO for an embedded lang that you can execute tests and scripts from C in a VM that can foreign function interface back to C functions.

There is on that uses JS called mJS but I found the one of the guys that supports it to be extremely rude and unhelpful. Documention is lacking. You basically need to be an expert in JS to get started. And then they doubled their ROM use age from 23k to ~50k. But it does seem to be the best out there for the goals I stated.

I’m still on the hunt.


It depends upon what's the target you're trying to embed into. If you're trying to put something on a PIC or an 8 bit AVR, you're down to something like assembly, C, or a very low memory language like forth. If you're looking at larger targets like the ESP32, then you can use an embeddable python, ruby, lua, js, etc without being too concerned. On something like a raspberry pi compute time likely ends up being more of a concern than memory or build complexities.

The other target is trying to embed some interpreter in a larger application, which ends up being mostly constrained by how nice the language is as well as how nice the C API barrier ends up being.

C is certainly not going anywhere, but it is pretty cool that other options are being built to lower the barrier of entry for some application domains.


FWIW there's still a pretty large delta from Lua and JS as far as embedding targets go. We use to run our whole game state in a 800kB fixed block on a 8mb system heap in vanilla Lua(pre-LuaJIT) with good success.

I've done some poking at various embedded JS frameworks like Duktape and it's pretty easy to trip on various memory leaks[1] that make it really hard to recommend it as a viable platform for memory constrained spaces.

[1] http://point.davidglasser.net/2013/06/27/surprising-javascri...


One project used ATS for an 8-bitter:

http://metasepi.org/doc/metasepi-icfp2015-arduino-ats.pdf

http://www.ats-lang.org

Chez Scheme used to run on 8-bit platform, too. Then there's Pascal and BASIC dialects for microcontrollers. Astrobe has Oberon dialect for ARM Cortex's. AVR-Ada lets you use Ada with AVR's. OcaPIC put Ocaml subset on PIC microcontrollers.

People are using C because most people already use C with the ecosystem benefits that follow. Although C and assembly are dominant, there are other options available if people think their pro's and con's line up with their needs.


Always cool to see ATS in the wild. Higher-order functions on an 8-bit CPU makes me happy. :D


That's excellent news. ATS is one of the best languages out there. It used to beat C in the shootout benchmarks, and adds much better security and types than C/C++.

I'll try to use in my next embedded job, where they do just C.


There are Basic and Pascal as well, there are a few embedded companies still selling compilers.


I don't see why you'd use MicroPython on a raspi-class device, unless you can't use a OS that runs CPython for for some reason. MicroPython is more for smaller stuff.


If you have 1/2 your code in C and want a Python VM for one reason.

But keep in mind that even MicroPy recommends 256k ROM, which is before your program at all, that excludes nearly all Cortex M series and 16 and 8 bit micros. So that’s what brought me to Pi in the first place.


Check out http://duktape.org. Extremely easy to embed, great performance, and can go down to a ridiculous 1.5KB of RAM at startup.

This and JerryScript (https://github.com/jerryscript-project/jerryscript) are the two most active embedded JS engines.


How is eLua dead? https://github.com/elua/elua

eLua is famously used in the https://en.wikipedia.org/wiki/NodeMCU firmware for the ESP8266, a non-Arm based system.


Have you tried Zig? [1] I tried Rust, but found I made better progress with Zig, and I can see it being a good, incremental replacement for C.

  [1]  https://ziglang.org/


Also, at first glance, Gravity looks a lot like Wren: https://github.com/munificent/wren


Which looks a lot like Potion: https://github.com/perl11/potion

But wren and Potion are much smaller. Wren added a nice work-stealing native-thread scheduler on top of potion.


MRuby[1] is another option in this space. From what I can tell the work for MRuby began around the same time as micropython, though micropython has seen more adoption.

[1] http://github.com/mruby/mruby


I was surprised nobody mentioned mruby in this thread until now. It's tiny, super easy to embed, and Ruby is a fun language.

Also, mruby has the benefit over most of these other languages of having had a recent thorough security audit[1].

1. http://mruby.sh/201703270126.html


It's still quite huge compared to Lua, not to mention slower. And gems need to be enabled at compile time.


mruby has a much higher adoption in Japan, but I don't think it ever really caught on outside of it


Is it possible to embed Gravity in my C/C++ project and safely run untrusted Gravity code?

The Lua implementation supports the creation of sandboxes to run untrusted scripts, but this is a feature that other embeddable languages (e.g. Wren, Python) don't seem to support.

I feel like this would be a basic requirement - or is it common to embed a scripting language without limiting what scripts are able to do?


Tcl is the granddaddy of embeddable scripting languages. It has had safe sub-interpreters for untrusted code for quite a while. They were added back when it had a shot of being a contender against JavaScript and safe script evaluation was deemed necessary.


I think the issue is really just that effective sandboxing is really really hard to pull off right without also severely limiting the capabilities of the language/environment in undesirable ways, even for simple applications.

Given that most of these things are volunteer projects and making strong claims about security is dangerous when you can't back it up, it's a tricky space most projects just don't want to touch.


Agreed, on my searching a VM to run foreign code is limited. You need a system that can run compiled code in order to be efficient, and interpreted is much more common but then you run into FFI issues. As to serialization of an interpreted it’s annoying that on embedded I’d have to send the letters “f” “o” “o” “(“ “x” “)” “;” which obviously kills some of the point here.

mJS does the VM and FFI part OK, but I didn’t like some of the way it handled JS types coming back to C, it wasn’t clear for example that a C defined value passed in would return as a signed JS/NaN until converted.


Tcl supports this, and many systems use it: https://www.tcl.tk/man/tcl8.6/TclCmd/safe.htm


A while back I was interested in learning more about programming compilers, so I checked this project out. I actually added array map, filter, reduce, and sort methods to the language, which was fun and a learning process.


That’s great! You’re the kind of person the hobbyist programming language development community needs. I’d encourage anyone who’s interested in learning more about compilers to see how they can contribute to a new language—it’s a great learning experience, one less thing for the primary author to do (a programming language can be an enormous undertaking), and who knows, you may be helping to build the next big thing.


I did the same thing recently, extending the "monkey" language which is introduced in "Writing An Interpreter In Go".

Now it supports comments, file I/O, string-primitives, regular expression support and similar things:

https://github.com/skx/monkey/

Updating the lexer/parser/evaluator was nice and simple once you understand how the various parts hook together. The downside is that if you want to create a standard-library for your language you need to spend a lot of time on it.


Can anyone suggest a good embedded language with static or at least optional typing ? I feel this is a gap in the market for embedded languages.



Lily looked pretty interesting, discussed here a couple years ago

https://github.com/FascinatedBox/lily https://news.ycombinator.com/item?id=12015158


Dyon https://github.com/PistonDevelopers/dyon

(Interesting and very easy to modify, maybe not "production ready")


Thanks for this!

This is by far the most what the fuck language that I've seen in a long while. It displays an astonishing amount of creativity and novel (at least to me) ideas, most of which are probably bad and won't survive for long, but some of them are really cool! Things like "packed loops"

    for i, j { println(list[i][j]) }
or using `return` as a variable without exiting

    return = 8
Really cool!


> or using `return` as a variable without exiting

Also found in the Nim language (no affiliation/loyalty, just in case anyone is curious to see an existing use of this)

https://nim-lang.org/0.18.0/tut1.html#procedures-result-vari...


Many languages have return variables (e.g. Matlab far predates Nim), but this is the first time I saw the name/keyword return itself be used.


The name is 'result' not 'return'.


I seem to recall something similar in an old language (maybe Pascal?) where the way to return something was to assign it to the name of the function.


Fortran does this (still does in F2008). But usually I end up using subroutines instead, since I want the code in question to modify more than one object. Functions can only return one object, and the overhead of packing/unpacking to a struct is annoying.

What Fortran really needs to get is first-class string handling and saner IO. Apart from that being a pain, modern Fortran is a nice language to use.


Pascal you can set and return the "result" variable in a function without declaring it. You can also use the functions name to do the same


Julia also does loops with two indexes.


I think the point of packed loops is that the compiler/runtime figures out the bounds/ranges for each variable automatically.


I wonder if the lack of strongly statically typed embeddable languages is due to an accident or a fundamental limitation of implementations of such languages. Is the type checker code too big? Is it too complex?


Writing a type checker is conceptually much more specialised than just a scripting language. Certainly I have no idea how to write one!


Moreover, designing the actual type system is at least as hard as implementing the type checker. Coming up with a static type system that is flexible/ergonomic, expressive/powerful, sound, easy to use, and also a good match for mutable state and imperative code is really, really difficult, as demonstrated by C, C++, and Java.

A flexible/ergonomic dynamic type system is trivial in comparison. Soundness is handled at runtime when the exact value of each expression is known, and ease of use is almost a given.


Not really. You just need to walk up both types until you find a match. It's similar to a simple eval matcher, which is about 10 lines in lisp.

https://github.com/perl11/Bla has typically the smallest and easiest to understand type inferencer and checker.


It's actually extremely easy, particularly if you're familiar with Prolog (the core algorithm is a couple lines in Prolog, so knowing how that works is useful for implementing it in other languages).

I suspect the lack of statically typed scripting languages is more of a historical accident than anything.


As a hobbyist I love seeing new languages. I look at their features and a few simple examples and immediately imagine they'll solve all the issues I have faced with other languages.

I generally appreciate the massive amount work that goes into something like this.

But when I want to work on a project, I am wary of using something new. I just know in my heart I'll set the wrong flag on the compiler and the binary will end up in a directory I can't find and it'll take me two days to figure out how to do it.

My question is this: if the main strength of this language is that uses Swift-like syntax, and lets you develop native apps in Android and iOS, why not just make an Android compiler for Swift?


"issues I have faced with other languages"

Which ones, specifically?


I mean that in the generic sense when you grow a project (I use python mostly) and run into issues invariably with the size and complexity.


As an embedded programmer, it is still very hard to displace C and will be for some time. The two main reasons: the compilers are well-behaved, and the debug tools are very, very mature. One thing newcomers overlook is the importance of understanding what the compiler does to the code. C++ is notorious for being painfully unpredictable, and in the embedded world, that is a death sentence. mbed is learning this the hard way. On the other side, debug tools are critical. Unlike the decade long surge of web-related programming with its sophisticated GUI-based debugging, embedded is a completely different beast, and requires much more attention to detail since reprogramming costs are so high. Because C has such a robust hardware debugging ecosystem, it is a clear winner.

It'll be a long time before anything knocks C down as #1 in the embedded space. Even as the definition of embedded stretches to include *nix variants (like Yocto), there will always be the squeeze to get rid of that and run bare metal with C.


I think you're getting confused between embeddable languages (Guile, Lua and kin), and languages for embedded development (you mentioned C).


You are correct, sir.


I’m a little bummed about the class-based bit; I generally want structs/records, not classes. Specifically, I want to avoid inheritance altogether (yes I know I don’t have to use it, but I still have to interact with it when libraries or coworkers use it). Still, this is a neat little scripting language, and best of luck.


On the off chance you're not already acquainted, you might really like Go. There are no classes, thus struct types are the bread and butter. "Methods" are lightweight syntactic sugar combining structs and functions; structs effectively double as classes, but the scope and complexity they offer are perhaps easier to juggle compared to the typical object-oriented language.

The inheritance mechanism, implemented via interfaces, is interesting because it stresses conformance to behavior rather than explicit relationships between types. If your Circle struct type walks and talks like a Shape, then it is implicitly usable as a Shape. It's functionally identical to explicitly defined inheritance between types in other languages, but there's something about the _feel_ of developing with Go's interfaces. You begin to think more about the simple behaviors that each type should exhibit, rather than drawing hard lines in the sand about the hierarchy of relationships. Circles and Squares don't extend a base Shape type. Rather, a Circle can calculate its surface area. A Square can do the same. Conveniently, it just so happens that any type that can calculate its area is promoted to being usable anywhere a Shape is expected.


Yeah, I love that Go uses structs. Go is my favorite language, but it still has lots of rough edges, like no generics or sum types. If I were designing Go2, I might also get rid of interfaces in favor of closures or explicit vtables (but that's a little puritanical and I might change my mind after programming in that style for a while). Still, I get things done faster in Go than I do in many of the languages that I've used professionally, like Python, JS, C, and C++.


>Still, I get things done faster in Go than I do in many of the languages that I've used professionally, like Python, JS, C, and C++.

What's the reason for that, in your opinion?

Do you mean dev time or run time?

If it is dev time, I can understand it about Go vs. C and C++, not sure about JS, but vs. Python?

(I know some Go, but much more of Python.)

I know Go has a fast compiler and is somewhat fast to write and run too, sort of a cross between dev time of Python and run time of C (I've that heard from someone who used it for a startup, and others), but I would have thought Python may have somewhat of an edge (even if small) when it came to dev time, over Go, or at least that they would be about the same, all other things (like dev experience) being equal.

That's just a guess, though. Don't have data to back it up.


Thanks for asking, happy to share. I was referring to dev time, but Go is also much faster than Python at runtime. To give a little context, I've been writing Python since 2007, and professionally for ~5 years. I've been using Go in my hobby time for about ~5 years as well.

I think Go allows me to develop more quickly for a variety of reasons.

First of all, it is statically typed by default. This means that APIs tend to make more sense out of the box. You don't get APIs like Pandas' or matplotlib's or etc where you can pass three strs and an int or two floats and a datetime unless it's a Tuesday. I don't need to spend time pouring over documentation because it's all right there in the type signature, and there are high-quality plugins available for every major text-editor that allow the text editor to show you the function signature (and docs) at the push of a button (this is possible largely because of static typing).

Also, because of static typing, I get faster feedback when I've broken something. Unit tests do a pretty good job of catching things in Python, but I still find a couple of bugs a week in our Python codebase (usually in error paths) that a static analysis tool would have caught.

Now of course Python has a static type checker (mypy), and mypy can catch many of these errors. However, static typing support in Python isn't great. mypy is buggy, many popular libraries like SQLAlchemy simply can't be annotated, simple types like JSON can't be represented (no recursive types), dealing with type variables is miserable, and the documentation is poor. Things are progressing, but progress is slow. I will say that I like that Python at least has typing.Generic and typing.Union; Go has no generics and the idioms for sum types are all painful in one way or another.

Also, because of static typing, the documentation generation support in Go is way ahead of Python. Python basically has sphynx and readthedocs, which takes a pretty miserable syntax and turns it into a giant wall of HTML. Many popular projects are incompletely documented. Just today, I was searching on aiohttp's docs page for the errors documentation. It just doesn't exist, but they definitely have an exception for every HTTP status code. Many times, devs just punt and give you a handful of parameters and maybe their types (even in the stdlib, like `subprocess.run()` which tells you that the params are sorta like `subprocess.popen()`). It's basically impossible to ctrl-f in the docs either because you never know which class's `__str__()` method you're looking at. Also, in our own project, we kept seeing the sphinx docs getting out of date since there was no type checker running on the sphinx annotations (wrong annotations are worse than no annotations). Many times I just have to fire up the REPL and test the method until I figure out what the hell it actually expects for its args and what its concrete return type is, and this is an even bigger pain when the library depends on a connection to some remote database or service.

By contrast, Go has http://godoc.org which generates docs for any public package based on comments (no special syntax!) and types. You can tell at a glance that you're looking at `Foo.String()` instead of `Bar.String()` when you search for `String()` on the page. Even if the developer doesn't document anything, plenty of information is available, and if you ever need more information, each identifier has a link to its definition on Github (or bitbucket, gitlab, etc), so you're never more than a mouseclick from the complete information.

So static typing is a pretty big deal here, but there's also things like async. I'm really happy that async is taking off in Python, but it's still pretty confusing and tedious. I still don't have the whole model in my head (we've only been using it for ~8 months), but it's really annoying that every library that does any I/O at all now needs an async version. Recently I was trying to concurrently build Docker images from a Python script, but the Python Docker API isn't async, so I guess I'll need to fork a process or something. Most popular projects like Docker and AWS have some Joe Developer maintaining an equivalent async library, but that comes with a bunch of additional concerns.

In Go, everything is async by default with a synchronous interface, which is almost exactly the interface I want for async things (although I do prefer Python's `await` to Go's channels and goroutines).

Also, making things fast in Go is a lot easier and more straightforward than in Python. First of all, Go is 10-100X faster than Python, so you don't need to optimize in Go as often as you do in Python. But when you do, the optimizations are usually spelled out to you by the profiler and you can actually fix them (since Go gives you control over things like allocations which allows you to intervene to reduce memory pressure and improve cache performance). Also parallelism; for all intents and purposes, your only parallelism option in Python is multiprocessing, and this is shitty. For most peformance issues, the Python answer is "just use C for your hotpath", but this can actually make things slower for a lot of situations since calling into C typically means converting between Python data structures and C data structures. I had a coworker throw Pandas at a performance problem, but the solution involved running Python on every cell in the dataframe, so we actually lost (10-30X) performance over the naive list-o-dicts solution because of all of the calling across the language barrier. And if you're not using something like Pandas or Numpy, writing C is actually pretty hard for the average Python dev, and writing memory-safe, secure, portable, cross-platform C is hard for lots of above-average C developers. There are definitely cases where "just write it in C" is a reasonable thing to do, but even being able to identify those cases takes a certain amount of expertise that your average Python developer doesn't have.

Lastly, Go compiles everything to a static binary. Python finally got pipenv, which solves some of the same problems, but it's still a pain and every issue seems to take a full afternoon to debug. Go binaries just run out of the box on pretty much any Linux.

I don't want to give the impression that I write Go 10X better than Python; it's probably closer to 1.2-1.5X, and there are some times where Go is the clear loser. Some libraries just don't exist or aren't yet mature in Go, and any sort of magic is far easier in Python (although this is also a misfeature since you get more people adding unnecessary and/or bug-ridden magic into their APIs). Hopefully this wall-o-text answers your question. As always, YMMV.


Thanks a lot for the detailed answer. Some other comments inline below.

>Also, because of static typing, I get faster feedback when I've broken something. Unit tests do a pretty good job of catching things in Python, but I still find a couple of bugs a week in our Python codebase (usually in error paths) that a static analysis tool would have caught.

True, static typing can help a lot, and you get the feedback at compile time, not run time, as you said.

>so you don't need to optimize in Go as often as you do in Python. But when you do, the optimizations are usually spelled out to you by the profiler and you can actually fix them (since Go gives you control over things like allocations which allows you to intervene to reduce memory pressure and improve cache performance).

Interesting, will check that feature out.

>Also parallelism; for all intents and purposes, your only parallelism option in Python is multiprocessing, and this is shitty. For most peformance issues, the Python answer is "just use C for your hotpath", but this can actually make things slower for a lot of situations since calling into C typically means converting between Python data structures and C data structures. I had a coworker throw Pandas at a performance problem, but the solution involved running Python on every cell in the dataframe, so we actually lost (10-30X) performance over the naive list-o-dicts solution because of all of the calling across the language barrier. And if you're not using something like Pandas or Numpy, writing C is actually pretty hard for the average Python dev, and writing memory-safe, secure, portable, cross-platform C is hard for lots of above-average C developers. There are definitely cases where "just write it in C" is a reasonable thing to do, but even being able to identify those cases takes a certain amount of expertise that your average Python developer doesn't have.

Those are good points about issues with calling C from Python.

>Lastly, Go compiles everything to a static binary. Python finally got pipenv, which solves some of the same problems, but it's still a pain and every issue seems to take a full afternoon to debug. Go binaries just run out of the box on pretty much any Linux.

True, and that (binaries from compiled-code languages) was a more common deployment method before dynamic languages like Python, Ruby and Perl became popular. Done a lot of dev work in C earlier.


Yeah, I think dynamic languages improved on C for certain tasks, but the improvements were strong typing and garbage collection. Go and a few other modern languages are adding static typing back into the mix and finding a lot of success in what used to be the purview of Python, Ruby, etc because it allows for better tooling, ergonomics, and performance (in practice, not just theory). It’ll be interesting to see what comes next.


Is there a Go VM for 32bit embedded?


The parent I replied to made me think so strongly of Go that I forgot the context regarding embedded systems. A quick search shows support for ARM; beyond that, I don't know if any options exist.


When a language says ARM like that, they mean the ARM that runs your phone. Not the ARMs that run your Fitbit.


In that context you'd probably be better off with Rust (which takes a similar attitude to inheritance).


try clojure


I’ve looked into it, but the learning curve for lisps seems too high to climb in my spare time. Also I’m not a big fan of VMs—for scripting I prefer a small interpreter and for app dev I prefer self-contained binaries.


Nonsense, you can learn it in your spare time bit by bit, the trick is really to take it in small pieces. If you want something for scripting aka fast startup time, then look no further than Clojurescript. There are two standalone Clojurescript environments that you can use to run clojure scripts. [1] Lumo and [2] Planck. For Clojure compiler that spits binaries there is [3] Ferret, but I haven't tried it yet, so I don't know how much of Clojue is implemented.

[1] https://github.com/anmonteiro/lumo [2] https://github.com/planck-repl/planck [3] https://github.com/nakkaya/ferret


It's not a question of "can I learn it in my spare time", but "is it worth devoting a considerable amount of my free time to learning it" and I'm far from convinced that it is. Maybe I'll pick up bits of lisp here and there and the learning curve will seem small, but in the meanwhile I'm not convinced enough to pursue it intentionally.


>null

>dynamic typing

>naked if-else bodies

I'm sorry, but I just don't see how this improves on anything. How will GravLang make me a better, more productive programmer? How will it make my programs more robust?

Don't get me wrong, as a hobby project it's fine, but as a "real" language it doesn't have any selling points to me.


It doesn't improve much on e.g. JavaScript or Python. That's not the point. The point is that it's a small fast zero-dependency embeddable language that could be used for e.g. scripting in games or small devices.

Wren is another nice one:

https://github.com/munificent/wren


Don't we have already Lua for that? It even has a state-of-the-art JIT compiler.

I am all in for competition, but I can't see how both of these languages could improve over Lua.


Although LuaJIT is crazy fast, it can't be used on iOS, so we first need to compare this with the unJITted version of Lua.

Lua is pretty simple and elegant, but has its woes on being too flexible and wonky (especially with nil and global variables). It also doesn't support OOP syntax directly, so everyone started to create their own wonky class system, which broke easily because of Lua's wonkiness. Gravity seems like a good embedding language packaging a nice OOP syntax with a lot of syntactic sugar (such as properties and access specifiers), so I think some UI/game developers would choose this over Lua.


Of course it can be used on iOS, there are commercial applications that do exactly that. You can turn off the JIT and you're left with probably the fastest interpreter for a dynamic language that currently exists.

LuaJIT is state-of-the-art even without the JIT. A lot of people miss this and go down various rabbit holes only to come up with something that's significantly worse. Personally, given what I just described, I don't see how Gravity compares at all favorably to Lua.


>it can't be used on iOS

LuaJIT interpreter which is _faster_ can be used on iOS. So you would use an immature language everywhere because you can't use the fastest implementation on one platform (iOS), when infact, the LuaJIT interpreter is the fastest interpreter of any dynamic language on all platforms.

Wonkiness is not a substantive claim. How do class systems break because of it?


Magic methods are wonky. Things that get called, although they don't exist, have to traverse an arbitrary system to determine an implementation (may or may not exist). Now add that you can determine that system of traversal, at runtime. Now add arbitrary constraints like 42 local vars per scope, singular inefficient table, no switch (but currying is ok, certainly one of these is more confusing than the other in every CS class) ultimately forcing some wonkiness in every project. It's truly a strange confluence of features leading to an even dumber javascript. I use Lua a lot.


Different strokes I guess.

I consider Lua's type system almost mathematical in its elegance. For my taste, Lua is the penultimate dynamic language, held back only by the unforgivable Wirth-style indexing from 1.

The one place where I simply must correct you is in calling tables inefficient. LuaJIT's implementation, as interpreted, is demonstrably among the fastest general-purpose container classes in widespread use.


I see your point, although I would probably use something like mruby[1] if I wanted something embeddable and OO.

[1] http://mruby.org/


I've also looked at mruby, but the language seemed quite complex. (It claims to have a Ruby compatible syntax, but also has its strange warts.) It doesn't help that its documentation is rather poor. Also by looking at the issues section I was pretty scared about the bugs that might come up while using this (https://github.com/mruby/mruby/issues). If there was any successful usage of it in game development I would have jumped in, but so far I haven't found any.


Can anything interesting be used on iOS?


There are some nice non-JIT embedded languages such as Angelscript (resembles C++, uses refcounting instead of GC), Squirrel (extension of Lua syntax to support classes and other nice features), and recently Wren (has a nice class system and uses fibers for concurrency).


Does Apple still prohibit any form of downloaded code or scripting or did they finally ease that?


They do if the user can download and execute the code themselves. If the underlying app uses it (e.g. React Native app downloads updated bundle) and user can't touch it then it's okay.


Wren looks interesting. It stands out by the lack of use of setjmp/longjmp for error handling. It's probably side effect of being able to return/suspend fiber from any call stack depth.


Haxe is the leader there, besides swift.


Ok, find me a Lua compiler for my ARM Coretx M4 that can spool up its VM on my chip, run my C functions natively, and takes less than 30k ROM.

There is a difference here where 1/2 people something iphone can run, and the other half want something that would run on a small 32bit.

I want a scripting VM with an FFI. Lua while supposed to be able to do that just isn’t supported by anyone for that purpose.


I think you might be served by elua, embedded lua. I haven't used it myself, but it looks pretty active.

https://github.com/elua/elua

P.s. the cortex m4 is awesome!


eLua is dead though. Shot itself by trying to be a C replacement. If an embedded Lang for embedded micro has system headers to allow you to stay in the embedded Lang - it’s not going anywhere.


>naked if-else bodies

For my own interest, do you have examples of the way other languages do it? I'm interested, and but I'm afraid to google that.


In Go, one has to always write the if-else bodies in curly braces (and, the opening brace should always be on the same line). I think this is great for two reasons. Firstly, it prevents bugs where a very tired programmer needs to add something to the else body, which is written like this:

    else
      foo();
and writes

    else
      foo();
      bar();
which compiles, but doesn't do what you think it does. And I have seen this bug in actual projects.

Secondly, along with gofmt, it prevents style wars.


"In Go, one has to always write the if-else bodies in curly braces (and, the opening brace should always be on the same line)."

If you really want to twist the knife... even Perl 5 gets this right. Even Perl 5 thinks that naked if/else bodies is too crazy and error prone.


'even Perl5'? Perl5 has also 'use strict' which ensure that variables ate declared before usage, a feature that Python doesn't have AFAIK.


That is why I got into the habit, very early on, to always, always enclose blocks in curly braces (or begin-end, depending on the language). If it is ingrained deeply enough, one no longer has to think about it.

But it is nice if the language enforces it. I would even go so far as to claim it is common sense.


It has automagic GC!


>Gravity syntax is designed to be familiar to people coming from C-like languages like Javascript, Swift, C++, C# and many more. We started working on this new language a year before Apple announced Swift and we were happily surprised to discovered how similar both syntax appear.

Amusing how the language looks so much like Kotlin - a language released in 2011 and 3 years before Swift.


> syntax is designed to be familiar to people coming from C-like languages like Javascript, Swift, C++, C# and many more

Besides Kotlin, other such modern C-like languages are Java, Dart, Go, and Groovy. But because Gravity is dynamically typed, a better peer language comparison would be Javascript, Groovy, Dart, and Swift. Each of those languages was designed for a specific platform, i.e:

  * Javascript for the web in 1995

  * Apache Groovy for the JVM in 2003 (still used for Gradle build scripts)

  * Dart for the JVM and for Javascript (and more latterly for Fucshia, Android, and iOS) in 2011

  * Swift for iOS in 2014
It's good to see modern dynamically typed languages reject Python's indentation and Ruby's `end` keyword, and settle around curly braces for scoping.


Dart has always had optional static types. With 2.0, those be come mandatory. If you are familiar with Swift, then dart would be Swift with a GC rather than ref counting and without tuples (or rather, the reverse would be true as dart is older than Swift).


Seeing the overall style of the documentation logos and copy, I am kind of surprised that embedding this from Swift is not covered.


Most scripting languages provide only a C interface, because it is what most of the other languages are universally compatible with. C++ codebases can use "extern C", other compiled languages like Rust or Swift can use C interop (for example: https://developer.apple.com/documentation/swift/swift_standa...)


At first look I love how the language is designed.

Now I'm curious about the "embedded" part . Apple doesn't allow any VM or JIT to be embedded for iOS application. Either I'm missing something or else this tech is not compliant with Apple Security Guidelines.


As far as I understand the bytecode is interpreted (not JITed to native instructions), which is afaik OK on iOS.


Apple, always promoting and inspiring new technologies!


Such an underrated project. IMHO if this one was launched by Facebook or Google now things would be different like 15k+ plus stars on GitHub and tech news sites posting articles with titles like "a new game changer language" and stuff.


Does anyone have numbers on how light it is? How much memory Does it need? How big is the library? Does it compare favourably to Lua or Guile? Guile shouldn't be hard to beat for size, but that's generally not what they try to achieve.


"Getting started" link from github readme points to 404.

Edit: so does "language documentation" link.


"It is a class-based concurrent scripting language with a modern Swift like syntax."

So, it looks like every other language designed in the last 40 years? This is good?


In the realm of embedded languages (which is dominated by Lua), Gravity seems like a great improvement.

A lot of game developers attach a small scripting language to their game engines because C++ isn't a good language for rapid game logic prototyping, and takes ages to compile. The majority of them use Lua for this purpose. Lua is certainly simple and elegant, but has its shortcomings (no direct support for classes, 1-based indexing, indexing a wrong field returns nil, can accidentally declare global variables on assignment, lack of syntactic sugar...) I've looked at other languages such as Angelscript, Squirrel, Wren, etc..., to use for game development, but I think Gravity is the cleanest and has the most features so far. I'm still curious about the performance though (If it runs about the same as non-JIT Lua I'll call it a success, given how Lua is much simpler than Gravity)

Even if you aren't a game developer, scripting languages also has its use with UI development. The majority of UI developers use Javascript with a combination of web technologies, so that's one example. Even QT developers use a scripting language called Qt Quick (which also resembles Javascript a bit), because C++ can be too tedious for rapid prototyping. The author of Gravity have also developed this language for mobile UI Development, so it seems good for its intended usage.

The language isn't particularily shining on new concepts, but I think it will be very useful for some of us.


The semantics and capabilities of Gravity mirror those of Lua almost exactly. Class systems, there are literally hundreds to choose from, basic Object/Method support is built into the language. As for globals detection, http://lua-users.org/wiki/DetectingUndefinedVariables and non-existent attribute lookup https://www.lua.org/pil/13.4.4.html

Anyone programing in Lua should be well versed in http://www.luafaq.org/gotchas.html

Gravity's class system is inflexible compared to Lua where it has Multiple Inheritance https://github.com/limadm/lua-oo Prototypical OO https://github.com/nusov/lua-object or anything else you desire.

As for extending syntax and semantics, http://lua-users.org/wiki/MetatableEvents provide a clean mechanism to do so.

Lua coroutines are more powerful than Gravity's fibers.

I was hoping that Gravity had pattern matching support, but it looks like it does not.

Given that Lua has more capabilities than Gravity could you substantiate

> Lua is much simpler than Gravity

In what ways?


You can create elaborate class systems using metatables in Lua; the problem is that there are hundreds of them. I've done some Lua programming using Love2D, and one of the painful parts was that a lot of the major libraries implemented different class systems (or rolled one of their own), making it hard to include in a single codebase. (At least Lisp has CLOS as its common class system...) Also you can mitigate the "undeclared variable = nil" problem using a script like strict.lua (https://github.com/deepmind/strict/blob/master/strict.lua), but when you try to expand that for field accesses you then want to make sure all of those libraries with different class systems or methodologies are also compatible with it (which a lot of the libraries do not).

The underlying problem with Lua is that it went too wild with the philosophy of "being the most expressive using the least amount of features" (so you can implement everything using only metatables). Although many would say that this is a good thing, for me it seems better for a language to have some kind of subset for which people can conform and rely on.


Lua provides only a meta-object protocol, without blessing a single class system.

I prefer this. Its more Scheme-like, philosophically. I've seen your criticism from many quarters and here's my guess at what happens: people come to Lua from other languages, find missing abstractions, recreate them, and in the process don't thoroughly learn the metamethod system.

Pure prototyping is a joy once you realize what all the moving parts are and how many ways they can be usefully combined. This is my experience anyway.


> the problem is that there are hundreds of them

It is a problem when writing general-purpose code in Lua. But why would it be a problem when deciding to embed it, and comparing it to Gravity in this context? With Lua, you'd pick one such system, and stick to it. With Gravity, the system is picked for you - if you don't like it, too bad.


This has been my experience.


>You can create elaborate class systems using metatables in Lua; the problem is that there are hundreds of them.

I have done quite a few Lua projects, and I can tell you've not quite gotten it right.

The Class system you use is the one you take with you on the project. This is a strength, not a weakness; your patterns are a base file in the project.


There are a number of these lightweight, embeddable programming languages - few are particularly innovative.

I have often wondered whether, rather than a new, very orthodox language, it would be more useful to have a lightweight implementation of (a subset of) an existing language?

However, my discussions about MicroPython have shown that many programmers would rather learn a new language than try to restrict themselves to a subset of one they already know. It's likely that programmers who don't know e.g. Python, would also rather learn a brand new language.

So, the number of lightweight, very orthodox languages continues to increase.


You could argue that that is a good thing because some people shy away from unfamiliar syntax, no matter how great the language is. Case in point: Lisp.


anyone using this can illustrate , how they use this ?


It's used for their tech called Creo [1] .

It looks a bit like Fuse Tools [2] , a tech designed for mobile designer that bypass OS Specific UI API to directly draw on the phone using the GPU , sort of similar to Flutter in core architecture.

[1]- https://creolabs.com/ [2]- https://www.fusetools.com/


Sounds not so light-weight.


Not to rain on your parade here but, in what world is a interpreter "fast and efficient"? Embeddable, minimal footprint and zero external dependencies etc are all excellent real selling points here.


Well, it’s not hard to beat Python and Ruby and PHP these days; I wouldn’t be surprised if this were quite a lot faster than those languages (or at least the reference implementations).

EDIT: Do downvoters think I’m wrong? Mine isn’t really a contentious point of view—-Python and Ruby and PHP have a lot of features that make optimization difficult. Python pays my bills, so please don’t interpret my remarks as language smugness.


I didn't downvote you but you waded into a holy war.

The main charge that python and ruby launch at each other is that the other is slow, then it doesn't matter because server costs are cheaper than human costs, then something about time to maintain and add features are the true costs and so on.

So when you mention anything about those languages being slow, and someone just walked out of a four day internet dispute on reddit...


Lol, thanks for the explanation. Usually the Python people just say, "Yeah, Python is slow; just write the hotpath in C", which is somewhat disingenuous but they aren't exactly denying that Python is slow (which is why I assumed my point of view was pretty garden-variety).


We've reverted the title from the submitted “Gravity: a fast and efficient programming language” to what the main page actually claims. Submitters: please don't editorialize.


IIRC, the K language's interpreter is/was really fast because it's tiny and fits in the processor's cache.


A consequence of this is that the language has many severe limits. For example, number of function parameters, number of local variables, amount of code inside a conditional branch etc. Working around these limits can often feel like being a human-compiler.


To be honest I just repeated back stuff I read about K, as I haven't gotten into K enough to hit these limits. Commentary from someone with actual experience is great, thanks!


That is generally true, but it's painfully slow for any operations that aren't vectorised or waiting on I/O.


Is this comment suggesting it is slower than other languages for those operations? If so, which languages? How much slower? Recommended tests?

Is it possible that mandating vectorised solutions and avoiding I/O were in fact design goals of the author?

Not disputing the comment. Looking for more details.


k/q/kdb+ are often touted as having C speed, and the architecture of the interpreter is always cited. In my experience raw performance can be compared to python (or R) for iterative work and numpy for vectorised workloads.

It is an array language and focused on storage and numerical processing of large vector oriented datasets. Its speed as a general programming language is overrated and we often offloaded things to C.

I personally love APL derived languages, but its mystique has fostered a lot of hype.

A simple example, write a for loop with dependent data flow. There is not much optimization.


Blank page with JS disabled.


I don't see how a new language can claim to fast and efficient when it does nothing to encourage data-oriented programming.


So a language that transpiles to C similar to Nim I guess?

Could I use this on Windows and create small binary executables? Any examples of this?


It's a virtual machine running some intermediate representation.

From github: "Gravity has been developed from scratch for the Creo project in order to offer an easy way to write portable code for the iOS and Android platforms. It is written in portable C code that can be compiled on any platform using a C99 compiler. The VM code is about 2K lines long, the multipass compiler code is about 3K lines and the shared code is about 2K lines long. The compiler and virtual machine combined add less than 200KB to the executable on a 64 bit system."


I find in annoying as an embedded engineer that “embedded” now mean phones which are more powerful than the PCs most of used relatively recently.

Embedded to me means 8-32bit micros for real devices. Not PCs that happen to fit in your pocket.

200kB is out for even a fairly serious cortex M4.


I don't think they're referring to the type of device when they say the language is embeddable.

If you wanted to create an app (maybe a text editor) with support for user-defined plugins, that's a good scenario for hosting an interpreter, or embedding it.


Embedded here means "embedded in another program".


Here I am looking for both!




Applications are open for YC Winter 2020

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact

Search: