Stream: ideas

Topic: opaque bools


view this post on Zulip Richard Feldman (Jun 21 2022 at 20:36):

curious what anyone thinks about this idea!

https://docs.google.com/document/d/1a51n7eIEbPjCWnGaL-pWbZBsRfi55GVQwIdPQTUu49Q/edit

view this post on Zulip Martin Stewart (Jun 21 2022 at 20:46):

Radical idea: Remove Bool and use Result {} {} like what was done with Maybe

Opaque bools sounds like a good idea!

Edit: Another advantage is that it avoids accidentally ending up with a type like [ True, False, SomethingElse ]* which is almost certainly a mistake.

view this post on Zulip Qqwy / Marten (Jun 21 2022 at 20:58):

I was skeptical at the start, but after reading the whole thing, you have convinced me!

view this post on Zulip Qqwy / Marten (Jun 21 2022 at 21:00):

The 'pit of success' argument is a very good one, in my opinion :+1: .

view this post on Zulip Brian Carroll (Jun 21 2022 at 21:00):

Yeah I definitely think it would be nicer to have Bool as a built-in!
I also agree they shouldn't be overused, for sure... but I still want to be able to use them in quick-and-dirty first-draft code, and it feels a bit "heavyweight" to have to actually define Bool.
So this seems like a good balance.

view this post on Zulip Hashi364 (Jun 22 2022 at 02:48):

If kand ki were already in scope, I'd use true = \ x, y -> x and false = \ x, y -> y (those functions are the Church Encoding for the Bools values). But anyway, I like the Opaque Bool idea.

Church encoding booleans: https://en.wikipedia.org/wiki/Church_encoding#Church_Booleans

view this post on Zulip Qqwy / Marten (Jun 22 2022 at 06:15):

@Hashi364 Then you should do it for lists as well ( https://aphyr.com/posts/340-reversing-the-technical-interview )

view this post on Zulip Qqwy / Marten (Jun 22 2022 at 06:16):

:stuck_out_tongue_wink:

view this post on Zulip Hashi364 (Jun 22 2022 at 10:41):

Cool! These days I've been playing with "list destructor" (not sure if it's a official name) in Haskell-land:

data List a = Nil | Cons a (List a)
list :: b -> (a -> List a -> b) -> List a -> b
list b _ Nil = b
list _ f (Con a as) = f a as

:P

view this post on Zulip jan kili (Jun 22 2022 at 13:32):

This is a fun an exciting plan. :)

view this post on Zulip jan kili (Jun 22 2022 at 13:33):

I don't like the current raw definition of booleans as tags, but I love tags. I didn't understand the root cause of my hypocrisy until reading this doc - the answer is that they are special, but other tags are often preferable :)

view this post on Zulip Patrick Kilgore (Jun 22 2022 at 15:02):

I rarely match on bool in OCaml but I frequently match on record/tuple product types of which a bool is a term. So anything that favors the ergonomics of that pattern is preferable even if it feels odd to fully qualify something so simple.

Something to consider though: frequently people judge complexity based on "how hard is this thing I consider simple." Think "hello world" or "fizzbuzz". It's why I have a hard time demonstrating OCaml/dune and Haskell. s-expressions and IO aren't really that bad (but way worse than Bool.true), but it hasn't mattered to colleagues -- they see something completely new on the way to something they think trivial and bail as unnecessary complexity (an instinct a lot of very good product engineers cultivate). It's why I love how types in Roc work! You don't need to see them until they become necessary complexity.

My 2c anyways from pushing FP from within product teams. I guess it is a vote for what I think OCaml does here with a Pervasives module with a few global types/functions.

view this post on Zulip Tommy Graves (Jun 22 2022 at 15:53):

It adds an entire special language keyword whose only real benefits are to make boolean values more concise (which is arguably a downside) and to make it look more familiar to beginners. I value Roc having a lower learning curve, but I think the learning curve of "you write true as Bool.true in this language" is extremely small; more than anything, this would be about familiarity.

This seems like a circular argument given that the context for this point is "Suppose we try this and it isn't nice in practice. What then?" but then the reasons for why you wouldn't choose this option are the same reasons you originally gave for why you want to try opaque bools. It seems like if opaque bools aren't nice in practice it would be precisely because it turns out _not_ to be a downside for boolean values to be more concise and because the learning curve is higher than anticipated or causes more confusion than anticipated.

I think this idea is worth trying but if it fails I would probably fall back to true/false as keywords rather than making them tags, since I think the tags approach is likely to cause subtle confusion. At least with Bool.true any confusion is likely to be really explicit: Why is it Bool.true and not true? On the flip side if you go around using True and False you might not realize you're just using plain old tags and this could lead to you making mistakes.

view this post on Zulip Richard Feldman (Jun 22 2022 at 16:21):

yeah totally agree - if Bool.true doesn't work out in practice, my next choice would be true

view this post on Zulip Richard Feldman (Jun 22 2022 at 16:22):

I thought of an interesting Ruby analogy: right now it's as if we use the symbols :true and :false for our booleans.

It works, but it means we can't really tell when the value we have is semantically a boolean vs something that happens to share a name with one.

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 08:24):

Found this issue related to this thread. Is someone already working on this? If not, I would like to start working on it. Is it just a case of changing the type alias to an opaque type and changing all instances of it in tests and source code?
Or do I also have to change the inner workings of some functions like Bool.and and Bool.not?

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 08:39):

I would probably also have to change the intrinsics of the if then else expression? Maybe this is above my capabilities :sweat_smile:

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 13:32):

No one is working on it yet, it would be great if you wanted to work on it! It should only require changing https://github.com/roc-lang/roc/blob/main/crates/compiler/builtins/roc/Bool.roc and usages of booleans, I believe. I don’t think you should have to change anything deeper than that, though I or anyone else would be happy to help you through that if it turns out to be the case.

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 14:33):

alright I will try and implement it then

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 15:03):

I got something working (still have to update examples and documentation though).
However when I use the new Bool.true or Bool.false in the repl, they get printed as True : Bool and False : Bool. I think they should be printed in lowercase however. How would I change that?

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 15:09):

ah good question

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 15:10):

In some sense it's not totally wrong, because I'm assuming you have defined Bool := [False, True]?

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 15:13):

but, what you will want to do is change https://github.com/roc-lang/roc/blob/6613e9d3472b3cb24c92134b97655743026f6181/crates/repl_eval/src/eval.rs#L1147-L1151 to be something like

Alias(alias_name, ...) => {
  if alias_name() == Symbol::BOOL_BOOL {
    return if value {
      Expr::Var { module_name: "Bool", ident: "true" }
    } else { /* resp. for false */ }
  }
  ...
}

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 15:21):

No it's not wrong, but if the type is opaque, what is printed should not show it's implementation in my opinion. Especially for beginners this could be confusing

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 15:21):

agreed

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 15:22):

Oh and I just tried out an if statement with a normal True tag. It compiles as if it is a normal bool. So I think I will have to dive a bit deeper into the compiler

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 15:24):

good catch. then you will need to update https://github.com/roc-lang/roc/blob/6613e9d3472b3cb24c92134b97655743026f6181/crates/compiler/types/src/subs.rs#L1732-L1751 as well

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 15:35):

how would I need to update that? I feel like if I just delete it all, the opaque type won't work either

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 15:36):

It should be enough to change https://github.com/roc-lang/roc/blob/6613e9d3472b3cb24c92134b97655743026f6181/crates/compiler/types/src/subs.rs#L1749 to AliasKind::Opaque

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 15:40):

don't I have to specify in from which file or package or whatever the opaque type is coming?
Say someone else (for some inexplicable reason) decides to make another opaque type with True and False tags, how would these be distinguished? Or should we assume no one will do that since it is in the roc source itself?

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 15:45):

The name Symbol::BOOL_BOOL takes care of that. Opaque types are compared nominally. So only opaque names with the same name are equivalent.

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 15:54):

Oh I see, thanks!

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:10):

When I run the tests I get a bunch of panics in rust like

thread 'gen_tags::applied_tag_function_pair' panicked at 'invalid cond_layout: Erroneous', crates/compiler/mono/src/ir.rs:6265:18

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:10):

any idea what the problem could be?

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 18:11):

probably a clash with the old and new Bool definition

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 18:11):

Have you run the type checker tests first? They can be run with cargo test -p roc_solve

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:11):

running it now

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:12):

I have changed all llvm-gen-tests containing True or False to their corresponding true and false though

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:14):

I have some errors when running the type checker tests

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:14):

Will try to fix them

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:16):

There are a lot of explicit [True, False] type aliases in these tests

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:16):

I assume I shouldn't change them as they test tag type inference?

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 18:20):

yeah the explicit ones shouldn't be fixed. unless they are being used in if conditionals or == or !=

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 18:21):

It's possible you are seeing the mono errors because there are things like if True or True == in crates/compiler/test_mono/src/tests.rs

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:24):

Ayaz Hafiz said:

yeah the explicit ones shouldn't be fixed. unless they are being used in if conditionals or == or !=

Sometimes there are functions which are explicitly typed with Bool, yet pattern match on True and False. Should I change these to if expressions, or change the type?

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:24):

Ayaz Hafiz said:

It's possible you are seeing the mono errors because there are things like if True or True == in crates/compiler/test_mono/src/tests.rs

Will look at it later

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:26):

Kilian Vounckx said:

Ayaz Hafiz said:

yeah the explicit ones shouldn't be fixed. unless they are being used in if conditionals or == or !=

Sometimes there are functions which are explicitly typed with Bool, yet pattern match on True and False. Should I change these to if expressions, or change the type?

solve_expr::inference_var_inside_tag_ctor is such a test

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 18:27):

Should I change these to if expressions, or change the type?

