You can do a LOT with it. It's one of the core data types in my language...
Can you give some examples and/or samples? It seems like a lot of overhead, with a global, a string, and a map for every atom.
The globals are only for the run-time declared atoms. The map and array of strings don't take up a lot of space, and the version of the library I included resizes the array on demand.
Examples include all of my data type declarations. This allows me to do type validation by checking things against _integer, _float, or _string as essentially enumerations that I don't have to worry about.
I also use this for declaring my keywords, my built-in procedures (called primitives in a forth-alike), and for all user-defined procedures (called words in forth).
These are even useful for string literals compiled into a word; a string gets added to the atom table and replaced with the atom reference. Any future instances of that string are similarly automatically replaced with the same atom reference, so I only have one actual string stored for every instance of a string literal.
; These add those to the atom table, and then when checking data types, I can use _integer, _float, etc in my PB code for the built-in types for my compiler.
; This is used to register simple prim names while also making them
Macro registerprim(primname, pointer)
If Not(FindMapElement(atomtoprimtable(), Str(_#primname)))
atomtoprimtable(Str(_#primname)) = pointer
primtoatomtable(Str(pointer)) = _#primname
Debug "Redefining prim?"
; This is a little misnamed, but essentially I use this for prims whose names would create invalid variable names.
; It skips the atom(primname) step, and just creates the atom and registers the prim
Macro registerprimunprintable(primname, pointer)
primatom.i = newatom(primname)
If Not(FindMapElement(atomtoprimtable(), Str(primatom)))
atomtoprimtable(Str(primatom)) = pointer
primtoatomtable(Str(pointer)) = primatom
Debug "Redefining prim?"
Since it's a generalized solution, I can always turn an atom back into a string without the code having to know whether it was defined at compile time or at runtime. This is very useful for debugging.
Since we can't add it to PB as a first class data type like erlang and ruby (or my language) do, sadly a lot of safety checks are only halfway there. (I do get one check in that if I mistype a _whatever definition, EnableExplicit throws a complaint at me)
Use of atoms as a data type in the compiled code is a more complicated subject, but basically it's the freedom to get something in the same category of enumeration for 'free', and being able to safely define new items whenever you want, and always having a guarantee that within a given execution of the program, there will be no clashes and no duplicates.
Obviously, this isn't useful for everyone, but there's a large category of things for which the concept is a very useful abstraction.