That said, I had to dig in the documentation to find code, and it didn't seem as obvious cause the syntax is definitely different from what I'm used to and there's weak syntax highlighting.
https://www.unisonweb.org/docs/language-reference#a-note-on-...
The Nim language in 2008 (when it was known as Nimrod) had originally been planned a similar approach of considering a unified AST with multiple "syntax skins," and as far as I understand there used to be a limited implementation of this. This would theoretically have been a boon for Nim, because an often complaint about Nim from emigrating programmers from languages with a C-style syntax was the (rather superficial) complaint - "no braces? Pythonic syntax? Yuck!"
https://forum.dlang.org/thread/mailman.1427.1428691340.3111....
The problem with this - and why Nim never really committed to the "syntax skin" concept - is that it would have led to far much fragmentation within Nim's own community as some users would prefer one "sub-language" over another. Given as it is the opinion of new languages (Nim included) towards a tool-based unified syntax formatting (e.g. certain naming conventions for identifiers, number of spaces for indent), "syntax skins" have become harder of a sell in favor of lingual consistency.
The original idea was that the same program could be rendered in a variety of different syntaxes in the author's IDE - but the implied maintenance cost of such skins plus that previously mentioned potential for fragmentation led to this idea falling to the wayside. Nim, as a language, grew to be quite flexible within its prime syntax over the years - e.g. the macro system (which modifies the AST directly) made way for a variety of DSLs (e.g. https://github.com/khchen/wNim#code-examples) that remain valid Nim code.
Wolfram Mathematica (or "the Wolfram language") introduced "forms" for that, for instance https://reference.wolfram.com/language/ref/FullForm.html or https://reference.wolfram.com/language/ref/InputForm.html or, quite charming in the math context, https://reference.wolfram.com/language/ref/TraditionalForm.h... or https://reference.wolfram.com/language/ref/TeXForm.html -- See also https://reference.wolfram.com/language/tutorial/EverythingIs...
Because when I see Chinese characters I know immediately if it's interesting to learn Mandarin or not.
It's almost like taking test drives when you go car shopping. You don't want to open the hood and check what's inside right away you just want to be able to drive the car.
One problem I did find: Somewhat deeper into the tour, the tour makes the claim:
> a Unison codebase can be versioned and synchronized with Git or any similar tool and will never generate a conflict in those tools.
This is pratically speaking of course not true: If you have some unison codebase, whilst under the hood it's all hashes, that doesn't change the simple fact that if 2 people both check out the exact same code, and then both people make a change to how the software operates, where that change is conflicting (say, I make the green 'OK' button now red, and you make it blue), you.. have a conflict. That is a fundamental property of this interaction; no language bells and whistles can ever change this fact.
So, what does happen in unison? Is it 'last committer' wins? That'd be a bit problematic. A conflict in your version control system, _IF_ it is representative of the fact that there was an actual conflict (as above with the button and the conflicting colours), is a good thing, not a bad thing.
I did not fully read the introduction yet, but in my mind, in a truly content-addressed system this is not a conflict: you have the hash of a main() function which ultimately makes the button red, and the other guy has a main() function that makes the button blue. No conflict in the physical sense. Yes, philosophycally there is a conflict, which is resolved by you deciding which main() function you find more useful.
Developer A makes the button red, developer B changes the label from "OK" to "Accept", both inside the same function.
You can't just pick one or the other, you have to combine the changes.
Or two developers add two different fields to a type. Or ...
Sadly the docs only have placeholders for "Concurrent work and resolving edit conflicts" and "Pull requests and collaboration". Would be very interesting to read the developers thoughts on this.
The system seems to support patches, forking and merging.
The most hopeful case is that there is ONE place that isn't 'strictly append only', and that's, for lack of a better term, the 'main pointer'. Which hash is the actual state of the app as it should be compiled and deployed right now?
Then THAT conflict, together with tooling to diff 2 different hashes, should swiftly lead to figuring it out.
But then you're still kinda hosed; how do you merge 2 nodes? It sounds like you can't; you can only pick one. With git, you can do a text merge.
I get the beauty of AST based storage and reasoning, but, hey, trying to use git to merge 2 versions of a word doc together is also a disaster, so it sounds like it'd be the same here.
In that sense, unison is worse than your average (text based) language, not better.
That's not actually a downside though; it's different. If I demerited the language for this, that's akin to complaining about a delicious roasted bell pepper because it doesn't taste anything like an onion.
But I do find it a spot irksome that the otherwise well written tour is implying something here that doesn't appear to be true. Perhaps I'm merely misinterpreting it and reading more into that sentence than is implied.
Developer A made a change which adds feature X and makes a button blue.
Developer B made a change which adds feature Y and makes a button green.
Now the system does not allow both feature X and feature Y at the same time. Our options are either drop X, drop Y, or expend more effort/programming time to make a version which includes both X and Y.
The description above applies equally well to Unison code CAS system and regular program in git. Sure, one is at the file level, and other at the function level - but you still need same kind of tooling. You want to look at the changes and somehow produce a merged version.
it's not just philosophical though. In git terms, when merging the two sets of changes, someone has to choose which of these functions goes onto the master branch. git calls this a "merge conflict".
That's how conflicts are avoided.
I wonder what would happen if we would change one thing at a time to test the idea: create something like unison based on an existing popular programming language. (python, javascript, java, etc...) All the syntax handling, building and version control would be done the unison way, but the interpreter would be the same as today in that popular language. The benefit would be that a lot of existing code could be transferred into the initial 'blockchain', with all the current dependency hell being solved from day one. I did not think this through deeply though... Maybe some aspects of existing languages prevent them to be handled the 'unison way'?
If you're using a language that doesn't need a AST/where the AST is the same as the source code, I'm sure we could pull out more benefits of this approach. Seems one would need a lisp or lisp-like language that is homoiconic.
My (admittedly little) experience with Unison though is that it's far from ready for the spotlight however. Much of the docs 404, and joining their discord mostly resulted in the advice to wait for future releases.
I wish Microsoft would invest in something like the koka language https://github.com/koka-lang/koka. Perhaps the idea of content-addressable / immutable code could find a place too, but I think it's less critical.
------ EDIT: My tone here is a little more antagonistic that I meant (proofread kids, don't just check for spelling and publish) but my first impression was a kind of "buzzword-y" vibe. I've looked a bit more and the authors seem genuinely passionate about shaking things up and trying something completely different, which I can respect. I'll leave it up as is because I still think my issue are legitimate, but the tone is a little off and I apologize. ------
Let's take my discovery path so far as an example:
> Functional language: cool cool. I know what that is. Immutable: also cool. Content-addressable: no clue. Saying it'll simplify code bases and eliminate dependency and "no builds" is supposed to sound amazing, but it really doesn't land when I have no clue what the premise is.
> Alright, that's it for the front page. No code examples, nothing linking the currently known to this new paradigm-shifting stuff. K. Let's try the docs.
> The Welcome is the state of the project without saying anything about the language itself. Roadmap isn't gonna help Quickstart! Let's look there.
This amount of effort for discovery isn't very helpful for me. I found a selection of talks elsewhere on the site. One of them is labeled "This is a longer (40 min) introduction to the core ideas of Unison and probably the best talk to start with." That might be a nice link for the front page.
I unfortunately don't have anymore time to look at this this morning, but the first 5 or so minutes of the talk say waaaaaaaaay more than the first however long I spent. Honestly, I think all of that information (code examples, linking core ideas to what we already know, basis of comparison) could be on the front page.
The tl:dr I can glean from it is:
It's kinda like Haskell, but every definition is turned into AST and put in a hash table. Any references are secretly references to the table, so changing a definition is making a new hash and changing the reference, and renaming is changing the reference to name table.
Also, everything is append instead of rewrite, which makes all kinds of stuff cleaner, like versioning and refactoring.
Pretty neat idea, I just don't really like how long it took me to come to that knowledge. I don't know how well it would scale or be usable in a real world scenario, but I also have never used Haskell or any functional language in a "real world" scenario so I'm not the best one to ask.
Definitely appreciate this honest and helpful, specific docs feedback, so thanks for taking the time to put it together. We do want the value of Unison to be straightforward given even a quick skim of the site, so: sorry about the current state, we'll try to improve it. :)
Just by way of anchoring: I’m no dunce, I’m a typical nerdy guy who knows his UNIX and has written lots of C, Python, JavaScipt, Ruby, etc. Written some mobile apps. Written plenty of web stuff. But I’m not immersed in any academic language study and not really an FP guy.
I don’t think you should dumb this down - the text itself is very good prose. But the context is missing something — perhaps a paragraph or two of anchoring for us “normal programmers” to explain why this is relevant to people who normally write Python web apps or Rust utilities or C++ games all day long (or if it’s not relevant, why, and who it is relevant for).
Good luck with the project!
Beyond just the idea of a global program store (which I hope has space for some virtualenv equivalent), the cleverness of effect handlers as a first-class language feature is very exciting.
Once the type system is more mature, this could easily be the next kind of Haskell - a language which redefines modern ideas of good programming.
Forth ideas: a global dictionary with pre-existing and user-definable words
Haskell ideas: functional programming and type signatures of functions / words
Clojure Datomic ideas: immutable store of object definitions that append and use references to point to the latest definitions.
I am curious how I/O and error handling are done in Unison.
The short summary is that you can write code with side-effects that you don't define the implementation for, and then let call sites handle those effects or defer them.
[1] https://www.unisonweb.org/docs/language-reference#abilities-...
You can have a decades-old git repository containing hundreds of MBs of files, but output a 2kB program. Someone correct me, but it sounds like if you had the same thing with a unison project, the actual executable size can only grow?
Large quantities of code can already be compiled to WASM. Since Unison works best with pure functions, most system interfaces would presumably be emulated. This emulation is already possible with Browsix.
Highly recommended.
[0] https://joearms.github.io/published/2015-03-12-The_web_of_na...
> Once we have the SHA1 name of a file we can safely request this file from any server and don't need to bother with security.
> For example, if I request a file with SHA1 cf23df2207d99a74fbe169e3eaa035e623b65d94 from a server then I can check that the data I got back was correct by computing its SHA1 checksum.
—-
that’s crazy! i’ve had the same thought myself, and wondered why no one had done something like that... i hope this becomes more and more common (though not using sha-1 ^^)
I've wanted to see a Merkle tree based AST for faster incremental compilation (and incremental static analysis).
I wonder if the source manager interface makes this easier to use than editing source files?
AST can still be the source of truth. To edit need to generate source text from AST, edit as text, parse to AST, save.
In other words, I think most of the benefits of this can live behind the scenes for other languages.
Seems like this could be implemented in other languages though, and don't really understand why we need yet another new language.
For instance, can I not build my JavaScript code in similar ways: I would need a compiler that stores things in trees rather than dirs.
So to get it to work with other languages, you would need a way to convert their abstract syntax into a DAG compatible structure. It may be possible to do so, but I doubt there is a general approach which will work across multiple existing languages. It would require per-language hacking, if it is even possible without rewriting some parts of code.
Seems like languages like OCaml and F# might be slightly more suitable for this approach because they require compilation units to be ordered by their dependencies, and thus promote a convention of avoiding cyclic dependencies between types (although it is possible to make such dependency in the same compilation unit with `type A and B`). They also allow recursive functions, but these should be simple enough to represent with a non-cyclic syntax tree.
If you design a language from scratch, you can simplify things by requiring that no cycles exist in the language - at least at the syntax tree level. You might be able to design representations for recursion at a higher level which collapse to non-cyclic structures in the AST.
1) If I fix a bug in a function, (thus creating a new version of an existing function) how do I propagate that fix to all that code that transitively depends on the buggy version? Doesn't that mean I need to create new versions of all the downstream dependencies? Doesn't that defeat separate compilation? Does this scale?
2) Does this closely couple interface and implementation? Is it a hash of an implementation, or a hash of an interface? Is it possible to depend only on an interface, and not on an implementation?
This can vary depending on how many dependants the code you change has. If you have some fundamental type which an entire codebase depends on, then modifying it will essentially require recomputing hashes for the most of the codebase. On the other hand, if it's something close to the entry point, you would only need to recompute a few hashes and the root hash which represents your entry point. Any parts which are unchanged effectively have their hashes cached.
> 2) Does this closely couple interface and implementation? Is it a hash of an implementation, or a hash of an interface? Is it possible to depend only on an interface, and not on an implementation?
I don't think unison has interfaces or any similar construct yet. There's an FAQ item mentioning why type classes are not included[1] yet.
Personally I think the right approach would be to use a structural type system similar to OCaml's. You would have functors which describe the code you're calling, and call sites would use the hashes of the functor definition. You can then define concrete representations of types independently of the functor, and instantiate them against the functor afterwards. Thus, if you change some implementation detail in a type, then the only hashes that will need updating are those of your concrete type and the instantiation of the functor against your type. The functor, and any code which depends on it, will remain unchanged.
Basically you can quote code (like in Lisp) and pass it to an executor (thread, remote server, ...).
The main difference is that thanks to the immutability, you can also have a protocol which shares efficiently all the dependencies required to execute the code (like a git pull).
It also uses an effect system, so the implementation does not need to decide which executor to use, it only uses an interface called an "ability", and the caller can choose the implementation.
Even the toy examples I'm having trouble following along with.
The benefit of code-as-text is that you can navigate the text without knowing what you are looking for.
This seems to take the opposite approach of making it very easy to look things up assuming you know ahead of time what you are looking for.
That is something that a good usage of grep or some more sophisticated IDE integration will already take care of for you.
The only thing I could see this being potentially useful for is "notebook" style computation, where you are really only using the language to do things like structured math calculations.
What am I missing here?
I think IDE tooling for Unison could pretty easily give you the exact same experience. Generate code from the AST, group it reasonably, and give you a view that is indistinguishable from a traditional source tree.
It might be non-trivial to structure things nicely, but I think theres a good chance your generated structure would be better organized than a traditional codebase.
My first thought was the same insight from von Neumann architectures: code is data. So I thought of package repositories with URLs corresponding to hashes of function definitions. http://unison.repo/b89eaac7e61417341b710b727768294d0e6a277b could represent a specific function. A process/server could spin up completely dumb other than bootstrapped with the language and an ability to download necessary functions. You could seed it with a hash url pointer to the root algorithm to employ and a hash url to the data to run against and it could download all of it's dependencies then run. I imagine once such a repo was up and running you could do something like a co-routine like call and that entire process just happens in the background either on a separate thread or even scheduled to an entirely different machine in a cluster. You could then memoize that process such that the result of running repo-hash-url against data-hash-url is cached.
e.g. I have http://unison.myorg.com/func/b89eaac run against http://unison.myorg.com/data/c89889 and store the result at http://unison.myorg.com/cache/b89eaac/c89889
Conflicts are a feature, not a bug. If two developers are making divergent changes in the same piece of code, I suppose Unison would just let the divergence happen, essentially forking the code. I don't think forking code is the right default.
I loathe marketing BS like this. No, you’re not from the future, you pretentious twats.
If you have a good idea, let it stand on its own merit, don’t dress it up with manipulative language.
Unison is a functional language that treats a codebase as an content addressable database[1] where every ‘content’ is an definition. In Unison, the ‘codebase’ is a somewhat abstract concept (unlike other languages where a codebase is a set of files) where you can inject definitions, somewhat similar to a Lisp image.
One can think of a program as a graph where every node is a definition and a definition’s content can refer to other definitions. Unison content-addresses each node and aliases the address to a human-readable name.
This means you can replace a name with another definition, and since Unison knows the node a human-readable name is aliased to, you can exactly find every name’s use and replace them to another node. In practice I think this means very easy refactoring unlike today’s programming languages where it’s hard to find every use of an identifier.
I’m not sure how this can benefit in practical ways, but the concept itself is pretty interesting to see. I would like to see a better way to share a Unison codebase though, as it currently is only shareable in a format that resembles a .git folder (as git also is another CAS).
[0]: https://www.unisonweb.org/docs/tour
[1]: https://en.wikipedia.org/wiki/Content-addressable_storage
[2]: https://github.com/unisonweb/quickstart
PS: The website is very well made, but I would like to comment that there should be two buttons on the top front page, the tour and installation, as I think most people want to have a glimpse of the PL before installing. I, at least was puzzled because I thought the button was a link to an introduction of the language.
A larger thread, also from then: https://news.ycombinator.com/item?id=9512955
What other user-facing open-source softwares have been written in Unison?
Which of those softwares has been used for months or years by at least one person?
No, as I keep insistently repeating: Code is content-immutable and addressed.
Also, Urbit is a lie, Surface detail is true and you are a lie and your code is retarted.