8 ms·
I had a really hard time wrapping my mind around this just reading the website alone. If you are in the same boat, watch the first 10 minutes of this video at 1
by umvi 5y ago
I had a really hard time wrapping my mind around this just reading the website alone. If you are in the same boat, watch the first 10 minutes of this video at 1.5x speed: https://www.youtube.com/watch?v=gCWtkvDQ2ZI https://www.youtube.com/watch?v=gCWtkvDQ2ZI
and it will make so, so much more sense.
...and if you are like me you'll probably need to read this twitter thread to get the answer to your #1 question: https://twitter.com/unisonweb/status/1173942974381744134 https://twitter.com/unisonweb/status/1173942974381744134
Basically the core idea (or one of the core ideas) is instead of a function (like fib(n) which returns nth Fibonacci number) being identified by its name (fib) as is the case with most traditional languages, it's instead identified by a hash of its implementation.
- lisper 5y agoSounds like a cool idea, but how do you fix bugs in functions with lots of callers?
- acjohnson55 5y agoThe same way we do now: release a new version and tell people to migrate.
- lisper 5y agoNo, now I can just redefine the buggy function and all the callers will get the new version automatically. Having to update all callers seems like a high price to pay. Seems like the Right Answer is something like a hash of the api or the contract rather than the implementation.
- ucarion 5y agoThe (public) name of the implementation is the unique identifier of the contract in most systems, so I think your "Right Answer" is roughly the status quo?
- lisper 5y agoNo. The name of a function in current languages has no connection at all to what the function does. (What would be the contract for a function named ‘foo’?)
- gbhn 5y agoThat's kind of the thing that makes APIs possible, right? It sounds to me like "what if programming were done in a completely flat global namespace in which abstractions, encapsulation, and structure were impossible."
- lisper 5y agoNo. An API specifies more than the name of the function. It will specify the arguments, their types, the type of the return value, and at least informally, what the function does. You can change the underlying implementation without changing the API. That's the whole point of an API. The problem with current API technology is that the informality of the spec of what the function does. That allows some aspects of the behavior of the function to change without triggering any warnings. By having the linker work on hashes of implementations you eliminate that problem but create a new problem. You can no longer change the behavior of the function because you can't change the function. That means you can't suddenly change behavior that some caller is counting on, but it also means you can't fix bugs without changes in the caller.
- tgbugs 5y agoReading this now, I'm imagining all the horrors of static linking but applied to every function instead of whole modules. Maybe the simplest solution is to allow the function to change to the new version, but make it easy to revert in the event that something breaks. This of course means that you can't make the names of the functions their hash (without lying, preventing the runtime from checking that hashes ways match, or modifying emitted bytecode or native code to do what you want), it has to be an orthogonal layer on top of them like types (as I mentioned elsewhere in the thread).
- umvi 5y agoThat's basically what the twitter thread I linked explains. It sounds like there is an automatic propagation mechanism for updating downstream callers if the type hasn't changed, otherwise it sounds like a manual update process.
- torginus 5y agoSounds like trading one set of problems for another.
- capableweb 5y agoWelcome to software engineering where there is no golden bullets, only different tradeoffs :)
- nytgop77 5y agoThere are also circles of hell. (opposite of silver bullet)
- aparsons 5y agoLaw of conservation of complexity
- vanderZwan 5y agoTangent: here's what I find fascinating: when we evaluate which algorithm or data structure is best to handle a given situation, we know how to reason about algorithmic complexity and pick a best option for our situation. But then when it comes to ideas like this we just tend to say "we're trading one set of problems or another", as if we can't evaluate the problems in a similar manner. And I'm not picking on you here, I tend to do the same! Yes, we're trading one set of problems for another, but what if the old set of problems was "O(n²)" and the new set of problems is "O(nlog(n))"? Or maybe it's the other way around. Why isn't it obviour how to apply those earlier skills here?
- nytgop77 5y ago
- mpweiher 5y ago> identified by a hash of its implementation Sound a lot like darklang. Like others, I am dubious about this being in any way a useful feature. Separating implementation from name (/interface) and binding to that interface/name instead of the implementation is one of the fundamental and useful parts of abstraction.
- dcposch 5y agoCool to see people thinking this big! One challenge I foresee is unintentional coupling. Say you have two functions: func serialize(MyRecord) ... func debugToString(MyRecord) ... Now if you ever make the mistake of having giving those the same implemention, then in Unison they'd be the same hash reference, right? Then if you want to update, say the debug print later it would update all callsites for that hash including the ones that originally called serialize(). The two are no longer distinguishable.
- sgk284 5y agoThe names are just pointers, and they're both pointing to the same definition in your example. But when you redefine one of those, you would point one of the names to a new definition. It's similar to how DNS can have two domains point to the same IP, but then you can change one of those domains point to a new IP.
- ajuc 5y ago> The names are just pointers, and they're both pointing to the same definition in your example. But when you redefine one of those, you would point one of the names to a new definition. But how do you know which name was called where if the callers referenced the content hash not the name?
- aparsons 5y agoWould it not be correct for those callers to keep calling the old (shared) implementation?
- ajuc 5y agowell it would be nice to have a way to update old code
- milansuk 5y agoI also think that the DNS analogy is wrong because all callers are hash-based. The only solution I see is to go through the list of all callers and manually update selected ones. If I understand Unison right, the names are used only on the developer's layer(to write code), but when you save code, it's all hash-based. Still, Unison got my attention.
- andi999 5y agoCool, but why/what for?
- umvi 5y agoI'm just as much of a novice as you, but one of the use cases the creators had in mind are distributed computing systems. For example, if you have to crunch a bunch of data in the cloud, you would write your data crunch function/algorithm (which is represented by some hash '#asdjh238ad') then spin up nodes to crunch data using '#asdjh238ad'. When a new node in a cluster comes up it can say "I don't have '#asdjh238ad'" and the orchestrator or one of the node's peers can send over a copy of it. With a traditional programming language you couldn't do this because "send me a copy of sort()" would be met with "which sort()?". Whereas with unison every different sorting implementation would have different hash, so there would be no confusion.
- Nullabillity 5y agoThat makes sense as a build system (and is more or less how Nix works). The question would be why you'd subject your source code to this.
- mst 5y agothere was a paper that implemented an r7rs compatible module system for termite scheme that used hashes for identification for netework transfers of code but left the source files still normal - I think focusing on the textual representation too much misses the point a bit here.
- torginus 5y agoI'm not totally convinced by this. - Storing the AST on the disk in a million files is not necessarily the best use of the filesystem. In contrast, most languages store text files on the disk, and build up a similar AST in memory only - You can't view your code without special tools, which means all text editors/version control etc. need to be Unison-aware - Since the language is append only, all edits look like additions in version control - Their solution for the diamond problem (depending on multiple versions of the same library) is having hard dependencies on exact versions, and including both copies can be at best wasteful, at worst bad (what if v2 fixes a bug that was in the v1 dependency), I think this is a hard problem, and the reason why semver exists - As others have mentioned, the append-only nature of the language makes bugfixes difficult - Solutions that dynamically discover code dependencies and automatically run tests exist for both procedural and functional languages - Detecting that 2 things are the same through hashing is nontrivial, can it detect that 1 + x + 1 is the same as x + 2? The ASTs are different
- lamontcg 5y agoLoading both copies of a library can be very useful to deal with the situation where one piece of code has been ported to v2 (due to bugs/features or just generally keeping up with updates) and another piece of code is hard blocked on the v1->v2 migration because it is much more costly, and its possible that v2 is actually buggier for that other use case. There's a bit of a naive idea that software always gets better for everyone and that projects have an infinite amount of spare time to drop everything to bump dependencies. That feature is actually very useful. (Which is not to defend the rest of the append-only immutability of the rest of the language, that looks a bit whack -- but then I've seen whack stuff get wildly popular, so I have no idea -- but while having 2 versions loaded at the same time might be useful I'm not sure I want to deploy every version that has ever existed that smells way too bloated)
- torginus 5y agoYou are right - but choosing the correct solution imho needs to be done with human oversight - I think a semver based dependency resolution works great here, for example if bar requires foo 1.0 and baz needs 1.0.1 they will happily use the same version, but if baz used foo 1.1 they would use the separate ones.
- tgbugs 5y agoInteresting. You can write a macro and some buffer modifying code to do this in elisp. But having now written up the rest of my response, why not just use Smalltalk? The hard part is coming up with the normalization routine which guarantees that (lambda (a) b a) == (lambda (b) a b) and coming up with the rules for statement reordering for top level and internal definitions so that you can identify semantically equivalent statements where the outcome is order invariant. This is critical for making the hash functions useful and I suspect preventing denial of service attacks on the human brains that have to audit the code. Being able to write a version of the code and then do the equivalent of creating a package.lock file to crystallize the hashes seems like a reasonable workflow. This probably winds up being easier in common lisp though since you can put the crystallized implementations in their own packages. You could also view this as a kind of extreme type theory where every function (with regular names) has the type of its normalized representation (compacted to a hash for sanity's sake) and then you can run the checker to see if the types/hashes have changed. If you have somewhere that keeps track of every hash that a function with a particular name has had then you could automatically refactor, or could even support having multiple versions of the function with the same name used in a program at the same time. I'm not sure how users would feel about having to carry around `(funcall ((with-norm-id '(lambda (+ a b)) f)) a b)` though ... probably just give up on editing the textual representation and go back to the image based approach of Smalltalk and Interlisp where you can hide the hashes. Will be interesting to see how Unison evolves.
- taneq 5y agoWhy not just identify it as the function text at that point?
- fouc 5y agoChecked the twitter thread and I was thinking it sounds a lot like how strings are linked lists in elixir.
- remram 5y agoThis reminds me of Kubernetes, where all cluster state is neatly structured and placed in a replicated data store (etcd) that is the source of truth for operation, with the right parts immutable (e.g. volumes). The first thing people do is check in textual representations of those things in version control and operate on that instead.