25 karma · joined December 5, 2021
The Database abstraction helps scope and namespace (potentially many) tables. It is especially important in scoping transactions, since one of the things we wanted to support with our storage primitives is transactionality across multiple storage types.
The browser or mobile apps cannot execute arbitrary code on the server. Those would typically call regular Unison services in a standard API.
There are indeed tradeoffs; as an example, one thing that trips folks up in the "save typed values without encoders" world is that a stored value of a type won't update when your codebase's version of the type updates. On its face, that should be a self-evident concern (solvable with versioning your records); but you'd be surprised how easy it is to `Table.write personV1` and later update the type in place without thinking about your already written records. I mention this because sometimes the lack of friction around working with one part of Unison introduces confusion where it juts against different mental models.
Other general tradeoffs, of course, include a team's tolerance for newness and experimentation. Our workflow has stabilized over the years, but it is still off the beaten path, and I know that can take time to adjust to.
I hope others who've used Unison will chime in with their tradeoffs.
I'll speak a bit to the language audience, and others might weigh in as they see fit. The target is pretty broad: Unison is a general-purpose functional language for devs or teams who want to build applications with a minimal amount of ceremony around writing and shipping applications.
Part of the challenge of talking about that (the above might sound specious and bland) is that the difference isn't necessarily a one-shot answer: everything from diffing branches to deploying code is built atop a different foundation. For example, in the small: I upgraded our standard lib in some of my projects and because it is a relatively stable library; it was a single command. In the large: right now we're working on a workflow orchestration engine; it uses our own Cloud (typed, provisioned in Unison code, tested locally, etc) and works by serializing, storing, and later resuming the continuation of a program. That kind of framework would be more onerous to build, deploy, and maintain in many other languages.
Unison does diverge a bit from the mainstream in terms of its design. There's a class of problems around deploying and serializing code that involve incidental complexity and repetitive work for many dev teams (IDLs at service boundaries and at storage boundaries, provisioning resources for cloud infrastructure) and a few "everyday programming" pain points that Unison does away with completely (non-semantic merge conflicts, dependency management resolution).
We wrote up some of that here at a high level: https://www.unison-lang.org/docs/what-problems-does-unison-s...
But also, feel free to ask more about the technical specifics if you'd like.
The projects in a codebase can absolutely be shared and versioned as well. Here's a log of release artifacts from a library as an example: https://share.unison-lang.org/@unison/base/releases.
You're spot on with shifting the complexity to the cloud layer; our thought was that it would free up time for dev teams to focus on other layers of their application.
Maybe I'm misinterpreting your question though, I'm happy to annotate that code snippet with a walk-through.
1. Unison terms are stored as hashes so checking in a binary file wasn't very ergonomic and didn't really enable much in terms of collaboration. If we store our code as text on the file system, we have less information than what's tracked in the Unison tooling, since the plain text version isn't aware of its dependencies. 2. Unison's versioning system is more syntactically aware than Git's since its granularity is based on the definition of your functions and types, not incidental changes like whitespace or newlines.
You can of course, bring all the Unison code for a program into a text file (you write Unison code in your regular editor) and then check it in, but that's not as nice of a workflow than the one that's supported directly.
Unison's core difference is that your code is not stored as regular text files; instead your functions and types are stored as a hash of their AST. This enables nice dependency management workflow and makes things like renaming functions trivial. The thing to remember is that your functions are programmatically tracked. Based on that core difference we built a platform that can deploy those arbitrary hashes (and all their dependencies) to different locations in a cluster, and we created a Unison library so that folks can describe how their code should be shipped across cloud computing resources. So you have Unison code describing and orchestrating Unison services.
The process for upgrading Unison code is that if you have a function A that calls B, and you update B in some way, as long as the change is type-preserving it will automatically be propagated to all the sites in your project where B is called. If the change is not type-preserving (for example, if you added a parameter) the tooling itself will direct you to resolve all the places where B is applied. So as you change code locally, you're continually keeping your codebase in sync; A will always be calling the updated function.
Here's an example from our docs: https://www.unison-lang.org/docs/usage-topics/workflow-how-t...
Some basic IO functionality is supported by the `base` library (the standard lib) which contains functions like `openFile` which are effectful functions expressed in terms of the `IO` ability.