Hacker News new | past | comments | ask | show | jobs | submit login
Bog – small, strongly typed, embeddable language (github.com/vexu)
189 points by tsujp on Jan 29, 2023 | hide | past | favorite | 50 comments



Somewhat related is yesterday's post about the Cyber programming language, also built on Zig and also embeddable: https://news.ycombinator.com/item?id=34553236

(Not affiliated with either, just can be easy to miss stories on here sometimes, and I figured anyone interesting in Bog might also be interested in Cyber)


Looking at the two of them, both awesome projects, not a competition but here are a few things I noticed. Cyber seems to have pretty good documentation [0] (maybe Bog does too but I didn't find too much from the readme. For example, you can see Bog has a GC and its standard library supports JSON, but memory management and non-scalar data structures aren't mentioned in the Bog readme).

Cyber also seems to be under more active development at the moment.

https://github.com/vexu/bog/graphs/contributors

https://github.com/fubark/cyber/graphs/contributors

[0] https://github.com/fubark/cyber/blob/master/docs/docs.md


Pretty interesting. I'm a professional user of Lua, and while I love it, I really get frustrated at a lot of aspects of it, especially how loose the typing is. I've long wished there were more strongly typed alternatives, but just nothing compares to the performance, stability, and quality of documentation of Lua.

I'm not sure how I would embed this in a non-Zig project, though.


