Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

No. I did a project on Urbit for an OS course. It is deliberately obfuscated. For example, take the specification of Nock, the lisp-like "assembly code":

https://github.com/cgyarvin/urbit/blob/master/doc/book/1-noc...

The symbol evaluation is designed to take whoever is trying to program in Nock in circles around the specification. It makes even the most basic operations unnecessarily verbose and hard to memorize. To increment a number, you must put it adjacent to the expression [4 0 1]. So 42 [4 0 1] evaluates to 43, after several steps. There is no reason that an "assembly code" should have a decrement operator that must be evaluated more than once in order to get to the final expression.

Hoon, the "high-level" langauge is worse. A function to decrement an input by one is specified by the expression:

(a=. =+(b=0 |-(?:(=(a +(b)) b $(b +(b))))))

Like a postmodernist artists asks "what makes art?" and answers that there can be no objective answer, Urbit asks the question "What makes good software engineering?" Unfortunately there are many objectively bad design decisions you can make in software engineering. Forcing even the most basic operations to be incredibly verbose and unintelligible is one of them.



Well, normally, if you want to decrement a, you write (dec a). But yes, if you want to write your own implementation of decrement, you could do it this way. It's just as bad an idea in Urbit as in any other language.

Nock is a functional assembly language, so it's not meant as a human interface. I think you mean "increment" instead of "decrement." Yes, [4 0 1] is an increment formula in Nock; 4 is the increment operator, 0 dereferences a tree address in the subject, 1 is the root of the tree; so [4 0 1] means "increment the subject," where your subject is 42.

If you know Lisp, you can think of Nock as Lisp without symbols or an environment; instead of having this hardcoded key-value store, the environment, you have a subject which is referenced with tree addresses (1 is the root, n2 is the left child, n2 + 1 is the right child).

Is this horribly arcane? It doesn't seem that way to me, but de gustibus non disputandum.


Nock just seems deliberately obtuse. The website boasts that "Nock is also the easiest thing in the world to learn" and that the spec "gzips to 340 bytes", but I can't seem to make heads or tails of it. Sure, you can defend Nock by saying it's not meant to be used directly, but can you give any advantages Nock offers over anything else? Because lambda calculus is even easier to implement and is more intuitive.


I don't know anything about urbit.

I stared at the nock specification for about 3 minutes, just the symbolic specifications, not any explanation in english language, and I easily understood it. After just 3 minutes of staring at some symbols, I know everything there is to know about nock.

There is no other assembly language with this property. It seems to me it not deliberately obtuse, but in fact it's very clear, simple, and understandable.

I haven't looked at hoon and other things yet.


So I get that Nock is hard to wrap your head around. But that's like complaining that your cpu's machine code is deliberately obtuse. You aren't going to be writing nock unless it's for fun.

Hoon may be a little deliberately different which can be construed as obtuse but it actually takes less time than you might think to get up to speed in it.


I still don't understand why this thing was chosen to be the basis for all of Urbit. What advantages do you get from building on top of Nock vs. lambda calculus or SKI combinators or anything else?


I don't know that anyone has tried to build a practical system on top of SKI combinators, which are certainly simpler than Nock. It's probably quite hard.

You can build a practical system around the lambda calculus, which is arguably as simple as Nock. But you don't build a practical system by building layers on top of a simple lambda interpreter; you do it by extending the simple lambda interpreter, until it's no longer simple.

The critical feature of Nock is that it's very simple and you don't extend it, you layer on top of it. So, for instance, it's very easy to upgrade Hoon's syntax or semantics over the network in a live system, because the interpreter is a Nock interpreter and doesn't know anything about Hoon.


I have no idea. I'm not Curtis. However, have you ever just wanted to burn it all down and start over? I mean all of it. lambda calculus, the von neuman architecture, all of it?

Urbit is a little bit like that. When he says clean slate he really does mean clean slate.

Which in a way is what makes it fun. It's so far out of your normal experience that it's delightful to play with. At least if you are someone like me anyway.


Starting over is fine, but if you make choices that are deliberately awkward because the only reasonable ideas you have were done before, that's not a clean slate. That's a twisted mirror of existing designs.


