gleam
gleam copied to clipboard
Re-export types and values from other modules
It would be nice to be able to re-export a type or function from another module.
Here's an idea for a syntax:
// one.gleam
pub type MyType {
MyConstructor
}
pub fn my_function() -> Nil {
Nil
}
// two.gleam
import two.{pub MyType, pub MyConstructor, pub my_function}
Questions
Should it be permitted to export only a subset of a type's constructors? This would mean it would not possible for users to pattern match on all records as only some have been exported. The above syntax would allow that.
I like the syntax, but only rexporting some constructors would be very awkward. (i'm sure I was discussing this somewhere previously but canot find it)
I'm keen that import option.{Option}
should import Some and None, but I understand that it would mean you couldn't just search in a file to see where the Some and None constructors where defined.
Let's use the syntax above as there's no other suggestions or major complaints.
Things to consider:
- When reexporting a function we need to generate a function definition for it.
Should it be permitted to export only a subset of a type's constructors? This would mean it would not possible for users to pattern match on all records as only some have been exported. The above syntax would allow that.
This makes me think of @hayleigh-dot-dev's suggestion to use a nested syntax for importing record constructors. I previously dismissed it but there's a second reason to have it here.
Adding to v1 as this may require syntax changes
I'd favor having the pub
as a qualifier for import
instead as it would be easier to read which imports are public and which ones are not:
import two.{pub MyType, my_function, pub MyConstructor}
vs
pub import two.{MyType, MyConstructor}
import two.{my_function}
I'd also prefer to have separation between pub and non-pub imports as @pvdrz shows. Reasoning, it may be a bit more verbose but:
- it far far better to read and
- the formatter could group into two parts
- alphabetically sorted pub imports and
- alphabetically sorted non-pub imports.
Fully agree with @pvdrz and @inoas here for reasons they already stated!
I'd love to import/export all functions / all types except a list (opt-out instead of opt-in).
We won't have that I'm afraid, it will always be explicit.
another question I'd have is: if you re-export a function (or type) but also define it in the same file, what happens? imho compiler error or newly defined is being exported > reexported is being exported
Yup same as today, it would be a compile error as two items cant have the same name.
Considering pub import a.{b}
and import a.{pub b}
syntaxes.
It seems that pub import a.{b}
implies you can import a
from this module in some fashion.
pub import
means that we have to permit module a module to be imported multiple times, or would the programmer be forced to pub import a.{b} as _
to not assign a name? What would the rules be here?
Sorry to bring this up again but I think it's relevant to your comment. I've said it before that to my brain, import a.{b, c}
suggests that I'm only importing b
and c
. But gleam imports a
too.
This is the actual root of the ambiguity you're talking about, not pub
. Because even with import a.{pub b}
, a
is still in scope. So even with import a.{pub b}
, you should still resolve that ambiguity.
I think given the circumstance, whether we use pub import a.{b}
or import a.{pub b}
, we should have as _
to make it clear to the developer that only b
is being reexported.
But then it would raise the question, are we reexporting a
too if as _
is not used? It's important to realize that even if we're not doing that, this would be a question that will come to the developer's mind, which is not ideal.
I know introducing new syntax/keywords is frowned upon, but given the mishmash of all the ambiguities here, the best and most clear option is to have a new keyword for this feature:
export a.{b, type MyType, MyConstructor}
This would mean that if you want to use something in the module but also reexport it, you would need to use both import
and export
. This may seem more verbose, but is far more clear as to what is actually going on. This would also remove the need to use pub
and also allows us to do anything we want with this feature regardless of how import
is behaving.
P.S.
The more I think about it, the more I realize what I said is not completely true. import a.{pub b, c}
is more clear and to some extent pub import a.{b}
can be considered as the real problem(which pub import a.{b} as _
does not solve!), but I still think a new keyword for this feature is better:
import a.{pub type MyType, type MyType2, pub MyConstructor, MyConstructor2, pub func1, func2}
vs
import a.{type MyType, type MyType2, MyConstructor, MyConstructor2, func1, func2}
export a.{type MyType, MyConstructor, func1}
To my eyes there is not even a competition here, the second one is more clear and less convoluted. You don't have to read through the imports and parse out which one is reexported and which one is not! Also, if you want to know what is used in the current module, you go to imports, if you want to know what this module is reexporting, you go to exports, clear separation of concerns.
import a.{type MyType, type MyType2, MyConstructor, MyConstructor2, func1, func2}
export a.{type MyType, MyConstructor, func1}
This seems to imply you can re-export a type without importing it. Is that right?
Yes, I think that would be more understandable and I don't see any issues. But if you think that may cause issues, you could make it a compile error if something that is not in scope is being exported. Although, that would couple the behavior of import
and export
to some extent, which is not ideal.
To import something it should also be brought into scope as otherwise you'd have a situation in which you can import an item from a module when its not actually in that module's scope.
Considering
pub import a.{b}
andimport a.{pub b}
syntaxes.It seems that
pub import a.{b}
implies you can importa
from this module in some fashion.
pub import
means that we have to permit module a module to be imported multiple times, or would the programmer be forced topub import a.{b} as _
to not assign a name? What would the rules be here?
What if rather than this syntax and trying to "overload" or extend what "import
" we introduced something like
include from module { type X, Y }
that would work like an explicit version of OCaml's module inclusion: https://ocaml.org/docs/modules#module-inclusion
This would prevent the confusion of importing a module multiple times or having to rename via as
. It would also have the benefit of not having to deal with introducing the pub
keyword to imports.
// foo.gleam
pub type Foo {
Foo(String)
}
pub fn print_foo(foo: Foo) -> Nil { ... }
// bar.gleam
import project/foo.{print_foo}
include from project/foo { type Foo, Foo }
pub type Bar {
Bar(String)
}
// baz.glem
import project/bar.{type Foo, type Bar, Foo, Bar}
...
You could probably get away just by swapping the keyword from import
to include
, or potentially even a pub
modifier on the entire import
:
import project/foo.{print_foo}
pub import project/foo.{ type Foo }
I think the problem with pub import project/foo.{ type Foo }
is that it bring foo
into scope. So potentially just swapping import
with include
is enough to change the semantics.
include project/foo.{ type Foo }
You could use the pub
keyword to effectively do the same, but I think having a difference between import
and include
is nice and might be more clear they work differently
Referencing https://github.com/gleam-lang/gleam/issues/2712#issuecomment-1998681052, we would not have this problem if types are first-class in Gleam.
Should it be permitted to export only a subset of a type's constructors? This would mean it would not possible for users to pattern match on all records as only some have been exported. The above syntax would allow that.
Having constructor not qualified by the type name is a bit weird here. If Some
is normally used as Option::Some
, then only Option
need to be imported.
@iacore If you have a proposal for a language addition please write and share the proposal in GitHub discussions, thank you.
Is this invalid or too verbose?
import two.{MyType, MyConstructor, my_function}
pub type MyType = two.MyType
pub const MyConstructor = two.MyConstructor
pub const my_function = two.my_function