Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve type and function sharing between modules #62

Closed
j14159 opened this issue Dec 7, 2016 · 16 comments
Closed

Improve type and function sharing between modules #62

j14159 opened this issue Dec 7, 2016 · 16 comments
Labels

Comments

@j14159
Copy link
Collaborator

j14159 commented Dec 7, 2016

Short list, driven by discussion on PR #61 with @danabr and from @lepoetemaudit's infix function work:

  • all type names available by default but not implementations (e.g. type constructors). Exporting a type exposes its implementation. This is to let us use a module's type in other modules' types without needing the details (information hiding, abstract/opaque types).
  • individual functions used in other modules without qualifying them with their module name, this will make infix operators more useful.

Per @danabr we should consider a single import directive that allows importing specific functions, types, or even a subset of a type's constructors.

Ideas/expansions/criticisms most welcome.

@j14159
Copy link
Collaborator Author

j14159 commented Dec 14, 2016

Opinions on separating function and type imports into two different statements? E.g.

import some_module.[foo/1, bar/3]

import_type some_module.[baz, xyz]

Too much? I kind of like the separation for clarity but I'm not particularly attached to it.

@danabr
Copy link
Contributor

danabr commented Dec 14, 2016

Does not matter much to me.

Personally, I just want to bring a name into local scope, be it a function name or type name. That argues for having a single import statement.

However, since it is possible to define both a function foo, and a type foo in the same module, having two different statements is probably a good idea, since that removes unnecessary ambiguity.

@yurrriq
Copy link
Contributor

yurrriq commented Dec 14, 2016

👍 to import for functions and import_type for types.

@yurrriq
Copy link
Contributor

yurrriq commented Dec 14, 2016

@j14159
Copy link
Collaborator Author

j14159 commented Dec 18, 2016

I kind of like the idea that a type itself can be completely hidden in a module as it seems with the Idris docs you linked, @yurrriq.

For type exports I'm thinking:

  • without being explicitly exported, the type name isn't even visible to other modules.
  • can export only the type name so that another module's type can leverage it without understanding its structure, equivalent to exposing an abstract type. I think this addresses one of the things you were advocating @danabr but would appreciate feedback regardless.
  • can export a subset of a type's constructors which also of course makes the type's name visible to other modules.
  • some special form that exports the entire type and its members.

Type imports would be simpler, either just the type name or specific constructors/members.

The four export points do seem a little messy. Might look something like:

module either

type t 'a = Left 'a | Right 'a

-- only the type either.t is available, can't see Left/Right outside this module:
export_type t

-- either.t and either.Left are visible outside of this module:
export_type Left

-- either.t, either.Left, and either.Right are all visible externally:
export_type t.*

Does that seem needlessly convoluted? Should we be adopting something like Idris' syntax here? I do like the decoupling of definition and exposure with something like export/export_type but I don't have extremely strong feelings about it other than consistency issues with exporting functions and not really wanting to change how that works for people coming from Erlang.

@lepoetemaudit
Copy link
Contributor

I don't feel very strongly any particular way. I believe however that your proposal grants a lot of control over what is exported without being too awkward to use, so I can't see any problems with rolling with that.

@yurrriq
Copy link
Contributor

yurrriq commented Dec 19, 2016

I agree with @lepoetemaudit; your proposal sounds good, @j14159. I'm not sure I love the third bullet point, but 👍 for fine-grained control and if I don't like it I don't have to use it 😄

Edit: Pulled out types question to #77

@j14159
Copy link
Collaborator Author

j14159 commented Dec 19, 2016

@yurrriq I hadn't thought of it but I don't think there's any particular problem with having the same name with different arity to describe different versions of a type. I'm not sure I can think of a concrete use for it but we could certainly try it out and see if it's useful.

Mind filing a different issue for it as a feature and broader discussion?

@danabr
Copy link
Contributor

danabr commented Dec 19, 2016

Regarding bullet point 1: How do you handle the case when you have not exported a type, but you have some function returning a value of that type? E.g.

module foo

type foo = Foo | Bar

export make_foo/1

make_foo () = Foo

If I call make_foo from another module bar, what is the type I get? My point is, if we want to have "hidden" types, then those types must never "escape" and the compiler must check this, right?

@yurrriq
Copy link
Contributor

yurrriq commented Dec 19, 2016

How do you handle the case when you have not exported a type, but you have some function returning a value of that type?

FWIW Idris will fail to compile the module in that case, so 👍 to having the compiler check.

@j14159
Copy link
Collaborator Author

j14159 commented Dec 19, 2016

I don't think the check would be too hard to implement, basically "for every top level function it is a type privacy violation iff the function is exported AND the function's type contains any unexported types". IIRC recursive ADTs (not the most appropriate name but all user-defined types are considered to be ADTs) only refer to themselves by name so we shouldn't get non-terminating behaviour running this check but that's a potential issue.

I guess the bigger question is: where do we want to sit in the balance of simplicity for the user vs most expressive/high degree of control? Related questions:

  • are errors about unexported types going to cause more confusion than all type names being public?
  • is the utility of hidden types high enough?
  • is every type name public actually simpler?
  • are good error messages enough if we allow hidden types? I don't think we provide enough information to the typer yet to be as good as something like Elm but I'd like to get there.

@tjweir
Copy link
Contributor

tjweir commented Dec 19, 2016

"are errors about unexported types going to cause more confusion than all type names being public?l

In this case, a glaringly obvious error message would be sufficient no?

"I can't find type foo, you must add import_type your_module.foo"

@tjweir
Copy link
Contributor

tjweir commented Dec 19, 2016

Your comment about a types being public is far more simple, maybe cross the type-name collision bridge when you get to it?

@yurrriq
Copy link
Contributor

yurrriq commented Dec 19, 2016

Idris says something like "exported function make_foo can't refer to private type foo."

Edit: changed names to go with @danabr's example.

j14159 added a commit to j14159/alpaca that referenced this issue Dec 30, 2016
This was suggested in alpaca-lang#63.  It's not actually part of the AST generation
phase yet but it made sense to handle this at the same time as function
imports for alpaca-lang#62.  I'm going to decouple the basic parsing pass from the
renaming and rewriting pass of the parser since rewriting the names of
imported functions to inter-module calls will require the other modules
to already be parsed and accessible.
j14159 added a commit to j14159/alpaca that referenced this issue Jan 2, 2017
This was suggested in alpaca-lang#63.  It's not actually part of the AST generation
phase yet but it made sense to handle this at the same time as function
imports for alpaca-lang#62.  I'm going to decouple the basic parsing pass from the
renaming and rewriting pass of the parser since rewriting the names of
imported functions to inter-module calls will require the other modules
to already be parsed and accessible.
j14159 added a commit to j14159/alpaca that referenced this issue Jan 3, 2017
This was suggested in alpaca-lang#63.  It's not actually part of the AST generation
phase yet but it made sense to handle this at the same time as function
imports for alpaca-lang#62.  I'm going to decouple the basic parsing pass from the
renaming and rewriting pass of the parser since rewriting the names of
imported functions to inter-module calls will require the other modules
to already be parsed and accessible.
j14159 added a commit to j14159/alpaca that referenced this issue Jan 7, 2017
This was suggested in alpaca-lang#63.  It's not actually part of the AST generation
phase yet but it made sense to handle this at the same time as function
imports for alpaca-lang#62.  I'm going to decouple the basic parsing pass from the
renaming and rewriting pass of the parser since rewriting the names of
imported functions to inter-module calls will require the other modules
to already be parsed and accessible.
j14159 added a commit to j14159/alpaca that referenced this issue Jan 7, 2017
This was suggested in alpaca-lang#63.  It's not actually part of the AST generation
phase yet but it made sense to handle this at the same time as function
imports for alpaca-lang#62.  I'm going to decouple the basic parsing pass from the
renaming and rewriting pass of the parser since rewriting the names of
imported functions to inter-module calls will require the other modules
to already be parsed and accessible.
j14159 added a commit to j14159/alpaca that referenced this issue Jan 7, 2017
This was suggested in alpaca-lang#63.  It's not actually part of the AST generation
phase yet but it made sense to handle this at the same time as function
imports for alpaca-lang#62.  I'm going to decouple the basic parsing pass from the
renaming and rewriting pass of the parser since rewriting the names of
imported functions to inter-module calls will require the other modules
to already be parsed and accessible.
j14159 added a commit to j14159/alpaca that referenced this issue Jan 7, 2017
This was suggested in alpaca-lang#63.  It's not actually part of the AST generation
phase yet but it made sense to handle this at the same time as function
imports for alpaca-lang#62.  I'm going to decouple the basic parsing pass from the
renaming and rewriting pass of the parser since rewriting the names of
imported functions to inter-module calls will require the other modules
to already be parsed and accessible.
j14159 added a commit to j14159/alpaca that referenced this issue Jan 7, 2017
This was suggested in alpaca-lang#63.  It's not actually part of the AST generation
phase yet but it made sense to handle this at the same time as function
imports for alpaca-lang#62.  I'm going to decouple the basic parsing pass from the
renaming and rewriting pass of the parser since rewriting the names of
imported functions to inter-module calls will require the other modules
to already be parsed and accessible.
j14159 added a commit to j14159/alpaca that referenced this issue Jan 7, 2017
This was suggested in alpaca-lang#63.  It's not actually part of the AST generation
phase yet but it made sense to handle this at the same time as function
imports for alpaca-lang#62.  I'm going to decouple the basic parsing pass from the
renaming and rewriting pass of the parser since rewriting the names of
imported functions to inter-module calls will require the other modules
to already be parsed and accessible.
j14159 added a commit to j14159/alpaca that referenced this issue Jan 7, 2017
This was suggested in alpaca-lang#63.  It's not actually part of the AST generation
phase yet but it made sense to handle this at the same time as function
imports for alpaca-lang#62.  I'm going to decouple the basic parsing pass from the
renaming and rewriting pass of the parser since rewriting the names of
imported functions to inter-module calls will require the other modules
to already be parsed and accessible.
@j14159
Copy link
Collaborator Author

j14159 commented Jan 13, 2017

I think in the interest of simplicity for now I'd like to go with:

  • all type names are public
  • only exported types expose their constructors/implementation
  • only exported types can be imported, importing them imports their full implementation (all constructors)

References to types that aren't exported must be qualified by their module name, e.g. given

module a
type x = X int

This is an error:

module b
import_type a.x
type y = x

This is OK but the constructor X is not in scope:

module c
type z = a.x

This should ensure that exported functions exposing un-exported implementations are still intelligible on some level and this will be somewhat consistent with us later adding things like opaque types coming in from Erlang code (e.g. a request structure passed to a callback written in Alpaca from something like Webmachine or Cowboy).

Both private types and limited implementation imports (e.g. import_type a.x.X) may well be useful but I think there are some larger semantic implications that are worth discussing in a bit more detail before we go down those roads.

Thoughts?

@danabr
Copy link
Contributor

danabr commented Jan 14, 2017

+1 on this, since it is fairly easy to reason about.

j14159 added a commit to j14159/alpaca that referenced this issue Jan 24, 2017
Fixes alpaca-lang#94.  We can now refer to type constructors with their module name
rather than needing to import them.  This is restricted to types that
are exported by their modules which helps towards alpaca-lang#62.
@j14159 j14159 mentioned this issue Jan 24, 2017
j14159 added a commit to j14159/alpaca that referenced this issue Jan 27, 2017
Fixes alpaca-lang#94.  We can now refer to type constructors with their module name
rather than needing to import them.  This is restricted to types that
are exported by their modules which helps towards alpaca-lang#62.
j14159 added a commit to j14159/alpaca that referenced this issue Jan 28, 2017
Fixes alpaca-lang#94.  We can now refer to type constructors with their module name
rather than needing to import them.  This is restricted to types that
are exported by their modules which helps towards alpaca-lang#62.
j14159 added a commit to j14159/alpaca that referenced this issue Jan 31, 2017
Fixes alpaca-lang#62

This was the last bit to wrap up issue alpaca-lang#62 so that unexported types can
be referred to by other modules' types without exposing their
implementation.  This correspondingly does not allow an unexported
type's constructors to be used.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

5 participants