Hacker News new | ask | show | jobs
by KPGv2 856 days ago
Hello! I'm a user of Unison. Been toying with it off and on since I heard about it maybe spring of last year from a Reddit post on the functionalprogramming sub.

As a somewhat stay-at-home dad I was looking to do something fun with programming, and ideally something where I could make an early impact. About the same time, I read about Tree Sitter Grammars and was looking at Lapce, a Rust IDE in its early stages, and uses TSGs for syntax highlighting.

So I ended up learning everything about the Unison syntax, going so far as to learn me a Haskell for great good to produce the TSG for Unison.

About that time, Unison started opening up early testers of Cloud. I passed because I hadn't really written much code in Unison. I'd just been writing the TSG (in C++ and JS).

But then I picked a project: write an implementation of Philips Hue's bridge API in Unison. In the process, I learned about server-sent events and wrote a library for that and released it on Unison Share, which I think of as a mix of Github and NPM (or Maven, or pypi, etc.). I also wrote a MimeType typings library.

I can't speak to the cloud stuff yet, and when I do use it, I won't have much to compare it to because my ops exposure in my profession (as a stay at home dad, haha) is limited.

That's my background, and here are my thoughts:

First, the Local UI for browsing your code and documentation is hands down the best I've ever seen. Everything you write is browsable there, and has hyperlinks to everything else. It's so money. And there's a `Doc` type as well, so you can write something like

  {{
    The first parameter of {term Internal} represents a count, and the second parameter represents a distance in 1-space.
  }}
  type Foo = Internal Nat Int

Then you can `add` and `Foo` will be stored in the current namespace, but so will `Foo.doc`, which is the content inside `{{ ... }}`. You can then delete this code from your scratch file and never think about it again.

If you browse the Local UI (you type `ui` in your `ucm` instance and it auto-loads in a browser), you can easily view the type, the doc above it, and you can click `Nat` or `Int` to be taken to the definitions of these in the base library, located at `lib.base` (`lib` is like your dependencies, like `node_modules` in JS, e.g.).

Say you later want to add a third type parameter. `edit Foo` and the current definition will be pretty printed to your scratch file. Then you can edit it, and run `update`. Anything relying on this type that can be migrated to the new definition will, and anything that can't automatically be migrated will get dumped to your scratch file for you to update manually. Once you have no more errors in your scratch file, `update` will finish it.

This feels a lot like the process of `git rebase --continue` until everything is consistent. Except here it's the code itself that `ucm` understands, not text data that `git` doesn't understand beyond "this is text with conflicts."

From one `ucm` instance, I can switch between projects. No managing folders on my computer in `/Users/foo/workspace/foo-project`, etc.

Anyway, the long and short is that once I got used to working this way, I immediately wished this existed for TypeScript as well, bc that's what I do so much of my work in. The doc generation is incredible, the source browsing is so good, and the process of updating my code is really slick. A few versions ago, it was less so, but it's been improved since then and now I really like it.

Pushing code is as easy as `push`. You can create releases of your libraries or applications by going to Unison Share, finding your project, and navigating through the simple "cut a release" wizard.

There are even types for License, CopyrightHolder, etc. so metadata about your application can be done in code. For example, the license for my mimeType library is

  LICENSE : License
  LICENSE = License [copyrightHolders.kpg] [Year 2024] mit

The type for `License` is defined as `License [CopyrightHolder] [Year] LicenseType. There are pre-configured license types in the base library, and `mit` is one of them.

I find this to be a nice addition as well, although for many this is something to be ignored. But I like the idea of encapsulating so much of a project in code rather than in things like a `package.json` file that is brittle.

The one other thing I'd like to mention is abilities. It was hard to wrap my head around them at first. I'm really familiar with monadic programming. My coworkers might say I'm too in love with it :) Wrapping my head around abilities was hard at first. They're kind of like...monads, DI, and interfaces all sort of mixed together. But there's essentially two components: the ability, and the ability handler. The ability is like you defining an interface. Any handler needs to know how to handle any of the "requirements" of the ability. For example, you might write an application that communicates with an api for example.com as

  ability Example where
    getAll : '{Example} [Foo]
    get : FooId -> {Example} Foo

You're somewhat defining an interface that handlers need to conform to (i.e., they must have code that handles each of the "ability requirements"). Your handler then essentially converts your abilities to more fundamental ones, or removes them completely. You're generally working your way, in an application, down to only IO and Exception abilities (there's probably some cloud abilities I am not familiar with), which UCM handles natively.

Your handler is like the implementation of an interface.

From there, you can write code using anything in that ability, and so long as some ancestor function call wraps all that in a handler, everything just works. It kind of acts like injecting your handler as a dependency of everything that is a descendant function of the handler.

I don't know if I'm effectively communicating how this works, but it makes sense for me. Those are the analogues I'm familiar with that I used to understand the ability system.

Now that I feel comfortable with it, it's pretty cool!

Edit: My final thoughts is that the language is really nice to use (though there are some things from TypeScript I miss, they're very few, and it's certainly superior to something like Java IME). It's nice the VCS and documentation/code browser is built in. Being able to push to a repo, again built into the ucm program, is convenient. Everything is wrapped up nicely. And the company behind the language is extremely online and repsonsive. I've gotten so much help from them. I wish I could speak to the cloud offerings, but I haven't worked with it yet.