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

Add a type t to many signatures #21

Open
ratmice opened this issue Jan 9, 2017 · 8 comments
Open

Add a type t to many signatures #21

ratmice opened this issue Jan 9, 2017 · 8 comments

Comments

@ratmice
Copy link

ratmice commented Jan 9, 2017

I didn't find any issues discussing this, but guess it possibly has come up in the past,
apologies If I have missed that.

It would be convenient if many of the signatures provided by the basis came with a 'type t'.
an implementation which adds these is the extended-basis from mltonlib, but it would be nice if the basis provided them itself.

https://github.com/MLton/mltonlib/blob/92450815c771774f3f1d6ffd245802f262fa9b0f/com/ssh/extended-basis/unstable/detail/bootstrap.sml

a simple use case, in the following example:
signature STRING_STUFF =
sig
type t;
val fromString: string -> t option;
val toString : t -> string;
end

functor Something(StringStuff : STRING_STUFF) =
struct
(* ... )
end
(
currently you must first add the type t *)
structure IntStringStuff = struct open Int type t = int end :> STRING_STUFF;
structure Foo = Something(IntStringStuff);

(* If it was included in the basis one could just use the following instead *)
structure Foo = Something(Int :> STRING_STUFF);

given that it is possible to add these to the existing basis as mltonlib does, code like above can even be backwards compatible given a small shim, I'm not sure what if any difference it might have on compile times, if that is an argument against it, it could be measured...

@jonsterling
Copy link

One disadvantage of the type t convention is that opening modules leads to the currently-in-scope definition of t being clobbered. Maybe best practice is just to rarely, if ever, open a module; but most SML code does open fairly frequently.

@JohnReppy
Copy link
Contributor

While I find myself using the t convention much more often in my own code these days, I think that there are some reasons that it would be a mistake to do a wholesale update of the existing basis interfaces to follow the convention.

The potential problems with conflicts because of open have already been mentioned (there is a lot of legacy code that uses open heavily). In addition, there is also the problem with error messages. If all of your types are called t, it can be hard to figure out some type errors.

@jonsterling
Copy link

@JohnReppy The error messages point is a really good one that I didn't think of! Yeah, I've had that happen with my own code that uses type t everywhere, which makes me think that it may be a mistake in many cases.

My preference would be to keep the Basis as it is, not using this convention; separately, there is the issue that in SML's concrete notation, it is fairly verbose to rename things when instantiating a functor, but that's something that could be addressed at the level of the language (if we do ever decide to address it).

@ratmice
Copy link
Author

ratmice commented Jan 9, 2017

Yeah, I had thought of the error message thing, what I had figured was that since we wouldn't be deprecating the existing type (e.g. Int.int) and Int.int and Int.t would be type aliases representing the same type perhaps basis implementers could order their declaration such that the preferred one comes first.

that is, I wasn't sure the error message thing was an issue except when 'type t' is the type declaration.

but yeah, code that uses open I can see how that is problematic

@ratmice
Copy link
Author

ratmice commented Jan 10, 2017

An attempt at a compromise, so far the 2 objections are:

  1. Existing uses of open can clobber variables
  2. Error messages may be less helpful when using this style.

Regarding the first, my initial reaction was that code such as:

structure ShootFoot =
struct
type t = String.string;
open Int;
(* ... presumably uses t expecting string. *)
end

is problematic due to the ordering of the type declaration and the open,
I do find it hard to be sympathetic to such code, I see it as needlessly fragile, and easy to switch the ordering of so that string clobbers Int.t instead.

Alas, I do see how others could be, so I wonder whether renaming this proposed type
to something other than type t would be an appropriate compromise which significantly reduces the probability of crashing.

I don't have a particularly good proposal for a new name, the best I could come up with is type basis_t offhand, where the intent is that basis_t is some type implementing a signature or the use of a subset of a signature defined by the basis library.

the name basis_t provides a rather limited bit of additional information over t in the error message case as well.

Regarding the second, this only really seems to me to be a problem when using open
otherwise the user should see (for example) IntStringStuff.t as in the following (addition to the code in the initial issue):

val to = IntStringStuff.toString;
open IntStringStuff;
to;
type t = unit;
to;

which results in types being printed as such (on sml/nj) the key point being that the user only sees
't' when t is within top-level scope, otherwise they see the more helpful IntStringStuff.t.

val to = fn : IntStringStuff.t -> string
opening IntStringStuff
  type t
  val fromString : string -> t option
  val toString : t -> string
val it = fn : t -> string
type t = unit
val it = fn : IntStringStuff.t -> string

There isn't much I can imagine which would solve this completely, short of inventing some new type pretty printing mechanism to be added to the language e.g. type t named "int" = int;
I'm not sure how I feel about such an idea, my gut reaction is not very positive

@RobertHarper
Copy link
Contributor

RobertHarper commented Jan 10, 2017 via email

@jonsterling
Copy link

the problem of there only ever being one meaning for “t” in a scope is probably unavoidable. for myself i long ago stopped using open. it’s just as fine to write structure S = MyBigStructure and then use S.t and S.f, etc. the standard basis is maybe more problematic, particularly since there is ad hoc syntax for, say, Int.t, but for my code, i’m fine with short names.

Yeah... In my own code and libraries, I generally avoid open and just rename a long structure into a short one as you mention; I think that people tend to expect stuff from the basis to be open-able, but maybe this is a mistake.

I did not know about this interesting idea from TILT! Sounds very promising.

@jonsterling
Copy link

jonsterling commented Jan 10, 2017

EDIT: I suppose the main problem with the approach of not using open is that you forfeit the ability to use infix operators. For this reason, sometimes I put infix operators in a separate module like MyModuleNotation, but it is a little clunky.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants