For me, Unison is basically inscrutable. I know that part of it is that functional programming is 100% completely brand new to me, so there is just too much to take in all at once.
One of my retirement projects is to learn functional programming. I’m hoping that will be enough to get me far enough along to be able to figure out the other parts.
I don’t think, it’s useful to hyper-optimize for Hello World, just so it looks simple (when it’s not).
Most learners will know what these syntax elements mean quite quickly. And from that point forward, they will find the language simpler, because there’s no special rules when precisely you’re allowed to leave out e.g. type annotations, even though any real program always wants to have them.
I don’t think it’s useful to hyper-optimize for Hello World
I agree. I was more referring to the various symbols which don’t appear to have a clear purpose. They raise questions for me. Here are some of them:
Why is helloWorld there twice?
What’s the ' for?
What are the () for?
Why is there an _?
I’m aware that you can just look these things up, but there’s at least 4 mildly confusing things in something that is rather simple in every other language.
Take Rust for example. I’ve heard people say it’s complex, yet it’s printline still simple:
println!("Hello");
Coming at this with the same lens, the only mildly confusing thing is the !.
I’m not saying “how Hello World is written is the be all and end all of language complexity”, but for a task that’s so simple in basically every other language to look like that in Unison, it just doesn’t fill me with confidence for the friendliness rest of the language.
async is what the ' in Unison is. I guess, because it is so commonly used in Unison.
And Rust decided, if the println!() fails, then panic!(). But I can see merit in a language that can guarantee that a given function cannot terminate your program. And that would mean we’d have to at least ignore the result in Rust:
And like, yeah, are you seeing the parallels? It’s a handful of small design decisions that make a huge difference here.
And you can absolutely argue that Rust took the less rigorous, less consistent approach for its println!().
I do think, that’s fine. Rust doesn’t give guarantees for not terminating to begin with and so, if println!() should ever not work, I do think, it’s fine to abort mission.
And the decision to not make println!() async doesn’t affect the remaining language design. Most production codebases will practically never use println!() anyways and should someone genuinely need an async_println!(), then that can be built.
But yeah, personally, it does excite me to see that the Unison authors thought differently. Even if it’s just sheer curiosity what they do with it and whether that added consistency is nice to work with.
But yeah, personally, it does excite me to see that the Unison authors thought differently. Even if it’s just sheer curiosity what they do with it and whether that added consistency is nice to work with.
This is what attracted me. Before I sold out to VB and Access in the early 1990s, I was just a labourer and hobby programmer of the “language junkie” type. The same three programs written in every language I could find (Metaphone, Pong, and Rolodex).
Now I’m retired (but 10 years away from programming) and want to try to reboot that hobby. I thought I was just making a tiny little complaint about my aged brain, not opening up a whole thread!
No worries, I think everyone’s having a grand time discussing Unison. I’m guessing, this whole post attracted lots of language junkies for obvious reasons…
You’ve done a great job of showing what it feels like when I see Rust sometimes. There’s something about the way that it “builds up” on the page over time…
I’ve never written any Rust - I’ll get to it one day! - but I found this article interesting look at how and why design decisions effect the final syntax: https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html
Oh, well, I hope, it was clear that the third and fourth code sample aren’t actually valid Rust, but yeah, I did write them to look like typical Rust production code.
And yeah, that article is great. I feel the same way, the semantics are just different. And whether you like those semantics, depends on what you’re doing.
If you’re just writing a quick script where you don’t really need error handling and you probably won’t have more than a few files etc., then Rust’s semantics can be painful.
But if you’re writing a larger application, then Rust’s strictness and explicitness often just portrays the reality you have to deal with anyways. And it makes you deal with it right away, which can be frustrating at times, but overall feels like it boosts my productivity.
Alright as someone who likes Haskell and has dabbled in unison before, I believe I can answer all these questions for you:
Why is helloWorld there twice?
It is common in languages like haskell and ocaml to first mention the type of a function, so in this case:
the type of helloWorld is '{IO, Exception} (). That is it’s type signature (important for later)
the implementation of helloWorld is \_ -> println"Hello, World!"
What’s the ' for?
What are the () for?
Here is where I have to get into the nitty gritty of how unison actually works. Unison has what programming language researchers call an effect system. The type signature of helloWorld indicates that it can perform the IO and Exception types of side effects, and these need to be handled. (in this case, they are handled by the compiler, but other types of side effects can be handled by the programmer themselves)
However, for reasons Unison does not like dealing with eagerly evaluated non-function values with side effects. For this reason, there is '. Essentially, what it does is turn a value into a function that accepts () as it’s argument. We could therefore say that the type signature of helloWorld is also () -> {IO, Exception} (). The last () indicates that, next to it’s IO and Exception side effects, it also returns () as a value. This is because, in functional programming languages, all functions need to return values (or run infinitely, but that is for another topic)
Now I’ve been used to functional programming for quite a while now, so things that seem natural to me can be absolutely woozy for anyone not used to this paradigm. So if anything still feels vague to you feel free to comment
… Maybe I’m too Haskell brained to understand why this is a problem, haha. That looks fine? I don’t know Unison at all, but you can probably elide the type annotation too?
For me, Unison is basically inscrutable. I know that part of it is that functional programming is 100% completely brand new to me, so there is just too much to take in all at once.
One of my retirement projects is to learn functional programming. I’m hoping that will be enough to get me far enough along to be able to figure out the other parts.
Oh god, they say it’s a friendly language but their hello world is:
Well, it always depends, to whom it’s friendly.
I don’t think, it’s useful to hyper-optimize for Hello World, just so it looks simple (when it’s not).
Most learners will know what these syntax elements mean quite quickly. And from that point forward, they will find the language simpler, because there’s no special rules when precisely you’re allowed to leave out e.g. type annotations, even though any real program always wants to have them.
I agree. I was more referring to the various symbols which don’t appear to have a clear purpose. They raise questions for me. Here are some of them:
helloWorld
there twice?'
for?()
for?_
?I’m aware that you can just look these things up, but there’s at least 4 mildly confusing things in something that is rather simple in every other language.
Take Rust for example. I’ve heard people say it’s complex, yet it’s printline still simple:
println!("Hello");
Coming at this with the same lens, the only mildly confusing thing is the
!
.I’m not saying “how Hello World is written is the be all and end all of language complexity”, but for a task that’s so simple in basically every other language to look like that in Unison, it just doesn’t fill me with confidence for the friendliness rest of the language.
Ah, now you’re talking my language. 🙃
Well, for one you’re forgetting the main():
fn main() { println!("Hello, World!"); }
Then Rust already has syntactic sugar to omit the Unit-return-type, so it’s really:
fn main() -> () { println!("Hello, World!"); }
And then Rust decided to make println!() blocking. It could also look like this:
async fn main() -> () { println!("Hello, World!").await; }
async
is what the'
in Unison is. I guess, because it is so commonly used in Unison.And Rust decided, if the println!() fails, then panic!(). But I can see merit in a language that can guarantee that a given function cannot terminate your program. And that would mean we’d have to at least ignore the result in Rust:
async fn main() -> () { let _ = println!("Hello, World!").await; }
And like, yeah, are you seeing the parallels? It’s a handful of small design decisions that make a huge difference here.
And you can absolutely argue that Rust took the less rigorous, less consistent approach for its println!().
I do think, that’s fine. Rust doesn’t give guarantees for not terminating to begin with and so, if println!() should ever not work, I do think, it’s fine to abort mission.
And the decision to not make println!() async doesn’t affect the remaining language design. Most production codebases will practically never use println!() anyways and should someone genuinely need an
async_println!()
, then that can be built.But yeah, personally, it does excite me to see that the Unison authors thought differently. Even if it’s just sheer curiosity what they do with it and whether that added consistency is nice to work with.
This is what attracted me. Before I sold out to VB and Access in the early 1990s, I was just a labourer and hobby programmer of the “language junkie” type. The same three programs written in every language I could find (Metaphone, Pong, and Rolodex).
Now I’m retired (but 10 years away from programming) and want to try to reboot that hobby. I thought I was just making a tiny little complaint about my aged brain, not opening up a whole thread!
No worries, I think everyone’s having a grand time discussing Unison. I’m guessing, this whole post attracted lots of language junkies for obvious reasons…
You’ve done a great job of showing what it feels like when I see Rust sometimes. There’s something about the way that it “builds up” on the page over time… I’ve never written any Rust - I’ll get to it one day! - but I found this article interesting look at how and why design decisions effect the final syntax: https://matklad.github.io/2023/01/26/rusts-ugly-syntax.html
Oh, well, I hope, it was clear that the third and fourth code sample aren’t actually valid Rust, but yeah, I did write them to look like typical Rust production code.
And yeah, that article is great. I feel the same way, the semantics are just different. And whether you like those semantics, depends on what you’re doing.
If you’re just writing a quick script where you don’t really need error handling and you probably won’t have more than a few files etc., then Rust’s semantics can be painful.
But if you’re writing a larger application, then Rust’s strictness and explicitness often just portrays the reality you have to deal with anyways. And it makes you deal with it right away, which can be frustrating at times, but overall feels like it boosts my productivity.
Alright as someone who likes Haskell and has dabbled in unison before, I believe I can answer all these questions for you:
It is common in languages like haskell and ocaml to first mention the type of a function, so in this case:
helloWorld
is'{IO, Exception} ()
. That is it’s type signature (important for later)helloWorld
is\_ -> println "Hello, World!"
Here is where I have to get into the nitty gritty of how unison actually works. Unison has what programming language researchers call an effect system. The type signature of
helloWorld
indicates that it can perform theIO
andException
types of side effects, and these need to be handled. (in this case, they are handled by the compiler, but other types of side effects can be handled by the programmer themselves)However, for reasons Unison does not like dealing with eagerly evaluated non-function values with side effects. For this reason, there is
'
. Essentially, what it does is turn a value into a function that accepts()
as it’s argument. We could therefore say that the type signature ofhelloWorld
is also() -> {IO, Exception} ()
. The last()
indicates that, next to it’sIO
andException
side effects, it also returns()
as a value. This is because, in functional programming languages, all functions need to return values (or run infinitely, but that is for another topic)Now I’ve been used to functional programming for quite a while now, so things that seem natural to me can be absolutely woozy for anyone not used to this paradigm. So if anything still feels vague to you feel free to comment
… Maybe I’m too Haskell brained to understand why this is a problem, haha. That looks fine? I don’t know Unison at all, but you can probably elide the type annotation too?