Have you heard of Teal (https://github.com/teal-language/tl)? It's like typescript for Lua made by Hisham. It also has the concept of declaration files so you can import (and type) existing Lua modules if needed.


Teal only type checks when run from the command line though, which isn't the best when you want to embed Lua. At least thats what my reading of the docs says, I'd be happy if I'm wrong. But reading the docs now, it doesn't seem to say that any more - maybe its changed recently?

Edit: Oh yes it's still like that 'When loading and running the Teal module from Lua, there is no type checking! Type checking will only happen when you run tl check or load a program with tl run.' from https://github.com/teal-language/tl/blob/master/docs/tutoria...


I haven't, but I might have to do a lot of work to get it working, as my typical use of Lua is deeply embedded and customized, and doesn't allow any filesystem access. I will definitely look into that, though, thank you. The only real Lua dialect I'm familiar with in that area is Moonscript.


There's many different approaches to typed Lua:

- https://luau-lang.org (interpreted / sandboxed)

- https://terralang.org (JIT interpreted)

- https://nelua.io (Lua -> C)

- https://typescripttolua.github.io/ (TS -> Lua)

- https://github.com/teal-language/tl (Teal -> Lua)

- https://github.com/sumneko/lua-language-server (IDE only typing)

With minimum effort you can get a lot of benefit from using Sumneko's Lua language server.


Terra and Nelua are both very different in goals than Teal. Teal is literally gradual types integrated into Lua keeping as many of Lua's idioms as possible (to a fault[1]). Terra and Nelua are both very metaprogrammable systems programming languages. Nelua's goals are primarily to soften C's rough edges, comparable to something like Nim.

There's another one you missed in Pallene[2]. But again, it's goal was to optimize the stack sharing involved in using the C API. It also adds types though and maintains Lua idioms as much as possible.

[1]: https://github.com/teal-language/tl/discussions/339

[2]: https://github.com/pallene-lang/pallene


There is Roblox's typed variant

https://luau-lang.org/


> more strongly typed alternatives

See also this list for comparison https://github.com/fubark/cyber/issues/6, if you dont want lua by a meta-compiler.

If you want non-allocating scripting for optimal performance (and io limited to what you provide as buffer), go for https://dascript.org/. I dont think there are currently other projects with that performance (+ also inspired by Zig). Though, not sure how clunky it is to use.


Curious, if you don't mind, what kind of professional usage with Lua you work on? I was working on some Nginx/Openresty scripting with Lua module to implement kind of web edge service and really liked it.


We use a heavily stripped and sandboxed Lua interpreter for user scripts in a monitoring system. The initial approach was going to be WebAssembly, but that didn't pan out for a couple reasons.

Much of the functionally has been wrapped or customized to disallow any system access that doesn't go through an access control layer, including for require, and disallowing catching certain errors with pcall and through coroutines. Anything that could feasibly break the sandbox is also removed, like the debug package, and the ability to load bytecode (text sources only). The actual Lua sources are 100% vanilla, though we complete it with C++ to be compatible with the rest of the codebase.


There’s a C embed example in the examples folder


Nice, thanks. Looks pretty reasonable, but I'd really have to learn some zig myself to be fully comfortable using it, I think.


Thank you for posting this.

> let {print} = import "std.io"

An idea obvious in hindsight that I hadn't thought before - if import returns an environment and let can destructure, then a pattern match can bind an explicit subset of that import. Something like:

`(let {print read} (import io) (print (read)))`

Further down I find essentially that example (with input instead of read) and destructuring assignment within function arguments:

`let add = fn ((a,b)) a + b`

though the grammar suggests that {} is initialisation and not a map/dict/hash/assoc/environment style thing, so you probably don't have `fn ({key value}) value + 4` or similar for taking maps apart as they are passed to a function. Thus I think import is special cased.

The bytecode layer knows what maps are though, so maybe they're just missing from the syntax


Funny, I find this syntax very obtuse: it looks to me like "print" is being defined as, well, the print function, through a very roundabout name collision. What happens if there is a typo and the code says: let {ptint} =... What error message comes up?


"Obtuse" is maybe not the word you were looking for. Opaque perhaps?

This kind of destructuring syntax is common in many newish languages -- Rust, Clojure, newer flavors of JS, Typescript, etc -- and becomes second nature pretty quickly. What you're seeing is really two things: first that you can pull apart a map as part of assignment, and second that there's sugar to name the variable after the map key.

In JS, you could write:

     const { foo: bar } = something
     
And that's sugar for

     const bar = something.foo
At the same time, JS many other languages have pervasive support for the idea that often you want the same name for a variable as the map key where you're going to use it. So, e.g.

     const mapOfFoo = { foo }
is equivalent to

     const mapOfFoo = { foo: foo }
Combining these things, you get:

    const { foo } = something
Equivalent to

    const foo = something.foo
i.e. the variable/property auto-aliasing works in the destructure too. When you consider that you are importing a big object with a bunch of fields and functions hung off of it, it becomes natural for that to work the same way:

    import { foo } from "someplace"
I'd argue that it's not actually opaque, let alone obtuse; it's merely unfamiliar because it's (I'm guessing) a combination of unfamiliar syntactical motifs. But as you get more familiar with that kind of syntax, you'll find it natural, and would be surprised if, say, it worked for regular objects but not in imports. When I picked up Rust, for example, I found myself right at home with it (same for the pattern-matching assignment stuff, which feels very similar to Clojure's).

As for errors, you'd get the same error as in the desugared version: ptint is not a property on the object it's pulling apart.


Well I can't test it in bog, but TypeScript allows exactly this syntax and it will give you an error like "Property ptint does not exist on type ..."

One would imagine that

  let {print} = import "std.io"
is equivalent to

  let print = (import "std.io").print


It's not roundabout. Namespaces in many languages are simply dictionaries/mappings of strings to objects, with the restriction that the strings must conform to a variable name standard.

There's not much difference between `obj.attr` and `obj['attr']`.


This is bog standard javascript these days.


Something I hate about about c is that you don't know where the hell somefunc() came from.

Some languages have foo.somefunc() which I think works quite well although iffooisrealllylong.function() is a risk.

I suppose this gets you the best of both worlds.

Although using let does seem a bit weird to me. It seems conceptually misleading.


> Something I hate about about c is that you don't know where the hell somefunc() came from.

I think you mean something you hate about not using an IDE with the language. One can do `from onoz import *` in python and Java to have a similar "wait, where did do_magic() come from?" experience, and then Scala will see your wildcard import and raise you implicit methods, which can die in a raging fire tornado


>I think you mean something you hate about not using an IDE with the language

Well it's a 'feature' of the language so the criticism still stands. Ides could solve a lot of language warts but we still rightfully criticise the language for the warts. C's lack of memory safety could be solved by good programming practices, but we still criticise c for the lack of memory safety.


This is true that there is no language feature to enforce this. But this can be worked around by enforcing naming functions with adequate prefixes, which is a very common approach, if not a best practice.


Isn't this exactly what CommonJS does/did?


No, you're thinking of `destructuring`. CommonJS was "just" a common format for defining/importing modules in a NodeJS environment (via `module.exports` and `require`), eventually being used for packaging frontend "applications" as well, popularized by Browserify.

To expand, the following was common before destructuring but with CommonJS:

    var some_func= require('some_module').some_func
Which, as you know, would probably usually be done like this today, where we have destructuring:

    let {some_func} = require('some_module')


This Syntax has the flaw that autocomplete cannot assist you before the from import clause has been added...


So this is from my particular bias, but any language that touts itself as embedded, I really want to understand it's memory allocation model and approach! I see it's built on Zig, but does it support custom allocators within Bog itself? I'd love to use embeddable languages like this in embedded firmware, but a lot of them have issues with heap fragmentation when actually used in-anger, I've found.

Aside from my biased nitpick, it looks pretty neat!


Interesting to see so many languages implemented in Zig pop up suddenly.


Thought this was statically typed for a moment... Shame.

I don't think you need to call out strong typing. Who would make a weakly typed language in 2023?


> Who would make a weakly typed language in 2023

Interactive programming and programming on data (e.g. SQL) both make sense to have weak typing to me.

But yeah I did misread strong typing in this as static typing. Thanks for calling that out.


Can match return value? It is not clear from the example since it has many duplicated print functions instead.


I am glad to see coroutines.

How are coroutines scheduled?

Can suspend return a value?


Love how this language is explained with code only.


That's actually something that struck me negatively (about the README, not about the language).

Sample code is great, but I'd be happier with a README that says something about the language (and, to a lesser extent, about its implementation).

Apparently Bog is based on Zig, but I didn't learn that until after reading about 80% of the README -- and since I'm not particularly familiar with Zig, I don't quite know what to do with that information.

Probably the author had a narrower audience in mind when writing the README.


Is it just me or is that a terrible “hello world”? I get that they’re trying to illustrate a few features but the whole point of hello world is that it is the absolute most simple program. Why put the string world into a variable named world just to show that you can insert a variable into a string? It looks so confusing.


I read that as "Yes, Bog does formatted strings", and appreciate the density.


The purpose of a Hello World, however, is not to showcase language features, it’s to provide a minimal program that produces output, to test that your dev environment is working properly, and for you to learn how to operate it. Adding any unnecessary complications to it only adds further sources of error.


Do you think real people will get confused and think the only way to write hello world in bog is using a variable ?

Or do you think most people will automatically adjust and think "hello world in itself doesn't show much, so the author decided to include something more informative and keep call that hello world" ?


I think he shouldn‘t have called it a Hello World if its purpose is to showcase a language feature instead of serving as an actual Hello World.


Obviously the examples in a README for a programming language are going to showcase said language's features.


What is the performance of this language? Does its performance resemble that of any popular language?


Do they offer it in multiple versions, the most common one being called "standard"?


Neat!

Looks like a LuPyrby language.


It's a pity people don't know the history of the comma in python syntax, and thus think it's totally unnecessary (in a CS sense it is)...


You mean the colon?


ugh, yes, I mean the colon


Is it my imagination or is the most embedded language in the world C# these days? Which doesn’t really look anything like a classic language designed for embedding.


Embed the whole .NET runtime? Huh?


People literally do that all the time! Godot does it, Unity does it...




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: