238 comments

[ 0.21 ms ] story [ 58.7 ms ] thread
Lua was on my very short list of languages to learn. Will need to revisit that.

Also worth noting, NetBSD has had lua hooks into their kernel for about 10 years I think.

You can learn Lua in a weekend, easy. The language is ridiculously small, but also ridiculously powerful. You can learn how to use C libraries in Lua in another weekend, easy. Using Lua from C is also easy, one weekend or less.
Just curious: Can you share your list of languages to learn?
Lua's presence is honestly hugely because it is so easy to embed in other programs. If you needed some scripting capabilities, Lua was the way. Whether it's a webserver (nginx) or editor (vim) or a window manager (awesome), Lua was the easiest & best at hand option to integrate.

There is a huge opportunity, IMO, for more players here. Deno definitely has some good gestures toward a this. Wasm though seems like the likely general heir, and will have many different offerings for how to do that (Deno being one!).

> hugely because it is so easy to embed in other programs

Not only; Lua is also an example of excellent language design: a minimum of concepts for a maximum of expressability; it's minimal, complete and elegant.

> Deno definitely has some good gestures toward a this

Lua is much leaner, only ~100k machine code. WAMR looks like a better candidate than Deno, but concerning performance and simplicity it has still to catch up.

Forth and Lisp/Scheme and TCL are in the same league. However, they are more alien to C programmers, I assume.
TCL, Forth and Lisp are languages with minimum possible syntax, delegating all higher-level concepts to runtime structures and conventions (e.g. which list position means what), whereas Lua for its syntax and semantics inherited, pruned and elegantly integrated the best features of Modula-2 and other languages. It is true that Lua is more familiar to C developers than the mentioned languages, since both Lua and C look back to the ALGOL ancestry; but Lua - unlike C - clearly follows the Pascal heritage.
I've just used `cloc`-ed Lua repo's entire code base, excluding makefile's and markdown's: 33619 lines in total of C, Lua, and C / C++ Header code; very lightweight code base.
Lua 5.1 has 11'081 SLOC in 32 .c and 1'506 SLOC in 23 .h files (without tests and examples).
And that includes the test suite! Over 1/3 of that is inside the tests directory.
There's a single C file you can compile really easily with `gcc -o lua luaall.c`. It compiles the whole thing in a couple of seconds!
You meant to say `onelua.c` file; indeed it's a fascinating language, I have to admit.
I think a "competitor" to Lua would be Guile [1], but I am not sure if it gets close to Lua in terms of lightweightness... it was designed to be used in the GNU project, with similar objects as Lua: to be light, easily embeddable. It's a Scheme (Lisp) so maybe not for everyone's taste... its "coolest" use i know of is for configuring Guix [2] (the GNU version of Nix).

[1] https://www.gnu.org/software/guile/

[2] https://guix.gnu.org

The competitors in practice were REXX and TCL. All three provide embeddable interpreters as libraries, and easy ways to extend what is available in scripts with custom domain-specific functions that one could call from them.
It's not just that it's easy to embed but it's also tiny as well, I've run it on many embedded platforms. It wasn't until quickjs came along that there was anything even in the same ballpark.

When you factor in LuaJIT[1] and the incredible performance/zero-overhead-FFI[2] it really is a neat language.

[1] https://luajit.org/

[2] https://github.com/dyu/ffi-overhead