It's probably best to change them to a type like MyBool : [ True, False ] so that they can continue to pattern match

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 18:28):

Yeah, for that one

badComics: [True, False] -> [CowTools _, Thagomizer _]
badComics = \c ->
    when c is
        True -> CowTools "The Far Side"
        False ->  Thagomizer "The Far Side"
badComics

or the MyBool alias would be fine

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:34):

Okay, now there are only two errors left.
one is with string_starts_with, for which I have to change its source I suppose. I'll do this later.
the other is with mutually_recursive_types, for which I don't get the type syntax

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 18:37):

What test is the mutually_recursive_types one? I can't see a test of that name

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:39):

oh my bad, it is mutually_recursive_captures

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:40):

I still don't get it, but I fixed it by changing the [True]* and [False]* both to Bool in the type signature

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:40):

and x and y have been changed to Bool.true and Bool.false

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 18:43):

oh yeah that works. thanks!

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:45):

now in the test_mono lots of tests failed :exhausted:
I don't understand these at all

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 18:46):

Do you have a branch I can pull down? Happy to hop and take a look

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:46):

I will commit and push quickly

view this post on Zulip Richard Feldman (Sep 14 2022 at 18:46):

the mono tests probably just need to be regenerated

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:48):

How would I do that?
Also what do they do exactly? At this point I feel like I am just doing magic with Ayaz whispering the spells in my ear :joy:

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:49):

Ayaz Hafiz said:

Do you have a branch I can pull down? Happy to hop and take a look

https://github.com/KilianVounckx/roc/tree/opaque-bool

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 18:52):

okay cool, it looks like you did update the mono tests but you're seeing a lot of these Erroneous errors?

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:53):

exactly

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 18:57):

okay yeah there's more going on here than I anticipated :sweat_smile: chasing it down now

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:59):

I will go ahead and look at the examples too see if they need updating

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 18:59):

once you find the issue, let me know, or update it yourself. Whichever suits you

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:09):

ah okay

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:09):

so, the builtin files need to be updated

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:10):