Seems like (as I've said in every thread on Urbit) it just needs more developers and users so that we get the Urbit flavoured higher level languages.


I can give you one advantage from the generic perspective of a creator, in that I experience unbridled joy from using my own creations.

From my point of view, no other justification need be made, since no one is in any way forced to use, understand, or even acknowledge my creations.


> Lisp without symbols or an environment

Why is that an advantage? The ability to give things names seems like a big win to me.


Naming things is pushed up to a higher layer. The lower layers of the Internet don't care about names; why should the lower layers of a computation system?

To be practical, though, I think the point here has to do with the idea that languages come and go, and their package namespaces along with them, but VMs stick around as long as useful libraries run on them... unless the VM is tied in some way or another to a language that has gone out of fashion.

Think of the JVM: even if you're writing Clojure, you call Java functions by their Java names. Effectively, you have to know a (tiny) bit of Java to call those functions. Why? Because Java syntax assumptions are baked into JVM's module/function naming rules. Clojure functions end up with Java-like names too, after compilation. Clojure hides it from you, but to call a Clojure function from Scala, you can't write a tiny bit of Clojure; you have to write a tiny bit of Java. Naming rules have made Java the only first-class JVM citizen.

Nock, on the other hand, defers the concept of naming up to the language/platform level. Effectively, each language running on Nock needs to make up a naming rule, and decide the canonical name (canonical to the language, not to the VM) for every available function itself. This means that there would be no de-facto "Urbit library ecosystem"; there couldn't be, as a particular arrangement of Urbit functions—a taxonomy—wouldn't be guaranteed to survive in any sensible manner between languages. Not all languages would have the concept of a "module", or a "package", etc.

This could be seen as bad: every Urbit-derived language would need to effectively create its own ecosystem of "library taxonomies", manifests mapping from its module system out to function-space, making each language maintainer roughly like a Linux distro maintainer, consuming functions from "upstream" and packing them.

But this lack of forced naming also has the potential to be very good: it creates the opportunity for taxonomies to be created separately from any particular language, and then consumed voluntarily by multiple languages. A taxonomy becomes its own first-class object, above "runtime" but below "language", where languages can pick a runtime+taxonomy to support. This, further, moves the creation of a "standard library" from the language authors to the taxonomy authors; languages "on" the same taxonomy become thinner bundles of syntax and compile-time features, while sharing all their stdlib algorithms, "primitive" data structures, and language "features" like GC (because that's mostly up to whether the data structure "primitives" provided by the particular taxonomy are implemented that way.)


> The lower layers of the Internet don't care about names; why should the lower layers of a computation system?

Because the whole point of computing is to create value for humans, and humans think in terms of names. The fact that the lower layers of the internet don't care about names is not a feature, it's a bug. The internet runs on 32-bit IPV4 addresses because in the 1970s when the ARPAnet was invented that's all we could afford. But it's not 1970 any more, it's 2015, and computing and storage are many orders of magnitude cheaper today then they were then. We can afford to give names to things that we couldn't before.


> The fact that the lower layers of the internet don't care about names is not a feature, it's a bug. The internet runs on 32-bit IPV4 addresses because in the 1970s when the ARPAnet was invented that's all we could afford.

We had the chance to "fix" this with IPv6... but we decided to give IPv6 addresses as well. I'm pretty sure having addresses that can be broken into prefixes to assign to different ASes and refer to in BGP routing tables is a feature.

(In IPv6, there are such things as Cryptographically Generated Addresses that are "identity-like"—but these still only exist within the last 64 bits of the IPv6 address, leaving the first 64 bits to function as a hierarchically-assigned routing prefix.)


> We had the chance to "fix" this with IPv6... but we decided to give IPv6 addresses as well.

Who is this "we" of which you speak? IPv6 was designed by a committee, and committees get things wrong all the time, even in 2015.

> I'm pretty sure having addresses that can be broken into prefixes to assign to different ASes and refer to in BGP routing tables is a feature.

You are conflating two different things here. Yes, it's good to be able to build routing tables that are very efficient. But having a number that describes the route to a machine is a very different matter than having a number that determines a machine's identity.

The way things work today -- even with IPv6 -- is that a machine's identity is determined by a number (its IP address) and we have a namespace layered on top of that (DNS). The assumption that the machine's identity is determined by its IP address (a number) rather than by its host name is baked deeply into the fabric of today's standards. This leads to all sorts of horrible non-orthogonalities, like the "Host" header being required in HTTP 1.1.

I'm not saying this was an unreasonable design tradeoff, just that it was a tradeoff, not something that was desirable for its own sake.


> There is no reason that an "assembly code" should have a decrement operator that must be evaluated more than once in order to get to the final expression.

Doesn't this ignore the concept of Urbit's "jets"? You write code that does things in a stupid-but-canonical way; it's then the runtime's responsibility to take canonical patterns and provide optimized implementations.

The cool thing is that you get to write a naive interpreter for all existing code in a couple-hundred lines, while also being able to write a good interpreter that makes the same code run quickly. Like regular Lua vs. LuaJIT, but much moreso.


Jets are pretty ignorable. Pattern-matching an incredibly obtuse language against a distributed database of machine language is not in any way comparable to LuaJIT, nor do I see any good way for it to do any meaningful optimization beyond e.g. replacing peano numbers with machine integers.


I would note that Nock is a representation of an ISA for an abstract machine, not a language. Aren't loop-unrolled x64 code, LLVM bitcode, BEAM bytecode, etc., also "obtuse"?

In fact, the best thing to compare it to, in my mind, would be RarVM bytecode: another ISA designed to be "forward-compatible" in the sense of ensuring that even the first interpreter will be able to run code from years in the future. Nobody ever sees RarVM bytecode; nobody even realized it existed for decades. It was just there, an implementation detail of the RAR encoding/decoding process, doing its job of enabling the RAR format to change its algorithm over time.


Abstract ISAs are still languages.

And at least loop-unrolled x64 code describes what it's doing straightforwardly, instead of throwing around redundant mathematical abstractions and hoping the interpreter will magically pattern-match them away.


I get the sense that the (unspoken) point of encoding basic operations in an extremely redundant way is, in fact, forcing "production-quality" Urbit interpreters to support jets. It's not really a "magic hope" for this pattern-matching to happen if the language's stdlib is written to rely on the presence of it; any more than it's a "magic hope" that a Prolog interpreter will support backtracking, when all Prolog code relies on that fact; or a "magic hope" for an Erlang interpreter to support tail-call elimination, when all Erlang processes are idiomatically written using terminal self-calls.

As an aside, though:

> Abstract ISAs are still languages.

Not always true. A lot of current abstract ISAs are (because they were designed to be programmed in, as well as as compile targets), but many aren't. Most code read into a modern VM gets chewed on a bit more from its "canonical" form; the internal representation that results, with threaded code and JIT profiling hooks and tracing et al, basically looks like Nock: a graph of numbers. For most VMs, those numbers are just pointers to structs it has allocated, so they can't really "live" outside the VM. Nock just goes a bit further and says "but if you give those struct-pointers persistent wire-representable identifiers ala CapnProto, you can serialize the whole internal-VM-state graph in a portable way—and then that can serve as the VM's ISA."


I'm not disputing the idea that the standard library's reliance on a feature will force its support, I'm disputing the idea that jets are a workable solution to anything at all.

And no matter the form you turn a language into, it's still a language. Speech and text are generally considered equivalent in that sense, for example- it's the abstract form that matters here.


Yes, Nock is a "language" in at least one sense. But you're arguing that it's a "language" (n., 1. linear encoding of semantic information) and then using that to argue that it's bad, because it's not a very good "language" (n., 2. method for sequential communication of thoughts between humans.)

A Smalltalk live-image, a core-dump of a process, an SQLite database, etc. are all "languages" by definition 1, but obviously not "languages" by definition 2. Nock's ISA is more like those: a format for machines to generate and other machines to consume, and for humans to use tools to introspect; not a format targeted at direct human production or consumption, even through a 1:1 mapping ala disassembly.


Nope. I'm not arguing Nock is bad because it's hard for humans to understand- live images, core dumps, databases, etc. are great. I'm arguing Nock is bad because it relies on jets, which are nonsense.


Is your project online anywhere?


It is not. It might still be on the school's network, but I did it back in the spring and our department's network admin might have wiped my account since I graduated. Didn't accomplish too much to be honest, as might be hinted at by the frustration underlying my post.


You can certainly blame our (old) doc for that. Sorry for the bad experience -- we're definitely still at the stage where I wouldn't expect people to be playing with Urbit and not asking for handholding. Even more so this spring.




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

Search: