Gleam: Opaque custom types

Created on 3 May 2020  路  17Comments  路  Source: gleam-lang/gleam

It would be useful to be able to define custom types without exposing the constructors or accessors so that other modules would able to able to manipulate values of that type through functions exposed by the defining module.

This would allow us to hide implementation details in our APIs that we don't want clients to use.

What should the syntax for this be?

good first issue help wanted

Most helpful comment

Let's go with pub opaque type MyType { ... }

All 17 comments

Maybe access modifier keywords? pub on constructors to expose them. Bad thing with that is it is obviously breaking change.

You could just have an opaque keyword that's a modifier of type. Seems simple enough

That sounds good!

It would be good to see if we can come up with other design so we can compare their relative merit.

I like pub as it is consistent with the rest of the language in both syntax and behaviour (ie things are accessible in the current module only unless marked otherwise). opaque would have neither of those advantages.

It would be a breaking change, but:

  1. There's only one production app to break right now, and unless crowdhailer had a very productive Sunday it currently just says "Hello world".
  2. It would be a simple mechanical upgrade to insert pub into all existing constructors. This could be done with a new flag on format, a new command based on format, or just manually given point 1.
  3. Breaking things is the whole point of 0.x versions!

So that would be

pub type Result(a, e) {
  pub Ok(a)
  pub Error(e)
}

My immediate reaction is that it's quite verbose, but it is consistent.

Yeah it is a bit verbose, though that example is the worst case. Looks like rust, for better or for worse. I think constructors being private unless specifically marked otherwise feels less surprising and more consistent with the way types and functions work already. The fact that it uses the same keyword isn't as important.

Could maybe have a new keyword that means "this type is public and all its constructors are too" to handle the specific case above? puball, pubr, err pub虏? Functionally that's just !opaque but to me it feels more opt-in rather opt-out if that makes sense?

I like your point about it maching functions (private by default, public with the pub keyword).

Could maybe have a new keyword that means "this type is public and all its constructors are too" to handle the specific case above? puball, pubr, err pub虏? Functionally that's just !opaque but to me it feels more opt-in rather opt-out if that makes sense?

Generally I prefer not to have more than one way of writing the same thing, even if it means a little more verbosity, so a pub on each constructor would be my preference of the two options.

Don't you need more than the suggested syntax for accessors.
What about a user type where name is public and the other fields are not?

Could you share an example of when a half-public record would be useful?

I'm not sure it would. I'm also not sure why you would make only some constructors public. i.e

pub type Result(a, e) {
  pub Ok(a)
  Error(e)
}

If it only makes sense to have pub on all/none of the types then I feel like it's not the right approach, and would lean to a keyword at the top.

pub opaque type ...
pub open type ...

all / none does seem like a simpler option. Struggling to think of a realistic case where you'd only want some constructors public.

I agree, I can't think of a reason to have some constructors public and some private. I've asked Twitter to see if they have any use cases.

I agree that there is basically no use case for a limited set of constructors being private. That's why the opaque qualifier for the type itself may be the better choice

I had just assumed such a thing was useful in my comments about pub for some reason, but I definitely prefer all / none on the constructors, and opaque seems good.

Would some kind of readonly qualifier be useful as well to allow access to accessors but not constructors?

Accessors is the name used for the .field syntax, do you mean the ability to pattern match on a value but not construct it? What would the use case be?

A hypothetical date library might want to only allow dates to be constructed via its own functions to ensure they are valid e.g. assert Ok(date) = date.new(2020, 2, 28) rather than let date = Date(2020, 2, 31). That doesn't necessarily seem like it should also disallow using date.year.

Oh I see! Thank you!

I think for the sake of simplicity I would encourage exporting functions in that case.

Let's go with pub opaque type MyType { ... }

Was this page helpful?
0 / 5 - 0 ratings