
Gravity: An embeddable programming language without any external dependencies - creolabs
http://gravity-lang.org/
======
creatornator
I had the pleasure of contributing to Gravity a few years ago. I added builtin
filter, map, reduce, reverse, and sort functions and tests to the array types.
Marco was very receptive to my additions, and it felt very welcoming to
newcomers. I learned a lot about different internal workings of interpreters
making those contributions.

------
astatine
I imagine this would be an alternative where embedded Lua gets used. A
comparison with Lua would have been great to see. The 200K runtime, for
example, indicates that this might weigh in somewhat heavier than Lua.

~~~
theptip
Agreed, Lua seems to be the de-facto standard here, as it's so lightweight to
embed. But the language has some pretty janky rough edges (e.g. only having
Tables and no other data structures) so a modern alternative would be nice.

~~~
tastyminerals
Once you get to know Lua tables and then metatables, you will question the
existence of different data structures :)

~~~
jmiskovic
Yes, Lua tables are incredibly versatile tool. I'm not a fan of global-scope-
by-default and of verbosity, but other than that it's a fantastic language.

~~~
craftinator
I think global by default is one of the reason Lua shines in the embedded
world. Not many other arenas where that is good practice.

~~~
centimeter
Global scope by default isn't good practice in embedded. Global scope isn't
synonymous with static allocation, but unfortunately that goes over the head
of many people. Best practice in embedded is to have locally scoped statically
allocated data passed around by reference.

~~~
craftinator
It really depends on the application. For smaller, more targetted embedded
applications, global scope is fine. If every function needs to have access to
a common set of variables, there's really no need to be passing references
around.

~~~
centimeter
Even if the application is tiny, that's no excuse. It makes it even easier to
chuck everything into a single struct type. It's not like there's any
performance gain to having globally scoped stuff on a modern compiler compared
to passing references.

One reason using global variables is bad practice is it makes testing harder.
An unfortunately high percentage of embedded software doesn't have any sort of
harness-based testing because it's written with globals spammed everywhere,
which prevents you from using any kind of principled testing strategy where
you mock out all the hardware dependencies. It's especially bad if there's
globally defined MMIO stuff like "#DEFINE CCR1A _(uint64_t_ )0x74FEA10". Good
luck testing that!

~~~
jmiskovic
Smaller embedded targets don't have modern C++ compilers. Also many engineers
want to solve domain problems instead of dealing with C++ related problems.

In domain of C, passing by reference means passing pointer. If you chuck
everything into single struct and pass by pointer, it has same problems as
global scope.

Not that I'm advocating for global variables. Even tiny projects tend to grow
with time, and localizing scope across the code base is not fun at all. In
context of Lua, I've just trained myself to prefix variables with 'local' and
I don't give it much thought.

~~~
centimeter
> If you chuck everything into single struct and pass by pointer, it has same
> problems as global scope.

Not true. In fact, this refactor is one of the best things you can do to
improve an old shitty embedded C codebase. Among other benefits, it allows you
to have multiple instances (an arbitrary and easily-adjusted number, in fact)
of a system sharing the same memory, reduces the complexity of linker-related
BS, and simplifies testing. It's _vastly_ better than relying on horrendous C
cross-module scoping rules for sharing.

~~~
jmiskovic
To me the main problem of global is that any module can mutate and affect any
other part. All encapsulation and modularisation is then leaky and you are
always on your toes about implementation details in some other part of code.
Your approach does not attempt to solve this downside of globals.

I agree that passing in structs is vastly better than communicating over
globals. On the other hand, taking existing code base and implementing this
state-passing is quite large undertaking that affects all function
declarations/implementations. It might be beneficial, but there are often
better investments of your time.

------
huhtenberg
Slightly off-topic, but that's one beautifully done website.

~~~
yencabulator
The full-page pointless obstruction you have to scroll past to see anything
useful is the opposite of beautiful to me.

~~~
CGamesPlay
To be completely honest with you, I didn't even realize I could scroll. That
full-page landing page did have what I wanted, a single-sentence summary and a
"get started" button to take me to the documentation, which loaded
impressively quickly. I just found out that it was because it was on the same
page.

------
dang
See also from 2018:
[https://news.ycombinator.com/item?id=17373545](https://news.ycombinator.com/item?id=17373545)

------
ivoras
> 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.

... so, now we CAN have scriptable / interpreted code in iOS?

~~~
ricardobeat
You always could, any of the thousands of React Native, Flutter or
NativeScript apps are running a VM. What you can't do is run code that the
_user_ provides, or use it to sidestep app store updates (with plenty of
exceptions for both rules).

~~~
klodolph
You also can’t mprotect PROT_EXEC, which means no JIT.

------
a-nikolaev
The documentation says:

> Closures are self-contained blocks of functionality that can be passed
> around and used in your code. Closures can capture and store references to
> any constants and variables from the context in which they are defined.
> Closures can be nested and can be anonymous (without a name)

I think, the term "closure" gains some unnecessary semantic meaning of a block
of code / anonymous function. I might be wrong, but It is better to think
about it as simply a technique for implementing lexical scoping.

In other words, "lexical scoping" is a property of a language, while "closure"
is only an implementation detail to make lexical scoping work. So the term
closure does not have to leak in the description and semantics of the language
itself. What is your opinion?

Edit: I just think that such proliferation of terminology confuses people,
making them ask questions like: "What is the difference between 1) function,
2) anonymous function, 3) lambda function, 4) closure?" Instead, focusing on
the idea of a function (possibly without a name) + lexical scoping clarifies
everything immediately.