the ones in crates/compiler/builtins/roc/*.roc

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:10):

However, if you just run roc check (or cargo run -- check ) over them, you won't see any errors.

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:11):

That's because we cache types, which silences type errors. So you can see the errors, and update all of them by doing

ROC_SKIP_SUBS_CACHE=1 cargo run -- check crates/compiler/builtins/roc/List.roc

and respectively for each builtin file

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:11):

after that, the mono tests should start working again! hopefully, anyway

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:13):

Will try that

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:13):

I just updated most of the roc files with a good old search and replace, manually checking if they should be replaced

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:14):

The False interpreter got me good :sweat_smile:

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:16):

quick question (I think it is not related, but not sure)
in the crates/compiler/load_internal/tests/fixtures/build/app_with_deps/ManualAttr.roc file, there is a when True is statement.
Should I change anything about that?

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:22):

Ayaz Hafiz said:

That's because we cache types, which silences type errors. So you can see the errors, and update all of them by doing

ROC_SKIP_SUBS_CACHE=1 cargo run -- check crates/compiler/builtins/roc/List.roc

and respectively for each builtin file

I ran all of those. No errors there

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:23):

in the crates/compiler/load_internal/tests/fixtures/build/app_with_deps/ManualAttr.roc file, there is a when True is statement.
Should I change anything about that?

Nope, that's fine as is

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:23):

I ran all of those. No errors there

Do you mean there were errors and you fixed them, or you didn't see errors to begin with?

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:24):

No errors when type checking roc after fix

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:25):

the mono still fails on some tests though

thread 'quicksort_swap' panicked at 'Output changed: resolve conflicts and `git add` the file.', crates/compiler/test_mono/src/tests.rs:200:9

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:25):

that should be okay, for those you can just commit the changed file

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:27):

Yeah was just gonna say, git says some files were changed in test_mono/generated
I assume this is normal then?
Would you mind explaining what is generated? I like to understand what I'm doing so I could replicate it in the future if needed

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:30):

awesome, the mono tests are passing now as well!

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:32):

Sure thing. Those tests generate an intermediate representation of a Roc program that we use in the different backends we have (LLVM, wasm, dev backend) in order to generate an executable. We call that intermediate representation "mono", because it monomorphizes a program, amongst other things. The tests serve as a snapshot, so we can check when things go wrong in the conversion from Roc source code to the intermediate representation.

For a change like this, since it changes many symbols and Roc source code, the intermediate representation is very likely to change as well.

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:35):

I think I get the basics. Not sure if I could replicate though :sweat_smile: but it doesn't matter for now

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:35):

I think I changed everything

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:36):

Should some encoders be updated? I remember one of the reasons for the opaque bool types was to make it so encoders don't have to special case the True and False tags

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:36):

So do you think I have to change any encoder/decoder code?

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:37):

if ROC_SKIP_SUBS_CACHE=1 cargo run -- check crates/compiler/builtins/roc/Json.roc passes you should be good!

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:37):

They did!

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 19:37):

Nice :+1:

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:38):

I will test everything now, so that will take a while. If that succeeds, I will submit the pull request

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 19:39):

Thanks for all the help!
definitely couldn't have done it without you :heart_eyes:

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 20:05):

Noo
still some errors in gen-llvm

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 20:05):

thread 'gen_primitives::increment_or_double_closure' panicked at 'Symbol #UserApp.b is not defined in environment {#UserApp.23: ValueId(6), #UserApp.two: ValueId(2), #UserApp.one: ValueId(1)}', crates/compiler/alias_analysis/src/lib.rs:606:21

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 20:05):

failures:
    gen_primitives::increment_or_double_closure
    gen_primitives::mutually_recursive_captures
    gen_primitives::unresolved_tvar_when_capture_is_unused
    gen_tags::join_point_if

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 20:20):

Is this updated on the branch?

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 20:24):

yes

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 20:25):

the error in gen_tags is a different one

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 20:25):

failures:

---- gen_tags::join_point_if stdout ----
thread 'gen_tags::join_point_if' panicked at 'internal error: entered unreachable code', crates/compiler/mono/src/ir.rs:3925:30
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 20:59):

the problem in gen_primitives seems to be that for some reason boolean variables can't be captured in closures

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 20:59):

I have no idea what the solution could be though

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 20:59):

Yeah, it's kind of a painful error. I think I have a fix, but verifying it now.

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 21:00):

I have a fix for the join_point_if one. I'll send a PR to your fork for both when it's ready

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 21:03):

awesome
thanks!

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 21:52):

https://github.com/KilianVounckx/roc/pull/1

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 21:57):

merged it
should I still run all the tests or have you done that?

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 21:59):

I ran most of them, I think it should be okay

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 22:00):

okay
Then I'll submit the pull request to the roc repo!

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 22:01):

Thanks again

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 22:01):

Thanks for doing this! Always great to have folks help out :grinning:

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 22:04):

I hope to do more of it, but still learning :blush:

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 22:11):

I see I haven't properly signed my commits again

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 22:11):

I have no idea how though

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 22:12):

I used git config --global commit.gpgSign true

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 22:12):

and I had to enter my passphrase

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 22:12):

is there something else I am missing?

view this post on Zulip Ayaz Hafiz (Sep 14 2022 at 22:13):

Have you added your gpg key to your GitHub account? https://docs.github.com/en/authentication/managing-commit-signature-verification/adding-a-gpg-key-to-your-github-account

view this post on Zulip Kilian Vounckx (Sep 14 2022 at 22:14):

I did

view this post on Zulip Anton (Sep 15 2022 at 09:03):

@Kilian Vounckx your merge commit is signed but your earlier commits are not.

view this post on Zulip Kilian Vounckx (Sep 15 2022 at 09:04):

That's real weird, because I had to enter my passphrase on the first commit. Will try to fix it

view this post on Zulip Kilian Vounckx (Sep 15 2022 at 09:27):

As far as I can see, I can't go back and sign previous commits. Is there still a way to fix this? I'm really sorry if there isn't. Next time I will ask one of you before I make the first commit

view this post on Zulip Brian Hicks (Sep 15 2022 at 10:22):

if you can make a signed commit now, rebasing the branch in any way should make Git resign the commits.

view this post on Zulip Kilian Vounckx (Sep 15 2022 at 11:08):

I think I did it. Really not sure though, as I only use git for personal projects, so I don't know a lot about it. I rebased and pushed

view this post on Zulip Anton (Sep 16 2022 at 12:43):

@Kilian Vounckx Looks like something went wrong with the signing. No problem, git is hard :) .
To be sure I'd start with a fresh clone of your repo, the commands below should sign all unsigned commits:

mkdir newroc
cd newroc
git clone https://github.com/KilianVounckx/roc.git
cd roc
git checkout opaque-bool
git filter-branch --commit-filter 'git commit-tree -S "$@";' fe60ff83e646647b247273ab6acee0da0d176bc1..87c15d16ef9e89dd82aa2b9af0496f4e36a484d8
git push origin opaque-bool --force

view this post on Zulip Kilian Vounckx (Sep 16 2022 at 14:20):

just tried that
at the git filter-branch step, it says: You must specify a ref to rewrite.

view this post on Zulip Anton (Sep 16 2022 at 17:28):

Sorry, I think you need the short SHA's, so:

git filter-branch --commit-filter 'git commit-tree -S "$@";' fe60ff8..87c15d1

view this post on Zulip Kilian Vounckx (Sep 16 2022 at 21:33):

still the same you must specify a ref to rewrite :disappointed:

view this post on Zulip Anton (Sep 17 2022 at 09:44):

Hmm, I can look again tomorrow, I'll make sure to test it first.

view this post on Zulip Anton (Sep 18 2022 at 13:01):

@Kilian Vounckx the filter-branch commit needs the last ref to be HEAD so we can't use it here.
These are all the commands you need to execute for a rebase:

mkdir newroc
cd newroc
git clone https://github.com/KilianVounckx/roc.git
cd roc
git checkout opaque-bool
git rebase -i HEAD~14
# this will open an editor, change `pick` with `edit` next to the commits fe60ff8, 83019c5, ... up to(including) 87c15d1
# save than close the editor
git commit --amend --author="KilianVounckx <56159729+KilianVounckx@users.noreply.github.com>" -S
git rebase --continue
git commit --amend --author="KilianVounckx <56159729+KilianVounckx@users.noreply.github.com>" -S
git rebase --continue
git commit --amend --author="KilianVounckx <56159729+KilianVounckx@users.noreply.github.com>" -S
git rebase --continue
git commit --amend --author="KilianVounckx <56159729+KilianVounckx@users.noreply.github.com>" -S
git rebase --continue
git commit --amend --author="KilianVounckx <56159729+KilianVounckx@users.noreply.github.com>" -S
git rebase --continue
git commit --amend --author="KilianVounckx <56159729+KilianVounckx@users.noreply.github.com>" -S
git rebase --continue
git commit --amend --author="KilianVounckx <56159729+KilianVounckx@users.noreply.github.com>" -S
git rebase --continue
# you should see `Successfully rebased and updated refs/heads/opaque-bool.`
git push origin opaque-bool --force

view this post on Zulip Richard Feldman (Dec 04 2023 at 12:54):

so we've had Bool.true and Bool.false for awhile now...some things I've noticed about them that I hadn't originally thought of:

view this post on Zulip Richard Feldman (Dec 04 2023 at 12:56):

these reasons, plus the other reasons discussed previously (general weirdness budget etc.) are giving me a sense that maybe true and false should be reserved keywords like they are in most languages - and still be opaque, but participate in exhaustiveness checking

view this post on Zulip Richard Feldman (Dec 04 2023 at 12:56):

what do others think?

view this post on Zulip Declan Joseph Maguire (Dec 04 2023 at 13:17):

Exhaustiveness seems pretty conclusive. Having something as elementary as booleans defy the most basic expectations of not true => false is such a bad bit of jank that it seems worth fixing even if some people consequently overuse bools. Which itself, sounds like an issue that could be addressed with strong community norms and a section in the tutorial advising people on better alternatives.

view this post on Zulip Elias Mulhall (Dec 04 2023 at 13:37):

Would true/false as keywords make json decoder logic any simpler? I seem to remember a conversation about that being awkward.

view this post on Zulip Declan Joseph Maguire (Dec 04 2023 at 13:40):

That was about using the tags True and False to my understanding. That said, I think introducing true and false keywords might demand a lot of special error messages just to tell people "you don't capitalise it in this language, however tempting it may seem".

view this post on Zulip Richard Feldman (Dec 04 2023 at 14:06):

yeah encoding and decoding behavior would be unaffected by this

view this post on Zulip Brendan Hansknecht (Dec 04 2023 at 15:45):

I would much rather allow exhaustive pattern matching on opaque types as an opt in

view this post on Zulip Brendan Hansknecht (Dec 04 2023 at 15:46):

As long as they exposed all wrapped values and are tags under the hood

view this post on Zulip Brendan Hansknecht (Dec 04 2023 at 15:46):

We have talked about pattern matching on opaques on a few separate occasions, but never added support. It sounded like overall we wanted some sort of support though.

view this post on Zulip Brendan Hansknecht (Dec 04 2023 at 15:56):

Personally, I haven't seen much of a reason for changing opaque bool from actual community comments of use cases. From what I can see:

  1. Pattern matching on bools is never needed and probably is a bad pattern. I don't think it really needs to be supported. If it gets added due to some sort of opaque pattern matching sure, but I think explicitly trying to support it is silly
  2. Bool.something is written, but really not that often. I probably see it the most when it isn't even needed when a beginer does somsFn someVal == Bool.false. that can always be replaced by some sort of negation and probably isn't good practice anyway.
  3. I think people will understand if they are talked about as true and false. It is maybe a gotcha once. Just doesn't really feel like a major issue to me.
  4. Booleans can be a fine choice for data modeling, but not often enough to justify a keyword in my opinion
  5. You can just import true and false at the top of any file and it would work just like a keyword.

view this post on Zulip Anton (Dec 04 2023 at 16:03):

Pattern matching on bools is never needed [...]

Maybe all we need is a nice error message for when people try it.

view this post on Zulip timotree (Dec 04 2023 at 16:26):

Pattern matching on bools is never needed and probably is a bad pattern.

What about when the Bool is part of a larger structure? For example

isBread : Food -> Bool

isSandwich : List Food -> Bool
isSandwich = \layers ->
    when List.map layers isBread is
        [Bool.true, Bool.false, Bool.true] -> Bool.true
        _ -> Bool.false

view this post on Zulip Anton (Dec 04 2023 at 16:31):

Tags would be more readable in my opinion: [IsBread, isNotBread, IsBread] ->

view this post on Zulip Brendan Hansknecht (Dec 04 2023 at 16:33):

That's a fair use case for sure. I think a proper tag would be better, but I definitely consider the bool case reasonable.

I guess I was just thinking of bool and not nested bools.

That said, I think this example falls into "we should allow pattern matching on opaques if they opt into it", not that we should add a special case for bools.

view this post on Zulip Ayaz Hafiz (Dec 04 2023 at 16:52):

timotree said:

isSandwich : List Food -> Bool
isSandwich = \layers ->
    when List.map layers isBread is
        [Bool.true, Bool.false, Bool.true] -> Bool.true
        _ -> Bool.false

I think we intend to support this anyway right? Like I would expect this to be sugar for

        [a, b, c] if a == Bool.true && b == Bool.false && c == Bool.true -> Bool.true

(I know, no boolean zen in that code)

view this post on Zulip Ayaz Hafiz (Dec 04 2023 at 16:53):

Brendan Hansknecht said:

I would much rather allow exhaustive pattern matching on opaque types as an opt in

I think we should avoid doing this. It feels too complicated, and I don't really see the advantage it provides. The type is opaque; opaque types that are opaque just so that they can force the tag union to be closed, like Bool, are an extremely limited use case

view this post on Zulip Brendan Hansknecht (Dec 04 2023 at 17:33):

I assume they would be opaque for other reasons. Like wanting a custom inspect impl or otherwise, but yeah, maybe too niche for the complexity.

view this post on Zulip Brendan Hansknecht (Dec 04 2023 at 17:34):

I think we intend to support this anyway right?

I thought so.

view this post on Zulip Richard Feldman (Dec 04 2023 at 17:37):

yeah I agree with both of Ayaz's comments - I do think we should make that sugar work for pattern matching on opaques, but I think they should stay opaque and not be exhaustive

view this post on Zulip Richard Feldman (Dec 04 2023 at 17:37):

another option is to make Bool.true and Bool.false not keywords but quietly let them participate in exhaustiveness even though no other opaques work that way

view this post on Zulip Richard Feldman (Dec 04 2023 at 17:38):

I guess numbers are builtin opaques that actually do work that way (or should) because if I write out all 256 number literals for an 8-bit integer, that should be exhaustive :thinking:

view this post on Zulip Richard Feldman (Feb 06 2024 at 18:43):

thinking about this topic some more, I'm starting to think that the Bool.true and Bool.false experiment has suggested that we should just do true and false like most languages do. Reasons:

I don't think the theoretical benefit of encouraging tag union usage has played out in practice the way I hoped it would.

view this post on Zulip Richard Feldman (Feb 06 2024 at 18:44):

so all those things considered, I think we should just do true and false keywords and have them participate in pattern matching the way you'd expect

view this post on Zulip Richard Feldman (Feb 06 2024 at 18:51):

what do others think?

view this post on Zulip Anne Archibald (Feb 06 2024 at 19:23):

As a beginner, I keep typing True and False and it often sort of works, but not quite. Bool.true is longer but unambiguous. I'd find lowercase true and false rather surprising because they look like they should be variables (although no reasonable person would actually use those variable names).

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 19:29):

I guess we could instead make True and False special restricted tags

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 19:29):

But that would probably be heavy handed in terms of compiler implementation

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 19:46):

That said, I think I would prefer it over true and false. Would really make the language feel seamless around bools.

Bool is just [True, False], but True and False are special restricted tags that are only allowed to be used by Bool. Also, given True and False are only allowed in the Bool tag, Bool will never merge with any other tags and seeing just True or just False can still infer the type Bool.

view this post on Zulip Elias Mulhall (Feb 06 2024 at 22:53):

I don't love the idea of special restricted tags. I think if true and false were language keywords no one would bat an eye, and then if you separately wanted to have a tag union like [True, False, Either]that would just work, no special rules.

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:03):

I think people would question the when ... is syntax given it doesn't work with variables, but some reasons works with bools:

when x is
    true -> ...
    false -> ...

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:08):

Also, technically we don't have to restrict the True and False tags, we could also just make it so that Bool is magically exclusive and will never merge with other tags. So you can still have [True, False, Either], but a Bool would not convert to that without an explicit cast.

That said, I think that would lead to complication is type checking cause it is unclear what to do with a raw True value and when to consider it exclusive.

I guess to be fair, even simply defining bool as [True, False] might be just fine. Any time it is used in a conditional, it would restrict to just Bool.

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:09):

@Richard Feldman what are the issues with Bool just being an alias? Like it just being defined as [True, False]?

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:10):

see the start of the thread! :big_smile:

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:13):

ah yeah, issues with solo True and solo False.

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:13):

Yeah, I definitely think I would prefer to just restrict the tags True and False and then use those as always boolean values.

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:13):

Over true and false as builtin special variables.

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:14):

so if you put True into the repl, what would it print as the type?

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:14):

Bool

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:15):

cause True and False can only be Bool. They are restricted from being part of any other tag

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:15):

hm, so it seems like that idea is equivalent to (or at least indistinguishable from) True and False being the names of the special keywords instead of true and false, right?

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:15):

in that they behave unlike everything else that's capitalized like that

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:16):

sure, but:

  1. They look like regular tag unions and match like regular tag unions.
  2. They don't take a global keyword, instead they take a tag

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:17):

Unless we allow pattern matching on arbitrary variables or opaque types, this will always look wrong:

when x is
    true -> ...
    false -> ...

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:17):

that definitely looks strange today, sure

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:18):

although maybe with syntax highlighting it's less weird:

when x is
    true -> ...
    false -> ...

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:18):

This will look correct and wouldn't be question by users:

when x is
    True -> ...
    False -> ...

It would only get noticed if some user attempts to use True or False in another tag. In that case, we can easily print a good error message.

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:20):

Either creates two special names. One in variable/function name space. The other in tag space.

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:21):

The capitalized name just matches everything else tag union while the lower case name is just a bit quirky.

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:24):

interesting, so:

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:25):

if we did that, we could do the same for Ok and Err and Result, which would mean Result could be opaque and therefore used with ! while still having pattern matching on Ok and Err work :thinking:

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:26):

yep

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:26):

since they'd be different from normal tags, should they get different syntax highlighting?

view this post on Zulip Brendan Hansknecht (Feb 06 2024 at 23:28):

I guess they could get keyword syntax highlighting, but I think it would be fine if they just had regular tag highlighting...not really sure.

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:49):

interestingly, that would also mean that putting Ok "foo" into the repl would print Ok "foo" : Result Str *

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:50):

I wonder if having language keywords that look like tags might make tag unions harder to learn

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:50):

because if I put True in it says Bool but if I put Foo in, it's [Foo]

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:50):

(into the repl)

view this post on Zulip Richard Feldman (Feb 06 2024 at 23:50):

might be fine

view this post on Zulip Elias Mulhall (Feb 07 2024 at 01:01):

I think this is a neat idea, but I'd still push back on the idea that users would find matching on true and false weird. Lots of languages use true and false as keywords, and even if they aren't reserved using them as variables like true = 1 wouldn't feel right. Pattern matching works on other concrete types so it's not like this would be an outlier.

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:02):

interesting, so what would your preferred design be here?

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:02):

I guess now that you mention it, Rust is a language that specifically does this

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:03):

enum variants are always capitalized, variables are typically lowercase, but true and false are keywords you can pattern match on and I haven't felt it was weird

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:22):

I guess that is fair, though I do think it is pretty weird that Bool would be the only opaque tag that can be matched against.

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:22):

Roc isn't Rust. In Roc, Bool is a tag union.

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:23):

In Rust, bool is actually a standalone type.

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:28):

well bool isn't a tag union anymore

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:28):

it's opaque, hence why we have Bool.true and Bool.false now

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:28):

(because of the reasons at the beginning of this thread etc)

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:33):

Idk, in my mind that just means it is an opaque tag union. So it is still a tag union and would be nicer to treat as one.

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:37):

I guess in general, I don't like giving bool extra power that other opaque tag unions can't also have.

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:37):

So I prefer to make it act like a non-opaque tag union so it fits with the power it is given.

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:38):

what about the concerns at the start of this thread though?

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:39):

like we originally had Bool as a type alias for the tag union [True, False], and that led to problems which resulted in making it opaque instead! :big_smile:

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:39):

True. I guess that means that either way, Bool has extra power that is not given to normal tags.

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:40):

Is there a reason we can't allow opaque tags opting into pattern matching in general?

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:40):

yeah exhaustiveness checking

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:41):

Can you expand on that, I don't understand why there is a problem

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:41):

if opaque types can be exhaustively pattern-matched on, then that means you are no longer free to change the internal representation without it potentially breaking builds (due to things that were previously exhaustive becoming no longer exhaustive)

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:41):

and maybe the most important characteristic of opaque types is being able to make changes to the internal representation without breaking things!

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:42):

Sure, but this is opt in. I don't see the problem

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:43):

I mean we could introduce a whole language feature where opaque types can opt into being pattern-matched on, but that seems like an unnecessary amount of complexity to add to the language just for True and False to be capitalized instead of lowercased :sweat_smile:

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:45):

I more see it as a way to:

  1. Make Bool not special
  2. Open this feature to more types. I know it has been asked more than once in previous chats if this was possible.

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:45):

hm, but wasn't it only asked about in the context of builtins? :thinking:

view this post on Zulip Richard Feldman (Feb 07 2024 at 01:45):

maybe there are instances I'm not remembering

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:45):

I guess if you wanted to be super flexible, you could make it an ability. The ability would auto derive to just unwrapping the opaque type. That said, if you want to hide part of the impl, you could change it to generate an arbitrary tag union instead.

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 01:47):

I think it has come up a few times in beginner chats. Also came up in the abilities on any type chat cause it felt like a user had to pick between pattern matching and having abilities. This would get them both.

view this post on Zulip Elias Mulhall (Feb 07 2024 at 02:04):

Is the idea that opaque tag types would (optionally) work with pattern matching, but still would not union with other tags?

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 02:04):

yep

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 02:04):

And I guess optionally hide info when being pattern matched on.

view this post on Zulip Richard Feldman (Feb 07 2024 at 02:23):

hm, I don't think that can work though

view this post on Zulip Richard Feldman (Feb 07 2024 at 02:23):

like let's say I put True into the repl

view this post on Zulip Richard Feldman (Feb 07 2024 at 02:23):

how is it going to know to print True : Bool?

view this post on Zulip Richard Feldman (Feb 07 2024 at 02:23):

like if any opaque type can do this, what's to prevent some other type from claiming the tag True?

view this post on Zulip Richard Feldman (Feb 07 2024 at 02:24):

and then how do you know which one "wins"?

view this post on Zulip Richard Feldman (Feb 07 2024 at 02:24):

do you have to have that type imported and in scope somehow?

view this post on Zulip Richard Feldman (Feb 07 2024 at 02:25):

if that's the case, then let's say I call a function which returns Foo and I try to pattern match on it - now that fails unless I import Foo explicitly

view this post on Zulip Richard Feldman (Feb 07 2024 at 02:26):

at which point maybe we also need to introduce the concept of "qualified tags" like Foo.Bar -> ...

view this post on Zulip Luke Boswell (Feb 07 2024 at 02:48):

Richard Feldman said:

what do others think?

FWIW I like the way it currently is, and haven't had any concerns just using Bool or Bool.true.

view this post on Zulip Luke Boswell (Feb 07 2024 at 02:51):

I don't feel strongly though if it changed, I could get use to it. But I do like the arguments for discouraging Bool and to use Tags instead. It's like a Roc superpower

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 03:19):

Hmm. I was just thinking from the pattern matching perspective. I guess to initialize a value, it would still require a wrapping function or something else...not fully sure.

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 03:20):

Cause when pattern matching, you have the opaque type as input, no need to determine which one it is. Just apply the ability that turns it into a matchable value

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 03:24):

I guess with my idea, the only time you would see Bool.true is when setting the constant. (Like maybe the initial state of an accumulator)

Cause in matching you would use True.

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 03:26):

I guess that might be more confusing cause you might see both the qualified name and the raw tag. Though I think in practice for most types that implement this ability, you would just see it in pattern matching and it wouldn't be a big deal.

view this post on Zulip Richard Feldman (Feb 07 2024 at 03:28):

how would that work with type inference? Like if I put this into the repl:

\foo ->
    when foo is
        Bar -> ...

what would the inferred type be of that function?

view this post on Zulip Brendan Hansknecht (Feb 07 2024 at 03:52):

Hmm...I guess this is a case where it would auto-derive for standard tags and have a really ugly type. Though maybe it doesn't work at all (cause not sure how the output type of the ability would be constrained properly without higher kinded types).

...

Yeah, nvm, just seems too messy/potentially not really implementable. I guess let's just stick with Bool and maybe Result as types being considered for special treatment.

view this post on Zulip Richard Feldman (Feb 07 2024 at 04:39):

makes sense to me! So then the remaining questions for Bool are whether to:

view this post on Zulip Isaac Van Doren (Feb 07 2024 at 12:57):

My preference of the three is to have true and false be keywords. I think True and False would be confusing because they look like tags but wouldn’t behave like them.

view this post on Zulip Anne Archibald (Feb 07 2024 at 18:22):

Richard Feldman said:

makes sense to me! So then the remaining questions for Bool are whether to:

Is it outrageous to suggest an Ability that opaque types could implement that would allow pattern matching on them? I'm not quite sure what that would look like, but it would reduce the "for me but not for thee" aspect of built-in types. Records, tag unions, and lists can all be matched on, but there's no way to match on an opaque type without converting it into one of the above. For example:

when split iterator 2 is
    ([], _) -> ...
   ([a], _) -> ...
   ([a,b], rest) -> ...

or

when firstRest iterator is
    Ok (a, rest) -> ...
    Err _ -> ...

These are not terrible syntactically, but they aren't especially discoverable either. An ability could perhaps provide a standard way that the when statement could convert an opaque type into something it could already match on, or perhaps it could take some description of the match pattern and carry out the matching operation. Ideally,

when iterator is
     [] -> ...
     [a] -> ...
     [a, b, .. as rest] -> ...

(without converting the whole iterator into a list, so that rest would remain an iterator).

view this post on Zulip Richard Feldman (Feb 07 2024 at 18:29):

we discussed this earlier in the thread :big_smile:


Last updated: Jun 16 2026 at 16:19 UTC