Replies: 3 comments 9 replies
-
So your question points out some of their shortcomings, but since you didn't mention it explicitly: there is a primitive called Unlike some other languages, Janet will still intern
All symbols have value semantics in Janet. Which does mean that if you have multiple Janet VMs running in different threads, you could get a collision if you marshal gensym'd symbols back and forth. Generally marshaling kinda breaks the world here either way though: if you marshal an environment that contains a "unique" dynamic variable, then later unmarshal it in a new process, you have no guarantee that the "unique" dynamic variable in the environment you unmarshaled matches the "unique" dynamic variable that your library expects. Or, you know, even across threads: one thread's "unique method In library code I prefer using In cases where I have actually really really needed guaranteed reference-equality unique values, I have used buffers or empty arrays, since their addresses are guaranteed to be unique (with, you know, caveats there around marshaling that may or may not matter). I think it's roughly equivalent to using a I have never run into any of the pitfalls of just using |
Beta Was this translation helpful? Give feedback.
-
In certain places, Janet uses and empty table or empty buffer for such a value. Since buffers and tables are mutable and therefor have reference semantics for equality, they behave like described here. (def sentinel @"") # use an empty buffer here
(def my-fn [x]
(if (= x sentinel)
:do-this
:do-that)) |
Beta Was this translation helpful? Give feedback.
-
JavaScript has a primitive type called "symbol", which I think would be a useful addition to Janet. Obviously the name needs changing, so I'll call them "uniques", because that's the core idea. A unique is a value with reference equality and no content. In other words, if we say the function
unique
gives you a new unique, then(= (unique) (unique))
is never true.The main use of uniques in JavaScript, and one of the uses I see in Janet as well, is for interfaces in OO-style programming. Because interface methods are invoked without naming the interface, their names are effectively semi-global, in that if two interfaces require methods with the same name, one type can't implement both. This is a common problem in Java and C++ as well, I believe, but uniques solve this problem handily. Replacing a keyword name for a method with a unique (predefined by the interface's owner) means that it will never collide with anything, since uniques are only equal to themselves.
Another, similar use would be in dynamic bindings. At the moment, these have the same problem as interfaces -- their names are effectively global. Worse, it's almost impossible to tell in advance when dynamic bindings are going to collide. It wouldn't even be necessary to break existing users of dynamic bindings, since uniques and keywords can coexist in the same table.
In both cases, you could also keep the unique secret to at least mostly protect whatever it names from outside interference.
Uniques can sort of be emulated in pure Janet, but not very well. You'd have to maintain a private global counter, and implement
unique
by incrementing it and returning the old value, perhaps in a table wrapper with some sort of marker like a keyword, but then there's no way to prevent forgery, and collisions might happen if there end up being multiple libraries handling this in the same program. Also, multithreading would probably ruin your day. So implementing them as a primitive is probably the most correct option.I'd try my hand at implementing them myself, although I'm not exactly experienced in C, but I thought it would be good to get feedback on the concept before starting that.
Beta Was this translation helpful? Give feedback.
All reactions