~~~
traes
Closures aren't necessary for lexical scoping, and in practice they're only
real use (in imperative languages, obviously quite different in e.g. Haskell)
is to pass around blocks of code combined with captured variables. I think
this definition is quite reasonable.

~~~
a-nikolaev
But such a closure block is a function (it might be sugared like a ruby block
or something like that, but it remains a function that captures its
environment, thus implementing lexical scoping).

Well, okay, if a language does not normally have lexical scoping, but has a
special "closure" feature to implement it (dunno, maybe C++ lambdas may be
regarded as such a special closure construct?) That could be a justification
for the term closure on its own. But if the language has lexical scoping by
default (e.g. Gravity says that it has lexical scoping in its overview), then,
I think, there is no need in a separate notion of "closure" in the language
semantics.

~~~
codr7
Lexical scoping doesn't necessarily include lexically scoped function
definitions.

Lambdas have been spreading lately, but plain old functions are still not
lexically scoped in a lot of languages otherwise regarded as such,
C/C++/Java/Pascal etc.

~~~
a-nikolaev
What do you mean by lexically scoped functions? A nested function?

My point is that instead of reusing the term closure, one could use the term
anonymous function. To rephrase my question: Do you have a specific example
where a nested function (or an anonymous function) is not lexically scoped (In
a language that is otherwise lexically scoped)?

~~~
a-nikolaev
Actually, I know Ruby messes up its functions this way:

    
    
        def f()
           x = 1
           def g()
             return x
           end
           g()
         end
        puts f()
    
        undefined local variable or method `x' for main:Object
        (repl):6:in `g'
    

So, the nested function g() cannot capture the scope. I think they had some
justification for this funny behavior, because def is not a function, but
actually a method (I could be wrong). But in any case, there are examples of
languages that don't allow nested functions to capture environment (while a
proc or block would capture it).

------
nurettin
>Features >multipass compiler

irony is that this used to be a negative thing back in pascal days.

~~~
baq
I used to mock Pascal. I feel mildly ashamed of this now. Turns out Pascal was
simply a bit ahead of its time language-wise.

------
mkchoi212
After having looked at it for a minute, it looks like a very simplified
version of Swift. Looks great! Might try out the language on my Raspberry Pi
and see how fast the thing is :p

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

------
thickbiscuit
How do you import a library?

~~~
thickbiscuit
Apparently you can’t. I guess they’re serious when they say “no external
dependencies.” On the plus side there’s no need for a package manager.

------
fra
Has anybody tried compiling this for an embedded target (e.g. Cortex-M4, arm-
none-eabi)?

------
ctas
Did anyone use Gravity before and make a comment about the ecosystem /
community growth perspective?

~~~
creatornator
I never really used it, but I did make some contributions to the language. In
looking through some of the ecosystem, it seemed like the library ecosystem
left a bit to be desired, but the standard library does show promise. I think
its extensibility (proven by my ability as a noob to make useful stdlib
contributions) and use cases in embedded applications mitigates the lacking
ecosystem somewhat.

------
vmchale
Cool stuff! Thanks for sharing.

------
Koshkin
I am disappointed to see yet another language suffer from the same old
semicolon cancer.

~~~
ctas
Calling it cancer is a bit hard. But I agree that semicolons are likely
unnecessary.

~~~
carapace
(It's a play on "colon cancer", eh?)

------
blacksqr
The first thing I want to know about a new language is its performance. Then I
can start thinking meaningfully about the tradeoffs of using it compared to
other languages.

~~~
flohofwoe
For an embeddable language performance probably isn't the most important
property for typical use cases.

Ease of integration, interopability with the "host" language, the embedded
size of the scripting engine or compiler etc..., all might be more important.

------
gutino
The fact that it sets null to uninitialized variables is mindblowing for lang
created within the past 5 years. We all have learned that there "null" is
there just to create bugs.

Please Consider creating a Gravity 2.0 with this fixed as a compile error, you
will save a lot of debugging time to your users.

~~~
skrebbel
Pure FP isn't the only acceptable programming paradigm, please stop pretending
that it is.

(For context: a language without null pretty much implies an Option type,
which is mostly useless (or no better than null itself) without static typing.
Once you're that far you're probably gonna want to get monads to make them
workable and congratulations you're reinventing Haskell)

~~~
enricozb
Since when does static typing imply monads...

~~~
choeger
Actually, it does. If you want a sound and complete static type system for a
practical language, you end up with monads (or something very close to it,
like streams or effects) or you cannot type your side effects (and then your
type system is either incomplete or your language impractical).

~~~
Hercuros
What does it mean for a type system to be “complete”? Type soundness means
that well-typed programs do not “get stuck”, but it is not obvious at all (at
least to me) what it means for a type system to be complete, formally
speaking.

If you just mean that a program has certain properties (like performing IO)
that are not expressed in its type, then I can see what you mean. But even in
Haskell, some properties like “this function might fail to terminate or throw
an exception” are not expressed in the type (although you can use monads for
those things in languages where all functions terminate by default). There are
no distinctions between a function that performs network accesses and one that
does not, or functions that never return odd numbers, etc. There are
infinitely many program properties that you can come up with which are not
expressible in Haskell types.

Even dependently typed languages with very expressive type systems cannot
capture all possible program properties due to logical incompleteness results.

~~~
choeger
A type system is complete when every error-free program is well-typed. This
is, afaik, seldom the case.

I was using the term more informally. In fact it is the operational semantics
of your hypothetical language that is not complete in the sense that you will
have a hard time putting it into a form that allows you to do anything
meaningful (e.g. proof absence of certain errors) with it.

Interestingly, you _can_ define such a language with side effects (think ML),
by actually pulling the state monad into your meta-level (for instance by
turning your reduction relation into an instance of a state monad).