Janet is another language that's just trivial to embed, but it's newer so it has less traction.
Really enjoying Janet, but it has some sad and seemingly unnecessary syntax differences to the rest of the lisps and lisp-likes.
I have ignored it because quasiquote is `;’ and that just pisses me off. I should not have such a strong reaction, but I do.
Yes, we shouldn't, but sometimes superficial features can be quite important.

I've ignored CL because it seems like the BASIC or bloated kitchen-sink of lisp/likes, but probably mainly because 'car'? 'cdr'? 'setq'? seriously?

But that's just how Lisp works. Emacs Lisp (not CL) uses those too. It's Scheme that decided to rename car and cdr (and by doing so obscure how things are implemented underneath).
Scheme renamed them? When did that happen? R5RS? R6RS? R7RS? IEEE Scheme?
Okay, you're right. They are there as well. And actually the "first" and "rest" functions aren't even standard but are just there in some implementations as synonyms.
so it actually is like this: Scheme kept car and cdr.

Common Lisp added FIRST, REST, SECOND, THIRD, ..., TENTH and some others to the language standard in the early 80s.

lol, as I said, 'kitchen sink'. And then there's all that business with 'caar' 'caaddr', or whatever. I mean, just no.
Lisp is old. Thus software from several decades back can run.
Sure, but that's no reason to keep carrying past mistakes.

I don't understand why languages don't have a 'version' id as the very first thing, and then you can run appropriate interpreter/compiler given the version.

New versions of the language can drop historical mistakes.

You could kick-start the process in any existing language by making a next release that requires a version tag (and which maybe strips out a ton of legacy cruft). It would then default to a 'legacy' interpretation if the tag isn't present.

> I don't understand why languages don't have a 'version' id as the very first thing, and then you can run appropriate interpreter/compiler given the version.

Common Lisp may have that. My Symbolics Lisp Machine runs six different Common Lisp variants and additionally ZetaLisp side by side in one system.

There are often feature symbols which note language versions: CLtL1, CLtL2, ANSI90, ANSI-CL. It also has packages, where LISP will mean symbol table with CLtL1 symbols and COMMON-LISP will mean a symbol table with CLtL2 symbols.

> New versions of the language can drop historical mistakes.

Lisp may not work that way:

Lisp is not just a language, it is a running system. One can load many different libraries and programs into one running Lisp. Some older code will use CAR/CDR and some newer code won't. It still runs side by side in the same ONE environment and will call each other.

I can for example say:

#+ansi-cl (print "this is code for an ANSI CL compatible Common Lisp")

#-ansi-cl (error "this is Common Lisp does not support ANSI CL")

This will check if the Common Lisp has the features ANSI-CL.

Since cons cells and lists made of cons cells are so fundamental to Lisp, that it would be painful to support software where such a core data structure would be changed (for example by renaming the core operators).

Again, code from several decades with different language variants can be running in one Lisp.

But they're nonsensical.

I understand the history; some to-do with registers, but the names make no sense conceptually.

In TXR Lisp, I made ^ quasiquote. That wasn't some run-to choice or anything; there is a history behind it. Before there as a TXR Lisp, only TXR, backquote was used for interpolated strings: `@foo @bar`, so the character was not available for quasiquoting.

In hindsight, ^ is nice because it stands out. I might have been influenced by ^ptr syntax from Pascal for dereferencing a pointer. Or exponentiation in some languages x^2. Hat denotes "power" and quasiquote is power!

(For a while I experimented with an idea of making regular quote ' somehow intelligent so it could do quasiquoting when unquotes are present. The semantic restrictions are too severe though and cannot be fixed: e.g. you can't do the equivalent ^(foo '(bar ,unquote-here)) with '(foo '(bar ,unquote-here)) where the unquote isn't going to the quote that we want.)

Because @ is significant, I had to turn ,@ into ,*. That's not too bad because * already denotes repetitions in regex: zero or more. And splicing brings in zero or more, so ...

The only issue is that if you want to interpolate a special variable, you have to write , *foo* and not ,*foo, because the latter actually means , foo*.

Common Lisp has the same issue with @, but @ isn't a popular symbol prefix, so it rarely comes up:

  [1]> (defvar @foo@ '(1 2 3))
  @FOO@
  [2]> `'(,@foo@)

  *** - SYSTEM::READ-EVAL-PRINT: variable FOO@ has no value
  [...]


  [4]> `'(, @foo@)
  '((1 2 3))
None of these options lift the `;` into something I have to parse as code, not documentation, so you’re all good!

:)

Ugh. I meant `;` is unquote-splicing, which is an operator within a quasiquote. Sorry for my brain fart.
> webserver (nginx)

How timely! Right now I'm learning Lapis [1] by building a toy CRUD app that I plan on deploying to fly.io. Lua reminds me of Go insofar as it's a really "dumb" language. You can't get too cute and there are a few powerful abstractions that let you accomplish a lot. Coming from Python (which is an endless horizon of PEPs and build artifact standards and name mangling and kwargs and... well, you get the idea) it's kind of a breath of fresh air.

As for 1-based indexing: it makes more sense. We're all just used to 0-based because that's how C worked. If you stop and think about it early C was really just a veneer on top of assembly and since addressing an array in memory meant having a pointer to the first element that meant that it was arr[0]. We have no need for this decades later.

[1] https://leafo.net/lapis/

Years ago I wrote a DNS-over-HTTPS proxy as an nginx / OpenResty module in Lua. The Lua code included a very stripped-down DNS message parser. The 1-based indexes and closed (instead of half-open) intervals were a massive pain in the arse. The code has a tricky mixture of odd and even offsets that would all have been even in C, and it would have been much easier to replace the magic numbers with named constants.

https://github.com/fanf2/doh101/blob/master/roles/doh101/fil...

Oh you can totally get too cute with metatables. I've seen inheritance/straight-OO(Java/C++ style), component based design, and eventing/message based approaches all with the core primitives that Lua provides.

I think my favorite one was one that used yield[1] with the parameters being the number of frames to resume the execution of the coroutine. That was used by a number of our game designers to write AI scripting that felt close to literate programming(I.E. "move x, look for player, yield 30, if found player, go here, etc). You could build a whole AI state machine that fit into one page of code and was very easy to follow.

[1] https://www.lua.org/manual/5.1/manual.html#pdf-coroutine.yie...

> There is a huge opportunity, IMO, for more players here.

There are quite a few embeddable scripting languages [1]. I think these days it's less common to embed a language mostly because there are good high-level languages that applications can be predominantly written in.

[1] https://github.com/dbohdan/embedded-scripting-languages

I've honestly not found it easy to embed as-is, at least in C++. Getting functions into Lua is a challenge. Sol2 makes life soooo much easier for it.
Getting functions into Lua is my favorite use of macros in c++.
Easy embedding is one of the goals of FixScript [1] (my language). It consists of a single C file (and a header). It even includes a JIT while not being gigantic. It also provides a more direct C API which I think is more friendly for embedding.

It has strong support for backward and forward compatibility, a great feature for embedding usage to not make your scripts obsolete and generally working in any version of the host application.

[1] https://www.fixscript.org/blog/introduction

Its also the go-to language for lot of games when it comes to modding, or scripting things like custom missions or scenarios.
> Wasm though seems like the likely general heir, and will have many different offerings for how to do that (Deno being one!).

I was recently blown away by some ideas that StackBlitz [0] apply based on WebContainers. The idea of a "server in the browser", they allow you to run Node-based environment like that via Wasm.

[0] https://stackblitz.com/

[1] https://webcontainers.io/

For many purposes, Starlark[1] could be used as an alternative to Lua. It's also designed to be embedded, it's a very simple language, and has 3 implementations (in Java, in Go and in Rust).

[1] https://github.com/bazelbuild/starlark

Wow, I just integrated Lua into a C++ project for the first time last night.

Took <15 minutes to integrate it with the build system (FASTBuild), watched a tutorial video[1], and then in another 15 minutes I had it hooked up to configure my application.

[1]: https://www.youtube.com/watch?v=4l5HdmPoynw

For the past year I exclusively use Lua for my side projects. And I use it with certain self-imposed restrictions:

* no Luarocks

* no native libraries

More details: http://akkartik.name/freewheeling

Can you kindly provide a one line explanation as to why no Luarocks?
https://xkcd.com/2347

That way lies npm and left-pad.

Tools encourage behaviors. I avoid tools that encourage behaviors I want to avoid.

Luarocks works quite well if the lib is pure Lua.

For native libs it's a wild west and success depends a lot on your environment. Sometimes the lib is being built with -Werror and fails if you're using more recent compiler, sometimes include paths are not resolved correctly using pkgconfig or similar and so on.

That essay/software/discipline is awesome!
Problem is, which version of love to use? I do know someone had a multiversion love program but it didn't work for me.
Could you point me at it? Just for my own understanding.
I think its this but I am unsure.

https://github.com/Alloyed/flirt

I just looked at this. It feels awfully brittle. My goal at the moment is to try to support every version of LÖVE going forward. All my apps work with any version after 11.0. Hopefully that streak won't be too onerous to continue in the future.

Here's how my apps' version check works, based on suggestions from the LÖVE Discord (but mistakes are mine): https://git.sr.ht/~akkartik/template-live/tree/9cddb20b96aa1... I don't use t.version in conf.lua anymore in Freewheeling Apps (though my early apps are not perfect)

I've never used love. It's certainly difficult if you're going with portability, and then add to that backwards compatibility.

I've talked about this before, but I think the only way to do versioning is the way everyone does it nowadays. Have a directory, with a wrapper around the dvcs/(Commits/ Tags/) and then a subdirectory for a "git workingset", And then you Import("url",{tag="",commit="",req="x.y.z"}), it should be fast because you're just doing io.open($REPOHOME/$URL/TAG/$TAG/$COMMIT/x/y/z) which just gets dropped to require() as normal. of course, add LÖVE to the mix and I just don't know. love should have mandatorily a love-VERSION as the command.

how has your experience without using types in lua been so far with regards to finding out where issues reside and debugging unexpected values?
Great question! It's not ideal, but has been outweighed so far by the other benefits I mentioned. I do track the issues I run into due to lack of static types[1] in the Manual_tests files in each of my projects, e.g. https://github.com/akkartik/lines.love/blob/e568378ecb/Manua...

[1] I wouldn't say Lua/Python/Javascript has no types. They just lack types for variables rather than values. Forth, on the other hand, has no types period.

Arrays start at 1. Just... no.
Why? 0 based indexing came from a time where you used memory addresses directly to index into arrays. Today, we don’t do that, so 1 based indexing seems extremely reasonable. Besides, with Lua you can still do 0 based indexing with a small personal extension function.
One of the first things requested by users of an internal media sequencing tool where time is measured by frames was 1-based indexing, they absolutely hated starting from 0
Yea, I love how 0 indexing is such a big deal. Like tabs vs spaces, it doesn’t matter even a tiny bit, it’s like a religion. It’s not that hard to index off of 1, and like I said, you can write like 5 lines of code to make 0 indexing the default!
From an aesthetic perspective, I agree. In practice, it wasn't a problem, though. I rarely accessed arrays with a constant index anyway.
maybe I'm being dim but what possible aesthetic benefit does it offer? The only benefit that springs to my mind from a language design perspective is that it is most likely familiar to the user of the language from languages they have used before.

Both Smalltalk and APL use 1 based indices so I just find any claim of obvious aesthetic superiority on this issue a little hard to accept without some back up.

If speaking aesthetics the fact that the number of items in your array is equal to the position of the last index item in the array would seem aesthetically superior.

Lua's 1-based indexing is largely inspired by Fortran. Lua's first users were Brazilian engineers in the early 90s, who were mostly experienced in Fortran.

Arguably, one-based indexing is more closed aligned to mathematics tradition. That point about being easier to index the last element helps to iterate backwards over an array. With zero-based indexing, a backwards loop becomes for(i=N-1; i>=0; i--). Yuck.

exactly, I have a hard time understanding any argument for 0 based indexing other than tradition or a performance hack that I find unlikely was ever that great a benefit anyway.
It is admittedly a mindset that is born of C coding. Pointers and memory offsets and so forth. But lots of languages that abstract such "close to the metal" factors away, still use 0 based indexing.
I meant that aesthetically, I find 0-based indexing more appealing.
Yeah that's a wart, but it's basically the only wart in an otherwise amazing language.
Ehh, local being a keyword (instead of local default, global only if required) was a significantly bigger gaff. The other would be no nulls in tables. 0/1 based indexing is familiarity only.
Requiring a keyword to declare a variable is a good thing. Otherwise, it'd be easily to accidentally declare new variables when you meant to assign to an existing one. Since global is the default, starting a program with something like this effectively keeps that from happening:

  local gindex = {}
  setmetatable(_G, {__index = function(t,k)
    local v = gindex[k]
    if v == nil then
      error("Bad read of global variable " .. tostring(k), 2)
    else
      return v
    end
  end, __newindex = function(t,k,v)
    error("Bad write of global variable " .. tostring(k), 2)
  end})
  for k,v in pairs(_G) do
    gindex[k] = v
    rawset(_G, k, nil)
  end
Very insightful discussion.
Thank you. I'm here all week.. :-)
Nobody starts counting things at 0 in real life so… yes please!
I suppose it's whether you think of array indexing as an operation that offsets the read location relative to th start of the array. It's probably rather low level for the way people are used to working these days.
I think it's just common sense of pointing to the first location and saying "one" ("first element"), "two" ("second element") and so on.
One of my favorite projects that uses Lua is

https://redbean.dev/

It’s a single file web application engine, and more, in an executable you can run on several OSes and I think even bare metal, but it’s been a while since I looked at it so it’s probably even more awesome today.

Yes! I'm currently learning Lua now just because of redbean.

I also recommend the excellent ZeroBrane IDE - https://studio.zerobrane.com/

As a Java/Python dev there is something about using such light weight and fast tools like redbean, lua and ZeroBrane that brings a smile to my face.

There's also the http://prosody.im/ XMPP server that's written in Lua, and it's very successful there. The other major XMPP server implementation is in Erlang and they are equally praised, so that should tell something about Lua's versatility.
Lua was born in 1993. That's 30 years, not bad for a language that some still consider new.

For those looking for a GUI library for Lua, check out TekUI. http://tekui.neoscientists.org/index.html

I tested it on various distros without hassle, both on x86 and ARM. It would probably be worth of porting to other languages too.

Lua is also the language of choice for the nmap scripting engine.

While cute, easy to learn and all, I feel like it has kept back the development of nmap scripts. Only a few people are dedicated enough to take the time to learn it.

Then again, I think this has kept the quality of the available scripts high.

What are some of the nmap scripts that you use regularly?
Well who knew nmap has a scripting engine? :)

Before reading your post, if i were in a situation where I needed to do complex stuff with nmap, i'd just integrate it in a script that would run it multiple times, parse the output etc.

I never imagined it had a scripting language inside itself.

Guessing the only people who know about that are admins in large networks and black or white hat crackers...

Oh yeah, if i were in a position to use nmap every day... Fortunately or unfortunately nmap --help has been good enough for me so far :)

> if i were in a situation where I needed to do complex stuff with nmap

Note: I've never been in such a situation. My network admin duties are like 0.5% of my time.

A whole gamer generation has learned to make mods/games with this. (Roblox).

Not javascript.

Not python.

This.

That which educates the young, shapes the future, may the elders like it or not.

World of Warcraft addons as well.
Pico-8 fantasy console uses Lua (a subset of; with Pico-8-specific console APIs). I keep going back to it in my personal projects time and again. I always think, "OK, I'm going to build the next thing in C" and wind up falling back to Pico-8 and Lua.

The in-the-works Picotron will grow from Pico-8's roots and present a full fantasy computer, with an OS, windowing system, etc. and expands Lua support to the full v5.4 language.

I've started to view Lua as an extension to C rather than a language in its own right, which isn't really accurate, but reflects how well the two languages complement each other.
The ConTeXt typesetting system tightly integrates Lua. One aspect of Lua I dislike is that it isn't object-oriented. What's impressive is that the language can be extended to be so in astonishingly little code:

* https://github.com/kikito/middleclass/blob/master/middleclas...

Using OOP, typesetting a hexagonal grid and a symbolic representation of a neural network on top is easier develop, IMO.

A vertex defines a point in 2D space:

* https://github.com/DaveJarvis/keenwrite-themes/blob/main/bos...

An edge connects two vertices:

* https://github.com/DaveJarvis/keenwrite-themes/blob/main/bos...

A graph connects edges:

* https://github.com/DaveJarvis/keenwrite-themes/blob/main/bos...

A priority queue serves for ordering edges by weight of adjoining vertices:

* https://github.com/DaveJarvis/keenwrite-themes/blob/main/bos...

With these concepts in hand, we can typeset a grid and a "neural network" on top:

* https://github.com/DaveJarvis/keenwrite-themes/blob/main/bos...

Here's an example of the output from the first chapter:

* https://i.ibb.co/19DCDZy/ch-1.png

And a later chapter, where the "network" has grown in complexity:

* https://i.ibb.co/ncf16vg/ch-2.png

This is for my near future hard sci-fi book on AGI. I'm looking for alpha readers to give me feedback. See profile for contact details.

P.S.

There's a subtle bug in the code that can result in one or two disconnected vertices. If you spot it, let me know!

It is inevitable that people complain about Lua's 1-based "tables" which serve as both arrays and hash maps.

I usually encourage people to remember the practical origins of 0-based arrays when thinking in terms of pointers, where 0-based counting is useful in terms of talking about offsets from a point in memory, as contrasted to 1-based counting where you are primarily talking about quantities of collections.

Lua is a great language. My only wish is that it was easier to do multithreaded work in it. Unfortunately, this is not the case without some significant architectural forethought.

The reason why 0 makes sense for pointers isn't due to some arcane implementation detail of programming but because of how one conceptualizes ranges and distance and pointers just so happen to be a place where that comes up very directly in programming. Any time you attempt to develop an algorithm that needs to think about multiple ranges of a collection at the same time or needs to scale ranges through a collection, 1-based indexing causes you to have to throw in a bunch of +/-1 compensation factors to prevent you from double-counting or scaling past an edge. Life is just too short to have to constantly debug the issues that come from 1-based indexing when developing complex algorithms, even if it means beginners feel a bit confused by "counting from 0".
No, it actually does originate from 1960's era programming practices. Not to say there weren't also 1-based or N-based numbering, but you're historically misled here.

You could also say the same about counting collections. No regular person counts 0... 1... 2... 3... and ends counting N-1. It, too, is a workaround.

I don't think saurik is talking about history, so you're talking past each other in that sense.
I think so, too, but my point is that it makes sense for other historical reasons—not the one the poster mentions. Those decisions weren't made due to mathematics.
Agree. I came here to say that 0-based is more sensible, but as I thought through my arguments, I realised that I’m wrong. I do most of my coding in TS, Go and plpgsql. PL is 1 based, but the only time I find it frustrating is when translating between language boundaries. The rest of the time it’s just convention (eg, using <= instead of < in loops).

But I realised that I frequently want to get the last element in an array, and of course it’s easier with 1-based arrays.

So ultimately I don’t think it matters, except that most people are used to 0-based which can cause cognitive friction.

We aren't talking about counting. We are talking about indexing.

Most programs don't need numeric indexing. They can either treat indices as opaque keys or they can use a foreach loop to avoid touching indices at all (or friends like map, filter, zip, and so forth). Even in an old language like C the only place indices usually matter is in the first line of a for loop. But sometimes you really do need numeric indices, and when you do zero-based indices are almost always cleanest.

Just think of how gnarly a flat 2D array would be if you had to use one-based indices. Gross!

I don't think 0-based was ever as common a practice as it is today, especially in the 60s. In the beginning there were two camps fighting which system was to be preferred and there were many more 1-based or agnostic languages (like the whole Pascal family). "Numerical Recipes" a pretty popular book for some time even came in two editions, a 0-based and a 1-based version. I think the victory of C had a big influence on 0-based winning.

Personally I find it very sad that the agnostic approach of Pascal did not prevail. Let the programmer decide what they think fits the situation best and let the compiler deal with the technicalities. This, and I also miss range types... for like 40 years now...

Surely everything was 0-based initially, when everything was written in assembly? Are there any 1-based ISAs?
I wouldn't call assembly 0-based. Sure, the zero flag is convenient for branching on zero when counting backwards but what makes assembly 0-based apart from that? I'd say having a pointer to the first element of an array is pure convention and you could as easily point it to the imaginary element before, similar to how the program counter always points to the next imaginary instruction on most architectures.
The offset addressing modes all start from zero. You could hack around it in some places by storing the pointer to an element before but that's clearly going against how it is intended to be used.

For example if you have an array that you cast between different sized types (e.g. uint64 to uint8) then you have to change the pointer value!

> Personally I find it very sad that the agnostic approach of Pascal did not prevail. Let the programmer decide what they think fits the situation best and let the compiler deal with the technicalities. This, and I also miss range types... for like 40 years now...

I am with you there. However, Pascal, a toy Algol, was designed to be a language for a compiler course. It took its ideas like "where does an array start" from other Algol implementations and added a broken type system. Pascal gets the love now, as witnessed by the Lua designers referring to Pascal and not to Algol.

If the 1st element is [0], something's conceptually wrong.
[0] just means the address of the element is ptr+0. The first element is at the address from a memory addressing perspective it makes sense that it would be at [0]
Why care about the memory address for higher level languages like Lua?
The current element is at distance 0 from iter[].
You are right, but the thing that is wrong is that the word "first" is associated with the number 1!

Think about it. Natural language could easily have associated the initial ordinal with 0. Let's call it the Nilth (zeroth is really hard to say).

The nilth element is element 0, the first element is element 1, etc.

The winner of a race is in nilth place. Silver goes to the person who came first. Bronze so second.

You walk in to a building on the nilth floor, climb the stairs to the first floor (that might sound weird to Americans but it's how it works in other parts of the world).

This is probably all sounding crazy to you but it's way more elegant and self-consistent.

Which century is the year 2023 in again?

Some of us Americans know our elevator numbers are wrong — zero / nilth is the correct ground floor by far! — but that doesn’t mean when you pick up the first apple in a bowl of apples you should rename it to nilth. Counting order is different from Indexing order. One is the order of objects in a list and the other is the offset (2nd floor is two floors up from zero). They are just different things and that is why they sound right in different contexts!

(It’s 2023, and we both are right)

Correct, counting and indexing are different things. But the "first apple" is an index not a count.
(comment deleted)
> 1-based indexing causes you to have to throw in a bunch of +/-1 compensation factors to prevent you from double-counting or scaling past an edge.

Way back then, I started with Algol 68, moved on to Turbo Pascal, and finally Fortran (I was young and needed the money). My vectors and arrays started at 1, as was the default, and I do not even know what you allude to. The first element has the index 1. Since there is no zeroth element - neither in language nor in thinking -, starting at 0 is what causes problems. You always have to use a corrective term in your head - the element five elements after the third one has index 7.

Yes, whatever.

My experience is that the +-1s come up less often in 1-based languages if you're writing high-level code, and when they do they're more intuitive to catch, even after years and years of coding 0-based.
Yes that is annoying the first week, then you get used to it.

If the biggest complaint about Lua is 1-based tables, I’d say that’s a compliment to the language indeed.

The problem with 1-based arrays is that it makes the offset and location be different. For example, we want to place an item into a simple circular buffer with zero-based indexing:

arr[idx mod capacity] = 123

Now with 1-based indexing:

arr[(idx-1) mod capacity + 1] = 123

This is absolutely makes it a PITA if you're working with things like bitmaps, blending, or sound mixing. If you only need a language for high-level scripting, then it indeed is not such a big deal.

Lua’s arrays can start at index 0 if you like, there’s no real reason why the first line wouldn’t work.

Starting at 1 is a convention:

array = {“hello”, “world”}

print(array[1]) —- displays “hello”

array[0] = “why”

array[2] = “there”

for i=0,2 do print(array[i]) end —- “why” “hello” “there”

It's cute that you can do this, but it's not a practical solution, so I don't really know why people keep bring this up.

As shown by your example, arrays constructed the native way will start at 1. So you're going to need to make your own custom constructor which means additional overhead.

Also, all built-in functions that expects 1-based arrays will completely break:

    for _, value in ipairs({[0]="foo", [1]="bar"}) do print(value) end 
This will print "bar"

And even if you still insist on pushing through all of this friction by handrolling your own stdlib, everything is out of the window once you interact with any external library that you don't control.

There's a very good reason why no one is using this trick. Anything other than 1-based indexing is just undefined behavior.

There are different uses for `for’, and you’re supposed to use `for i=0, n do’ for the use case you’re describing.

People absolutely do this, and you have to for things like using LuaJIT’s FFI and doing work across C.

Lua tables support do 0 as an index. You can also use negative numbers or floating point numbers, which can be convenient for some applications.

One issue with using them that way is that some Lua idioms don't work as expected. For example, "for i,v in ipairs(t) do ... end" will start iteration with i=1 and stop when it reaches an index that isn't present. Also the length operator (#) won't work as expected.

What do you get when you get the 0-index? Does x[0]=x[1]?
Just reading the comments here, x seems to always be a hashmap. It is implied that there's no fundamental array type in the language. So no, x[0] and x[1] do not refer to the same variable, although they certainly could compare the same depending on their specific values.
Works fine in Fortran, R, and Julia. People do pretty complex stuff in these languages and two of them are compiled.
It's not an insurmountable barrier, for sure. Just something that makes it very annoying in a lot of code. While not really making it easier anywhere.
I've been working on a PR to document and make atomic pointer interface public in Julia. It's really not that hard to handle it under the hood so that array indexing and translates to LLVM and C zero indexing.
Weird then that scientific computing languages prefer 1 based arrays. Maybe can show how it's very annoying in lots of Fortran or Julia code?
Python is as much a "scientific computing language" as anything, and it's zero-based.
You haven’t shown how it’s “very annoying” in R, Fortran, and Julia.
Not really, the core language is general purpose. It's Python's scientific libraries and ecosystem which became popular.
> Weird then that scientific computing languages prefer 1 based arrays.

Not really when the code is routinely transcribed from papers, and the ages-old convention throughout mathematics is one-based indexing, both bounds of the interval included. (With the notable exception of Euler’s beta and gamma functions.) Of course, in a paper intended for a human reader the occasional off-by-one error is much less dire than in code, so the author is freer to use a more error-prone convention.

I once had to translate from a one-based paper to zero-based Python code (with the added bonus of different discrete Fourier conventions *shudder*), and it’s remarkably annoying even if the resulting code is more obviously correct than the paper’s original formulas.

It is potentially odd (or even) but nothing compared to everything else when it comes to programming oddities or quirks.

What is the first item in this collection? Oh that would be the one in the box marked zero unless it should be in the one marked ... one!

You mention a circular buffer as an outlier but you simply have to decide where it (the buffer) started off life - 0 or 1 or something else. Make sure that when it is defined, everyone agrees on offsets and off you trot.

No it isn't particularly easy but then bitmaps and blending and sound mixing involve some very involved concepts that are way more complicated than sorting array bounds and worrying about where you are on the Magic Roundabout.

> You mention a circular buffer as an outlier but you simply have to decide where it (the buffer) started off life - 0 or 1 or something else.

No. A circular buffer is just a simple example where you need to go between offsets and locations.

The same theme happens in many similar cases, like directly manipulating bitmaps. E.g. with zero-based indexing into an RGB bitmap:

  bitmap[y*width*3 + x*3] = 1
vs. 1-based indexes:

  bitmap[(y-1)*width*3 + (x-1)*3 + 1] = 1
The point here is that with the Lua code the CPU is doing some extra work because mod is 0 based because of mathematics and Lua is 1 based by design.

However mod itself is doing extra work compared to incrementing a counter by one.

This is pseudocode because I don't use Lua much and I don't have the time to check the syntax now, but it should give the general idea

  class CircBuf(size)
    buf = new Array(size)
    i = 1
    add(value)
      buf[i] = value
      if i == size
        i = 1
      else
        i = i + 1
The relative efficiency boils down to a comparison+increment vs mod on the particular CPU the code is running on. Mod should be a division, unless size is a power of two.
The difference between 1-based and 0-based indices is irreverent enough, that’s the reason it feels uncomfortable for choosing to use something that is different. It is like little-endian vs big-endian, but you never see people choosing to use big-endian now
Dijkstra had an opinion on this (and many other things). 0-based ftw.

https://www.cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF

Bit short-sighted of him to only consider positive array indices IMO. Lua allows them to be negative.
Lua also allows floating point indices. In fact, any value can be an index into a table. That means any number, string, True/False, function reference, thread (coroutine) reference, table reference, "userdata" reference (usually a pointer to a C object), or nil can be an index.
Dijkstra conveniently ignored the other situations where 1-based works out better than 0-based. For example, getting the least element of the array, or iterating backwards over the array.
This reminds me of that somewhat famous observation about the bright scientists working to increase clicks at Google/FB instead of finding cures for diseases and such.

So much brain power to defend something that existed a long time ago and was adopted as some kind of law of the Universe when there is clearly no good reason to be this way today other than "it's just how it is" and every new programmer has to pass for a quick phase where the 1-based it beaten out of his mind.

I'm not going to complain about the indexing but I am going to complain about tables. The fact that you need at least one element to be in a table to trigger heuristics to serialize it properly makes Lua uniquely bad for use in polyglot distributed systems.

I've specifically run into this with OpenResty and JSON processing...

I would avoid trying to serialize tables in polyglot systems since it sounds like a specialized data structure whose counterparts don't really exist in other languages anyway.
It's a specialized data structure that the whole Lua ecosystem drives you to use as your primary collection type. They're blessed by the language, every library function will use will take and return them, and your deserializer will produce them, whether you like it or not.

Ultimately, you can use different data structures in Lua... but you'll have to write reams of boilerplate to do it. That's why I'm saying that (unfortunately) it's Lua itself that's a bad fit, not just tables.

I think it goes well with the loop syntax. In contrast to C/C++ where you do

    for(i=0; i<n; i++)
In Lua you do:

    for i=1..n
On the Lua case, both the beginning and end of the loop are inclusive.

If you have to iterate over n elements, the first syntax is optimized for 0 to n-1, while the second is optimized for the other case. See:

    for(i=1; i<=n; i++) // <n becomes <=

    for i=0..(n-1) -- ugh
Lua metatable > JavaScript prototype
Let's put aside the god-awful 1-based tables, in 1993, which alone should have killed the language. My big problem with Lua is that it tries very hard to make possible all styles of language to all people, and as a result it is a language with a lot of boilerplate to do any one of them.

For example, consider Lua's OO. The most obvious inspiration for Lua here was Newtonscript, via Self. They're quite open about it. Newtonscript and Self are prototype-style OO languages done well, as opposed to Javascript, which is a prototype-style OO abomination. Lua advertises itself as being able to do prototype-style OO, all you have to do is make some tweaks to its metatables. But while this stuff is baked into Newtonscript and Self, it is an amazing amount of boilerplate to do it in Lua, to the point of parody. https://www.lua.org/pil/16.1.html

On the page you linked, I see four lines of boilerplate in a constructor:

  function Account:new (o)
      o = o or {}   -- create object if user does not provide one
      setmetatable(o, self)
      self.__index = self
      return o
  end
Doesn't seem so bad. Are you referring to something else?
Why would you wish the death of a language only because its default index for arrays is 1?

You're not forced to index from 1 and onwards. You can use any range you want to: https://www.lua.org/pil/11.1.html

You're way too triggered by a programming language, dude. Move past it.
Anytime Lua comes up I'm reminded of John Earnest's Lil scripting language[1]. It's inspired by Lua and Q, built for Decker[2] which is a re-imagined version of HyperCard. Generally though, I love lua for its embeddability and am extremely happy anytime I see someone chatting about integrating it. Modding and scripting in games was a tremendous motivation for me to dig more into programming and these approachable languages are a core aspect of that.

[1] https://beyondloom.com/decker/lil.html

[2] https://beyondloom.com/decker/

I'm currently using Lua as an extension language in my app [1]. Users can write their own custom UIs and DSP code. It's great how the Lua interpreter (and LuaJIT) allow you to provide your own memory allocator, so I can preallocate a region and do realtime-safe allocation within it.

[1] https://audulus.com

Hey just an fyi, but the screenshot on the homepage is 1.2mb in size. Might wanna optimize it a bit.
I once read through the entire Stroustrup C++ book and figured in one case, there is absolutely no way to make this work except for a secret, hidden variable in the class. Did a test compile and: There it was! OK, I get C++, done. Then I saw actual C++ programs. Whoa, how do you index into an array using a string? You don't of course, you overload the [ ] operator.

Reading about Lua when it was newer I could foresee the same thing happening on steroids. Yes, the language itself is simple but what you can do with it - emulate almost any construct in any language - promises confusion. When it comes to readable code, sometimes "less is more" in terms of langauge features.

> OK, I get C++, done.

> ...

> When it comes to readable code, sometimes "less is more" in terms of langauge features.

Please tell me that is meant as satire. There is no sane way to look at C++ and Lua and decide "less is more" would favor C++.

I'm surprised people dislike 1-based indexing. I use both types of indexing commonly and never had an issue with it.

The only issue I've come across in practice is lack of immutable tuples that could be used as table keys. I found myself either emulating this with nested tables or creating string identifiers for my multi-valued keys.

You can also use light userdata for that kind of thing.
1 - based indexing is superior when thinking about actual collections, and not memory pointers. Most regular folks think of '1' index based. Eg. Most elevators (at least in NYC) start at floor 1, not 0. Once they start CS/programing classes they adjust to 0 thinking

0 - based indexing is better when dealing with pointers. But, none of us do today, (at least directly), and it seems that 0 based indexing is just an artifact of time.

When I used Lua for professional programming, I thought that 1 - based index is just better. I saw my self doing a lot less (-1) calculations.

> Most elevators (at least in NYC) start at floor 1, not 0.

European buildings are generally 0-indexed: https://en.wikipedia.org/wiki/Storey#Numbering

Except people call it the "ground" floor which makes sense, not the "zero" floor which does not.
0 makes sense when you consider -1 to be the first basement level.
Which also gently introduces children to negative numbers : - )
It’s not consistent but I would estimate that “0” buttons are as common as “G” buttons in elevators in Europe - along with -1, -2, etc for basement levels.
The same applies to most former continental Europeans colonies in latin america and africa. 0 based indexing, sometimes with Floor 0 (argentina comes to mind) or simply ground (Terreo in Brazil)
Australian buildings generally have "G" for "Ground" level (or something similar), then level 1 above that, so they're basically 0 indexed.
0-based also makes more sense when modular arithmetic calculations are involved, which is not that uncommon.
In my building the 1st floor is on set of stairs/1 elevator ride up, the real first floor (i.e the ground floor) is labeled lobby (or a big green button in the elevator).

0-based indexing makes just a much sense at higher level abstractions, aka iterators because there you're more likely to think in distances and ranges than indices, iter.advance(0) => stay where you are, so if iter is at the start, iter+0 is the first element.

We really don't want to use U.S.A. lifts as a gold standard, unless we want array indexing that unpredictably misses out array index 13 based upon the religious beliefs of the initial software developer. (-:
I dislike 1-based indexing, but I also don't really care that much. In my experience, people react with disgust to it, then get used to it, and still prefer 0-based but discover that 1-based isn't all that much more awkward in practice.

You get used to it pretty quickly when the whole language is built around it.

I solved the immutable table thing by encoding my tables to CBOR in the canonical representation. The same table structure always encodes to the same CBOR. I did have to write my own CBOR encoder, though, because the existing art wasn't good enough.

Lua is a great little starter language.

I love that so many games use it.

Lua is great, I just wish it had typing
Nelua is mentioned in another comment.
This is timely - we've been gearing up on Lua as we're building an open-source web app firewall [1] on top of Redis and whatever framework, and it's been a great experience.

Lua really unlocks some superpowers with Redis with respect to being able to chain multiple requests into a single call, handle some application logic, etc.

1 - https://wafris.org

Another option for your weekend plan to learn Lua is to do it with Love2D [0].

Really easy, well documented and fun framework that uses Lua.

[0]: https://love2d.org/