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

added result type #147

Closed
wants to merge 2 commits into
base: trunk
from

Conversation

Projects
None yet
@yminsky

yminsky commented Mar 2, 2015

This PR adds a new result type, with the following signature:

type ('a,'b) result = Ok of 'a | Error of 'b

to Pervasives. This is very similar to a type that shows up in many
existing libraries, and should hopefully allow us to converge on one
definition that can be shared. Here are some other known use-cases in
prominent released libraries.

The first three of these use the polymorphic variant type,

[`Ok of 'a | `Error of 'b]

Batteries uses an ordinary variant with this layout:

type ('a,'b) t = Ok of 'a | Bad of 'b

Core_kernel uses an ordinary variant with this layout:

type ('a,'b) t = Ok of 'a | Error of 'b

After some discussion (notably including Daniel Bunzli), it seems like
the ordinary variant approach is better from a perspective of the less
error prone typing discipline of ordinary variants. In particular,
OCaml correctly detects that the following code is broken:

type ('a,'b) result =
| Ok of 'a
| Error of 'b

module Z : sig
  val f : ('a list,'b) result -> int
end = struct
  let f = function
    | Ok [] -> 0
    | Ok (_::_) -> 1
    | (OK | Error _) -> 2
end

But lets the same code done with polymorphic variants compile without
issues.

type ('a,'b) presult =
 [ `Ok of 'a
 | `Error of 'b ]

module Z : sig
  val f : ('a list,'b) presult -> int
end = struct
  let f = function
    | `Ok [] -> 0
    | `Ok (_::_) -> 1
    | (`OK | `Error _) -> 2
end

The hope is that by putting one of these in the stdlib, we can have
better interoperability between libraries on what seems to be a quite
basic type, without having to use the more complex and error prone
polymorphic variant solution. Bunzli among others have expressed some
concern with needing to pull in a separate package for such a basic
type, which is what motivates moving it to the stdlib.

@rgrinberg

This comment has been minimized.

Show comment
Hide comment
@rgrinberg

rgrinberg Mar 2, 2015

Member

As an OCaml user, I'd like to voice my support for this. This incompatibility between the standard libs is very painful and this PR would hopefully nudge all of them to be compatible with each other.

Member

rgrinberg commented Mar 2, 2015

As an OCaml user, I'd like to voice my support for this. This incompatibility between the standard libs is very painful and this PR would hopefully nudge all of them to be compatible with each other.

@Drup

This comment has been minimized.

Show comment
Hide comment
@Drup

Drup Mar 2, 2015

Contributor

If we add this in pervasive, I'm pretty sure people will want a compatibility layer for old OCaml versions, so we will need an external library anyway and this will use the same opam mechanism than byte. I don't want to wait 2017 to use Result in debian.

If everyone agrees on the interface (and I'm quite convinced we can agree on something now that everyone realized the current situation is a huge PITA), I don't see why it should be in the stdlib.

And anyway, agreeing on a small external library is exactly as difficult as agreeing on something being integrated in Pervasive.

I vote for a mini package, in the repository https://github.com/ocaml and nothing in the stdlib. I have pretty much the same opinion about #80.

Contributor

Drup commented Mar 2, 2015

If we add this in pervasive, I'm pretty sure people will want a compatibility layer for old OCaml versions, so we will need an external library anyway and this will use the same opam mechanism than byte. I don't want to wait 2017 to use Result in debian.

If everyone agrees on the interface (and I'm quite convinced we can agree on something now that everyone realized the current situation is a huge PITA), I don't see why it should be in the stdlib.

And anyway, agreeing on a small external library is exactly as difficult as agreeing on something being integrated in Pervasive.

I vote for a mini package, in the repository https://github.com/ocaml and nothing in the stdlib. I have pretty much the same opinion about #80.

@avsm

This comment has been minimized.

Show comment
Hide comment
@avsm

avsm Mar 2, 2015

Member

I think one issue with such tiny libraries is who 'owns' them. I'd be happy for small modules that are proposed for the standard library to go under the ocaml/ organisation on GitHub to make it clear that they are the result of some consensus. Same applies to Uchar in #80.

I do think it's a good idea to include them in stdlib, but a transition package is necessary for this to happen anyway for compatibility reasons.

Member

avsm commented Mar 2, 2015

I think one issue with such tiny libraries is who 'owns' them. I'd be happy for small modules that are proposed for the standard library to go under the ocaml/ organisation on GitHub to make it clear that they are the result of some consensus. Same applies to Uchar in #80.

I do think it's a good idea to include them in stdlib, but a transition package is necessary for this to happen anyway for compatibility reasons.

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky Mar 2, 2015

In my mind, result is such a close parallel to option, that it strikes me as a truly natural fit with stdlib, and I think it makes sense there. I agree that we'll need a compatibility library for older compiler versions, but there is some unifying effect of putting things in the stdlib, and I think that would be a valuable contribution on its own.

yminsky commented Mar 2, 2015

In my mind, result is such a close parallel to option, that it strikes me as a truly natural fit with stdlib, and I think it makes sense there. I agree that we'll need a compatibility library for older compiler versions, but there is some unifying effect of putting things in the stdlib, and I think that would be a valuable contribution on its own.

@rgrinberg

This comment has been minimized.

Show comment
Hide comment
@rgrinberg

rgrinberg Mar 2, 2015

Member

I don't want to wait 2017 to use Result in debian.

But even with an external library you'd need your users to have opam installed. In which case, what is preventing from using a recent version of OCaml? The only possible scenario where having an external lib is favorable is if you want to use opam and the system compiler which seems very niche and pointless to me.

I think the real advantage of having this in the external lib is that it allows the team be a little less conservative and starting adding useful combinators.

Member

rgrinberg commented Mar 2, 2015

I don't want to wait 2017 to use Result in debian.

But even with an external library you'd need your users to have opam installed. In which case, what is preventing from using a recent version of OCaml? The only possible scenario where having an external lib is favorable is if you want to use opam and the system compiler which seems very niche and pointless to me.

I think the real advantage of having this in the external lib is that it allows the team be a little less conservative and starting adding useful combinators.

@Drup

This comment has been minimized.

Show comment
Hide comment
@Drup

Drup Mar 2, 2015

Contributor

It's easier to bundle a lib than a future version of Pervasive.

Contributor

Drup commented Mar 2, 2015

It's easier to bundle a lib than a future version of Pervasive.

@c-cube

This comment has been minimized.

Show comment
Hide comment
@c-cube

c-cube Mar 2, 2015

Contributor

I kind of agree with @Drup about the need for compatibility with older versions (a type only available for >= 4.03 will not be used in practice before a very long time). However, it seems like people in the OCaml community like to disagree on interfaces, siwe of dependencies, etc. bound to libraries. I think it would be interesting to have "special" nano-libraries that only declare a type (be it Result.t, Sexp.t, UChar.t) and let regular libraries compete for providing a nice API to the type.

In particular, here, that's no use trying to impose a common Result library with combinators and so on: Core will keep using its own API, batteries its own one as well, etc. A library containing only the type is very consensual, doesn't need a version number, and could have a special prefix in opam (like type-result, type-sexp, ... similar to conf-gmp or conf-ssl).

Contributor

c-cube commented Mar 2, 2015

I kind of agree with @Drup about the need for compatibility with older versions (a type only available for >= 4.03 will not be used in practice before a very long time). However, it seems like people in the OCaml community like to disagree on interfaces, siwe of dependencies, etc. bound to libraries. I think it would be interesting to have "special" nano-libraries that only declare a type (be it Result.t, Sexp.t, UChar.t) and let regular libraries compete for providing a nice API to the type.

In particular, here, that's no use trying to impose a common Result library with combinators and so on: Core will keep using its own API, batteries its own one as well, etc. A library containing only the type is very consensual, doesn't need a version number, and could have a special prefix in opam (like type-result, type-sexp, ... similar to conf-gmp or conf-ssl).

@agarwal

This comment has been minimized.

Show comment
Hide comment
@agarwal

agarwal Mar 2, 2015

Member

This patch adds only a type, which I feel makes it appropriate for the stdlib. If you make a separate lib, presumably you will also be providing a bunch functions related to the type. At that point you again start having divergence of opinions.

he real advantage of having this in the external lib is that it allows the team be a little less conservative and starting adding useful combinators

Which is exactly the problem. We won't reach consensus on what these combinators should be.

Well, I see @c-cube is really proposing a library with only a single type definition. Seems a bit extreme.

Member

agarwal commented Mar 2, 2015

This patch adds only a type, which I feel makes it appropriate for the stdlib. If you make a separate lib, presumably you will also be providing a bunch functions related to the type. At that point you again start having divergence of opinions.

he real advantage of having this in the external lib is that it allows the team be a little less conservative and starting adding useful combinators

Which is exactly the problem. We won't reach consensus on what these combinators should be.

Well, I see @c-cube is really proposing a library with only a single type definition. Seems a bit extreme.

@c-cube

This comment has been minimized.

Show comment
Hide comment
@c-cube

c-cube Mar 2, 2015

Contributor

@agarwal well, a full module in the stdlib won't just work (nor get merged anyway); a full library outside will make people disagree on who owns it and what should go inside; so a one-type module in the stdlib and a lib version outside (for older versions) would do the trick, I think.

Contributor

c-cube commented Mar 2, 2015

@agarwal well, a full module in the stdlib won't just work (nor get merged anyway); a full library outside will make people disagree on who owns it and what should go inside; so a one-type module in the stdlib and a lib version outside (for older versions) would do the trick, I think.

@Drup

This comment has been minimized.

Show comment
Hide comment
@Drup

Drup Mar 2, 2015

Contributor

Which is exactly the problem. We won't reach consensus on what these combinators should be.

We already did: everyone includes return, fail, bind, map, join, pp, equal. Just take the intersection of the various libraries, and you already have something non empty and useful enough. People are free to use the bigger libraries for fancy combinators.

Contributor

Drup commented Mar 2, 2015

Which is exactly the problem. We won't reach consensus on what these combinators should be.

We already did: everyone includes return, fail, bind, map, join, pp, equal. Just take the intersection of the various libraries, and you already have something non empty and useful enough. People are free to use the bigger libraries for fancy combinators.

@c-cube

This comment has been minimized.

Show comment
Hide comment
@c-cube

c-cube Mar 2, 2015

Contributor

Maybe in that case we could also have an Option module in the stdlib, with basically the same set of combinators. When one just needs Option.{map, flat_map}, it's annoying to have to use a dependency just for that.

Contributor

c-cube commented Mar 2, 2015

Maybe in that case we could also have an Option module in the stdlib, with basically the same set of combinators. When one just needs Option.{map, flat_map}, it's annoying to have to use a dependency just for that.

@rgrinberg

This comment has been minimized.

Show comment
Hide comment
@rgrinberg

rgrinberg Mar 2, 2015

Member

Can't take map because Core uses by default but I agree with your argument regardless.

Also, I'm not against base-result it's just that this type is so fundamental and uncontroversial that it really belongs to pervasives. In any case, when 2017 comes I'm sure we'd all be saying "darn why isn't result in pervasives" anyway :D

Member

rgrinberg commented Mar 2, 2015

Can't take map because Core uses by default but I agree with your argument regardless.

Also, I'm not against base-result it's just that this type is so fundamental and uncontroversial that it really belongs to pervasives. In any case, when 2017 comes I'm sure we'd all be saying "darn why isn't result in pervasives" anyway :D

@Drup

This comment has been minimized.

Show comment
Hide comment
@Drup

Drup Mar 2, 2015

Contributor

it's just that this type is so fundamental and uncontroversial that it really belongs to pervasives.

This is also the case of the option module, and the reason it's not in the stdlib is not that no one proposed it.

Contributor

Drup commented Mar 2, 2015

it's just that this type is so fundamental and uncontroversial that it really belongs to pervasives.

This is also the case of the option module, and the reason it's not in the stdlib is not that no one proposed it.

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky Mar 2, 2015

What I like about this is that it's a small step in the direction of agreeing on the type, and avoids more controversial questions of library design. We can all design our own Monad interface and decide how we like to order our arguments, but it's nice that we all agree on the option type. Similarly, it would be great if we could all agree on the result type as well. I strongly prefer the one-line patch (well, two if you count the mli) to something more ambitious.

yminsky commented Mar 2, 2015

What I like about this is that it's a small step in the direction of agreeing on the type, and avoids more controversial questions of library design. We can all design our own Monad interface and decide how we like to order our arguments, but it's nice that we all agree on the option type. Similarly, it would be great if we could all agree on the result type as well. I strongly prefer the one-line patch (well, two if you count the mli) to something more ambitious.

@Drup

This comment has been minimized.

Show comment
Hide comment
@Drup

Drup Mar 2, 2015

Contributor

By the way, with result in pervasive, the compatibility layer is especially annoying to do.

Contributor

Drup commented Mar 2, 2015

By the way, with result in pervasive, the compatibility layer is especially annoying to do.

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky Mar 2, 2015

It's a fair point. Indeed, I don't have a great idea about how to get both compatibility and having the type be implicitly available everywhere, in the same way option is. We can give up on that, of course, but it seems awkward to have such different handling for option and for result.

So, the options are:

  • Have a minimal Result module in the stdlib, and a compat module that provides the same. Constructors will be brought into the namespace explicitly, either by opening Result, or by opening another module like Core_kernel.Std which effectively acts as an alternate pervasives.
  • Provide result in Pervasives only. The compat module will provide the same type, but will require an explicit open to access it.
  • Provide result in Pervasives, as well as a Result module in stdlib that contains only that one type. The compatibility library would provide just the Result module. If you want to write code that works across versions, you must open Result.

I tend to think (3) is the best of these solutions. From the perspective of libraries like Core that provide their own pervasives-like module, these proposals are all equivalent. It's really about the experience of someone using OCaml without any of these extensions.

yminsky commented Mar 2, 2015

It's a fair point. Indeed, I don't have a great idea about how to get both compatibility and having the type be implicitly available everywhere, in the same way option is. We can give up on that, of course, but it seems awkward to have such different handling for option and for result.

So, the options are:

  • Have a minimal Result module in the stdlib, and a compat module that provides the same. Constructors will be brought into the namespace explicitly, either by opening Result, or by opening another module like Core_kernel.Std which effectively acts as an alternate pervasives.
  • Provide result in Pervasives only. The compat module will provide the same type, but will require an explicit open to access it.
  • Provide result in Pervasives, as well as a Result module in stdlib that contains only that one type. The compatibility library would provide just the Result module. If you want to write code that works across versions, you must open Result.

I tend to think (3) is the best of these solutions. From the perspective of libraries like Core that provide their own pervasives-like module, these proposals are all equivalent. It's really about the experience of someone using OCaml without any of these extensions.

@agarwal

This comment has been minimized.

Show comment
Hide comment
@agarwal

agarwal Mar 2, 2015

Member

everyone includes return, fail, bind, map, join, pp, equal

There isn't agreement even on these. Some libraries label the arguments and some don't.

Member

agarwal commented Mar 2, 2015

everyone includes return, fail, bind, map, join, pp, equal

There isn't agreement even on these. Some libraries label the arguments and some don't.

@damiendoligez

This comment has been minimized.

Show comment
Hide comment
@damiendoligez

damiendoligez Mar 2, 2015

Member

@yminsky Your options 2 and 3 are equivalent if OPAM provides a compatibility module: in 4.02 it would provide the result type and in 4.03 it would be empty (for option 2) or absent (for option 3).

Member

damiendoligez commented Mar 2, 2015

@yminsky Your options 2 and 3 are equivalent if OPAM provides a compatibility module: in 4.02 it would provide the result type and in 4.03 it would be empty (for option 2) or absent (for option 3).

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky Mar 2, 2015

Let's see if I understand what you're saying. With approach 2, a
compatibility module would be required for code that wants to be
compatible that runs either before or after the switch in OCaml. With
approach 3, you'd only need the compatibility module if you were
running before the switch in OCaml.

In either case, compatible code would have to be careful to only refer
to the compatibility module. This would be easy for frameworks like
batteries and core with their own pervasives replacement.

This makes me think that solution 2, which is what is in the patch, is
preferable, since it leaves OCaml itself in a more consistent final
state --- in particular, with result looking just like option.

yminsky commented Mar 2, 2015

Let's see if I understand what you're saying. With approach 2, a
compatibility module would be required for code that wants to be
compatible that runs either before or after the switch in OCaml. With
approach 3, you'd only need the compatibility module if you were
running before the switch in OCaml.

In either case, compatible code would have to be careful to only refer
to the compatibility module. This would be easy for frameworks like
batteries and core with their own pervasives replacement.

This makes me think that solution 2, which is what is in the patch, is
preferable, since it leaves OCaml itself in a more consistent final
state --- in particular, with result looking just like option.

@Drup

This comment has been minimized.

Show comment
Hide comment
@Drup

Drup Mar 3, 2015

Contributor

Hum, what about that: we put the type in pervasive as @yminsky proposed and we put a small opam package with only a Result module with:

this for ≤ 4.02:

type ('a,'b) t = Ok of 'a | Error of 'b

and this for 4.03:

type ('a,'b) t = ('a,'b) result = Ok of 'a | Error of 'b

This is a sort of mix of (1) and (2). It doesn't force you to open if you prefer qualified usage to get the compatibility layer (much like the Bytes compatibility layer) and we can put a minimal amount of harmless combinators in it.

Contributor

Drup commented Mar 3, 2015

Hum, what about that: we put the type in pervasive as @yminsky proposed and we put a small opam package with only a Result module with:

this for ≤ 4.02:

type ('a,'b) t = Ok of 'a | Error of 'b

and this for 4.03:

type ('a,'b) t = ('a,'b) result = Ok of 'a | Error of 'b

This is a sort of mix of (1) and (2). It doesn't force you to open if you prefer qualified usage to get the compatibility layer (much like the Bytes compatibility layer) and we can put a minimal amount of harmless combinators in it.

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky Mar 3, 2015

That's precisely what I was thinking, and I agree it's better, for the reasons you said.

I'd argue against putting any combinators in it for now. I think we should keep the patch simple, and keep the support in the stdlib parallel between option and result. I think the eventual fate of the compatibility layer is to just be deleted, so keeping other stuff in it is a mistake.

yminsky commented Mar 3, 2015

That's precisely what I was thinking, and I agree it's better, for the reasons you said.

I'd argue against putting any combinators in it for now. I think we should keep the patch simple, and keep the support in the stdlib parallel between option and result. I think the eventual fate of the compatibility layer is to just be deleted, so keeping other stuff in it is a mistake.

@Drup

This comment has been minimized.

Show comment
Hide comment
@Drup

Drup Mar 3, 2015

Contributor

well, the Result module would not be in the stdlib at all, so the patch stay exactly the one you did.

Contributor

Drup commented Mar 3, 2015

well, the Result module would not be in the stdlib at all, so the patch stay exactly the one you did.

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky Mar 3, 2015

I would argue that the compat module should be a bare minimum synchronization point. There are already several natural places for people to develop opinionated APIs to Result --- Core, Batteries, Bunzli's libraries, Containers. I don't think this compatibility shim should add a new one to the mix.

yminsky commented Mar 3, 2015

I would argue that the compat module should be a bare minimum synchronization point. There are already several natural places for people to develop opinionated APIs to Result --- Core, Batteries, Bunzli's libraries, Containers. I don't think this compatibility shim should add a new one to the mix.

@alainfrisch

This comment has been minimized.

Show comment
Hide comment
@alainfrisch

alainfrisch Mar 3, 2015

Contributor

It would be interesting to quantify how many people are actually stuck with old versions of OCaml but would still insist to use latest versions of libraries such as the ones mentioned in this discussion. Does anybody have any rough idea of the kind of reasons for people to be in this situation? I can imagine very conservative projects (esp. in industrial settings, or for stable end-user applications) that don't want to upgrade OCaml too often, but are they the kind of projects jumping to adopt new libraries or versions of libraries?

If libraries put themselves the constraints of supporting older versions of OCaml, how do they do that? By maintaining a single code base (which means they can't benefit from any improvement to the language or stdlib), by using conditional compilation or maintaining several release branches (in which cases they could easily accomodate a special migration package putting the type in a specific module)?

Contributor

alainfrisch commented Mar 3, 2015

It would be interesting to quantify how many people are actually stuck with old versions of OCaml but would still insist to use latest versions of libraries such as the ones mentioned in this discussion. Does anybody have any rough idea of the kind of reasons for people to be in this situation? I can imagine very conservative projects (esp. in industrial settings, or for stable end-user applications) that don't want to upgrade OCaml too often, but are they the kind of projects jumping to adopt new libraries or versions of libraries?

If libraries put themselves the constraints of supporting older versions of OCaml, how do they do that? By maintaining a single code base (which means they can't benefit from any improvement to the language or stdlib), by using conditional compilation or maintaining several release branches (in which cases they could easily accomodate a special migration package putting the type in a specific module)?

@avsm

This comment has been minimized.

Show comment
Hide comment
@avsm

avsm Mar 3, 2015

Member

It would be interesting to quantify how many people are actually stuck with old versions of OCaml but would still insist to use latest versions of libraries such as the ones mentioned in this discussion.

The primary reason is upstreaming binary packages into distributions. They hold back widescale adoption of a new compiler version until the slowest (CentOS or Debian) catch up with the new release. Moving those distributions to a multi-version capable model where several compilers could be simultaneously installed is one solution, and OPAM is another. Neither is as perfect as just typing in apt-get install myapp and having it work.

Member

avsm commented Mar 3, 2015

It would be interesting to quantify how many people are actually stuck with old versions of OCaml but would still insist to use latest versions of libraries such as the ones mentioned in this discussion.

The primary reason is upstreaming binary packages into distributions. They hold back widescale adoption of a new compiler version until the slowest (CentOS or Debian) catch up with the new release. Moving those distributions to a multi-version capable model where several compilers could be simultaneously installed is one solution, and OPAM is another. Neither is as perfect as just typing in apt-get install myapp and having it work.

@alainfrisch

This comment has been minimized.

Show comment
Hide comment
@alainfrisch

alainfrisch Mar 3, 2015

Contributor

The primary reason is upstreaming binary packages into distributions.

I would have thought that users that rely on binary packages from their distribution would not be the same ones that need to use the most recent versions of libraries. Do you have an idea on how we could quantify the actual need for providing latest versions of libraries through "slow" binary distribution stuck with old versions of OCaml?

If the community wants to commit to long-term maintenance of old versions of OCaml, shouldn't this be done by having multiple released versions of libraries (e.g. "Core for OCaml 3.12")? Only important bug fixes or new features would need to be backported (this would also guarantee more stable APIs for people who don't need/want to benefit from bleeding edge features).

Contributor

alainfrisch commented Mar 3, 2015

The primary reason is upstreaming binary packages into distributions.

I would have thought that users that rely on binary packages from their distribution would not be the same ones that need to use the most recent versions of libraries. Do you have an idea on how we could quantify the actual need for providing latest versions of libraries through "slow" binary distribution stuck with old versions of OCaml?

If the community wants to commit to long-term maintenance of old versions of OCaml, shouldn't this be done by having multiple released versions of libraries (e.g. "Core for OCaml 3.12")? Only important bug fixes or new features would need to be backported (this would also guarantee more stable APIs for people who don't need/want to benefit from bleeding edge features).

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky Mar 3, 2015

Here's a minimal compatibility package:

https://github.com/janestreet/result

Responding to Alain: with respect to this PR, I think the compatibility package is warranted: this is a rather fundamental type, and it's easy to see libraries that want some modicum of portability across OCaml versions using it. It's also useful for forward compatibility, i.e., Core can start using this now, and so when OCaml moves to a new version, people using Core with a newer version of OCaml can use the shared type in pervasives.

yminsky commented Mar 3, 2015

Here's a minimal compatibility package:

https://github.com/janestreet/result

Responding to Alain: with respect to this PR, I think the compatibility package is warranted: this is a rather fundamental type, and it's easy to see libraries that want some modicum of portability across OCaml versions using it. It's also useful for forward compatibility, i.e., Core can start using this now, and so when OCaml moves to a new version, people using Core with a newer version of OCaml can use the shared type in pervasives.

@dbuenzli

This comment has been minimized.

Show comment
Hide comment
@dbuenzli

dbuenzli Mar 3, 2015

Contributor

Le mardi, 3 mars 2015 à 13:30, Yaron Minsky a écrit :

Here's a minimal compatibility package:
https://github.com/janestreet/result

Would it be possible to define the type in Pervasives_result rather than Result for < 4.03 ? I think the Pervasives_result conveys better the provided compatibility and this is anyway meant to be opened.

Otherwise I can change my package name but it's always painful to rename packages once the infrastructure has been setup (I usually try not to squat too common toplevel names by adding a silly naming scheme, but somehow in that case I forgot to do it).

Best,

Daniel

Contributor

dbuenzli commented Mar 3, 2015

Le mardi, 3 mars 2015 à 13:30, Yaron Minsky a écrit :

Here's a minimal compatibility package:
https://github.com/janestreet/result

Would it be possible to define the type in Pervasives_result rather than Result for < 4.03 ? I think the Pervasives_result conveys better the provided compatibility and this is anyway meant to be opened.

Otherwise I can change my package name but it's always painful to rename packages once the infrastructure has been setup (I usually try not to squat too common toplevel names by adding a silly naming scheme, but somehow in that case I forgot to do it).

Best,

Daniel

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky Mar 3, 2015

I'm frankly a little uneasy with your Result package grabbing the toplevel name of Result. I agree that Pervasives_result seems fine for this case, and I'm happy to change it, but I wonder if you could change your Result module too to something that doesn't grab the spot from the toplevel namespace.

Generally, I wonder if we could flip to a world where the "good" toplevel names are mostly left free, except by meta packages that rearrange the namespace, like Core.Std. With module aliases, such module rearranging packages are now very cheap.

yminsky commented Mar 3, 2015

I'm frankly a little uneasy with your Result package grabbing the toplevel name of Result. I agree that Pervasives_result seems fine for this case, and I'm happy to change it, but I wonder if you could change your Result module too to something that doesn't grab the spot from the toplevel namespace.

Generally, I wonder if we could flip to a world where the "good" toplevel names are mostly left free, except by meta packages that rearrange the namespace, like Core.Std. With module aliases, such module rearranging packages are now very cheap.

@dbuenzli

This comment has been minimized.

Show comment
Hide comment
@dbuenzli

dbuenzli Mar 3, 2015

Contributor

@yminsky, agreed I'll change the name.

Contributor

dbuenzli commented Mar 3, 2015

@yminsky, agreed I'll change the name.

@Drup

This comment has been minimized.

Show comment
Hide comment
@Drup

Drup Mar 3, 2015

Contributor

I would prefer Result.t, which would fit in signatures much better than Pervasive_Result.t (same as Bytes.t really, since you can't use bytes except in >= 4.02)

Contributor

Drup commented Mar 3, 2015

I would prefer Result.t, which would fit in signatures much better than Pervasive_Result.t (same as Bytes.t really, since you can't use bytes except in >= 4.02)

@c-cube

This comment has been minimized.

Show comment
Hide comment
@c-cube

c-cube Mar 3, 2015

Contributor

Well you won't be supposed to use the package directly, I think, but your favorite stdlib extension/replacement will have a FooResult module in which type ('a, 'b) t = ('a, 'b) Pervasives_result.t.

Since there are other types for which sharing a unique definition makes sense, could we reach a convention on naming? For instance, all packages that declare only a type, to be shared among libraries, could be named type-foo for the type foo, and provide a type Type_foo.t?

Contributor

c-cube commented Mar 3, 2015

Well you won't be supposed to use the package directly, I think, but your favorite stdlib extension/replacement will have a FooResult module in which type ('a, 'b) t = ('a, 'b) Pervasives_result.t.

Since there are other types for which sharing a unique definition makes sense, could we reach a convention on naming? For instance, all packages that declare only a type, to be shared among libraries, could be named type-foo for the type foo, and provide a type Type_foo.t?

@edwintorok

This comment has been minimized.

Show comment
Hide comment
@edwintorok

edwintorok Mar 3, 2015

Contributor

We already have a growing number of version incompatibilities that OCaml applications/libraries have to be aware of when trying to be compatible with old versions while still using new features:

  • ocaml-bytes if you are stuck with an old version of OCaml and old version of ocamlfind
  • base-bytes if you have a new enough ocamlfind but old version of OCaml (odd combination, not something you'd encounter in distro packaging, but I use it myself)
  • |> operator introduced in 4.01 (any compatibility lib for this?)
  • type ('a,'b) result discussed here
  • some convenience functions in other stdlib modules (Set.of_list, ...)

I think that the compatibility modules should be as easy to use as possible, and small, with the balance towards easy to use rather than minimal.
Surely having separate compatibility libraries is minimal on its own, but just makes it harder to use as each new stdlib feature would require using yet another compatibility library, which requires updating your build system/packaging again, etc.

I'd suggest that there should be just one compatibility library for everything related to the stdlib, based on the bytes model:

  • ocaml-compat module usable with old versions of OCaml and old versions of ocamlfind
  • base-compat ocamlfind module provided with newer versions of ocamlfind
  • contain only things that are in a newer OCaml version's stdlib, and no extra features or combinators
  • is pure OCaml code
  • future improvements on stdlib would be easier to make as there would be just one place to update (this compatibility module) and application writers would already be used to having it if they want to be compatible with older versions of OCaml

The bytes module could be an alias for this new compatibility module.

Contributor

edwintorok commented Mar 3, 2015

We already have a growing number of version incompatibilities that OCaml applications/libraries have to be aware of when trying to be compatible with old versions while still using new features:

  • ocaml-bytes if you are stuck with an old version of OCaml and old version of ocamlfind
  • base-bytes if you have a new enough ocamlfind but old version of OCaml (odd combination, not something you'd encounter in distro packaging, but I use it myself)
  • |> operator introduced in 4.01 (any compatibility lib for this?)
  • type ('a,'b) result discussed here
  • some convenience functions in other stdlib modules (Set.of_list, ...)

I think that the compatibility modules should be as easy to use as possible, and small, with the balance towards easy to use rather than minimal.
Surely having separate compatibility libraries is minimal on its own, but just makes it harder to use as each new stdlib feature would require using yet another compatibility library, which requires updating your build system/packaging again, etc.

I'd suggest that there should be just one compatibility library for everything related to the stdlib, based on the bytes model:

  • ocaml-compat module usable with old versions of OCaml and old versions of ocamlfind
  • base-compat ocamlfind module provided with newer versions of ocamlfind
  • contain only things that are in a newer OCaml version's stdlib, and no extra features or combinators
  • is pure OCaml code
  • future improvements on stdlib would be easier to make as there would be just one place to update (this compatibility module) and application writers would already be used to having it if they want to be compatible with older versions of OCaml

The bytes module could be an alias for this new compatibility module.

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky Mar 3, 2015

@edwintorok That's a really intriguing idea. Maybe this is something that could be hosted under the aegis of ocaml.org (at github.com/ocaml), since this is clearly a pretty broad service. This can be the one place we go for these compatibility shims. The issue of sharing types more generally is then separated out, which seems appropriate.

yminsky commented Mar 3, 2015

@edwintorok That's a really intriguing idea. Maybe this is something that could be hosted under the aegis of ocaml.org (at github.com/ocaml), since this is clearly a pretty broad service. This can be the one place we go for these compatibility shims. The issue of sharing types more generally is then separated out, which seems appropriate.

@damiendoligez

This comment has been minimized.

Show comment
Hide comment
@damiendoligez

damiendoligez Mar 3, 2015

Member

@yminsky As the travis build shows, your patch breaks otherlibs/threads. You have to add the type's implementation to otherlib/threads/pervasives.ml

Member

damiendoligez commented Mar 3, 2015

@yminsky As the travis build shows, your patch breaks otherlibs/threads. You have to add the type's implementation to otherlib/threads/pervasives.ml

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky Mar 3, 2015

Sorry. Stupid mistake. Fixed now.

yminsky commented Mar 3, 2015

Sorry. Stupid mistake. Fixed now.

@gasche

This comment has been minimized.

Show comment
Hide comment
@gasche

gasche May 15, 2015

Member

I think there is a problem in the type-checker. Looking at the first example

OCAMLOPT -c tools/JSON.ml
File "tools/JSON.ml", line 26, characters 15-20:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
File "tools/JSON.ml", line 30, characters 55-60:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
File "tools/JSON.ml", line 36, characters 15-20:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
File "tools/JSON.ml", line 38, characters 15-20:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
File "tools/JSON.ml", line 47, characters 15-20:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
File "tools/JSON.ml", line 48, characters 18-23:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.

The problematic code is https://github.com/savonet/liquidsoap/blob/master/src/tools/JSON.ml, but there you see that the only use of Error is in raise Error.

Indeed, toplevel experimentation confirms that:

$ ocaml -w A
        OCaml version 4.03.0+dev7-2015-02-08

# exception Error;;
exception Error
# raise Error;;
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
Exception: Error.
# raise (Error : exn);;
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
Exception: Error.

This is wrong, this case is not ambiguous and should not warn.

We should open a bugreport about this.

Member

gasche commented May 15, 2015

I think there is a problem in the type-checker. Looking at the first example

OCAMLOPT -c tools/JSON.ml
File "tools/JSON.ml", line 26, characters 15-20:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
File "tools/JSON.ml", line 30, characters 55-60:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
File "tools/JSON.ml", line 36, characters 15-20:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
File "tools/JSON.ml", line 38, characters 15-20:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
File "tools/JSON.ml", line 47, characters 15-20:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
File "tools/JSON.ml", line 48, characters 18-23:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.

The problematic code is https://github.com/savonet/liquidsoap/blob/master/src/tools/JSON.ml, but there you see that the only use of Error is in raise Error.

Indeed, toplevel experimentation confirms that:

$ ocaml -w A
        OCaml version 4.03.0+dev7-2015-02-08

# exception Error;;
exception Error
# raise Error;;
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
Exception: Error.
# raise (Error : exn);;
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.
Exception: Error.

This is wrong, this case is not ambiguous and should not warn.

We should open a bugreport about this.

@lpw25

This comment has been minimized.

Show comment
Hide comment
@lpw25

lpw25 May 15, 2015

Contributor

No, do not just turn on warnings without knowing what they are for. Warnings which are off by default are off by default for a reason. Some of the warnings are essentially mutually exclusive (e.g. warning 41 and 42), you are only supposed to turn one of them on.

Warning 41 is for people who feel that choosing a constructor based on normal value shadowing rules is inherently bad, and that all such uses should be explicitly disambiguated with a type annotation. This is a particularly paranoid view and I doubt that warning 41 is used by anyone deliberately in practice.

Contributor

lpw25 commented May 15, 2015

No, do not just turn on warnings without knowing what they are for. Warnings which are off by default are off by default for a reason. Some of the warnings are essentially mutually exclusive (e.g. warning 41 and 42), you are only supposed to turn one of them on.

Warning 41 is for people who feel that choosing a constructor based on normal value shadowing rules is inherently bad, and that all such uses should be explicitly disambiguated with a type annotation. This is a particularly paranoid view and I doubt that warning 41 is used by anyone deliberately in practice.

@damiendoligez

This comment has been minimized.

Show comment
Hide comment
@damiendoligez

damiendoligez May 15, 2015

Member

Indeed, some warnings do not fit all (or even most) programming styles, which is why they are disabled by default.

Still, as Gabriel said we need to fix this bug. Thanks for finding it.

Member

damiendoligez commented May 15, 2015

Indeed, some warnings do not fit all (or even most) programming styles, which is why they are disabled by default.

Still, as Gabriel said we need to fix this bug. Thanks for finding it.

@gasche

This comment has been minimized.

Show comment
Hide comment
@gasche

gasche May 15, 2015

Member

For the record, I strongly support using Warning 41 and I think it improves code quality. Considering it an error helps preserve the property that adding type annotations never changes the semantics of code.

I reported the type-directed disambiguation bug as PR#6872. We cannot properly evaluate the impact of the Error constructor on user code before this is fixed.

Member

gasche commented May 15, 2015

For the record, I strongly support using Warning 41 and I think it improves code quality. Considering it an error helps preserve the property that adding type annotations never changes the semantics of code.

I reported the type-directed disambiguation bug as PR#6872. We cannot properly evaluate the impact of the Error constructor on user code before this is fixed.

@lpw25

This comment has been minimized.

Show comment
Hide comment
@lpw25

lpw25 May 15, 2015

Contributor

For the record, I strongly support using Warning 41 and I think it improves code quality. Considering it an error helps preserve the property that adding type annotations never changes the semantics of code.

Surely warning 42 would be a better insurance of this, by avoiding disambiguation altogether.

Contributor

lpw25 commented May 15, 2015

For the record, I strongly support using Warning 41 and I think it improves code quality. Considering it an error helps preserve the property that adding type annotations never changes the semantics of code.

Surely warning 42 would be a better insurance of this, by avoiding disambiguation altogether.

@lpw25

This comment has been minimized.

Show comment
Hide comment
@lpw25

lpw25 May 15, 2015

Contributor

For the record, I strongly support using Warning 41 and I think it improves code quality. Considering it an error helps preserve the property that adding type annotations never changes the semantics of code.

Without Warning 41, adding a type annotation will still not change the semantics of code it will just change the type of that code.

Contributor

lpw25 commented May 15, 2015

For the record, I strongly support using Warning 41 and I think it improves code quality. Considering it an error helps preserve the property that adding type annotations never changes the semantics of code.

Without Warning 41, adding a type annotation will still not change the semantics of code it will just change the type of that code.

@gasche

This comment has been minimized.

Show comment
Hide comment
@gasche

gasche May 15, 2015

Member

Warning 42 completely disables a feature that can be used conveniently and safely (from the perspective of the criterion I gave above) when warning 41 is used.

# type t1 = A | B;;
# type t2 = B | A;;
# (A < B);;
- : bool = false
# ((A : t1) < B);;
- : bool = true
Member

gasche commented May 15, 2015

Warning 42 completely disables a feature that can be used conveniently and safely (from the perspective of the criterion I gave above) when warning 41 is used.

# type t1 = A | B;;
# type t2 = B | A;;
# (A < B);;
- : bool = false
# ((A : t1) < B);;
- : bool = true
@alainfrisch

This comment has been minimized.

Show comment
Hide comment
@alainfrisch

alainfrisch May 15, 2015

Contributor

helps preserve the property that adding type annotations never changes the semantics of code.

To be clear: the dynamic semantics (a version of it where constructors are represented by their name) isn't affected by the choice of type, with or without warning 41.

Contributor

alainfrisch commented May 15, 2015

helps preserve the property that adding type annotations never changes the semantics of code.

To be clear: the dynamic semantics (a version of it where constructors are represented by their name) isn't affected by the choice of type, with or without warning 41.

@lpw25

This comment has been minimized.

Show comment
Hide comment
@lpw25

lpw25 May 15, 2015

Contributor

Warning 42 completely disables a feature that can be used safely (from the perspective of the criterion I gave above) when warning 41 is used.

# type t1 = A | B;;
type t1 = A | B
# type t2 = B | A;;
type t2 = B | A
# (A < B);;
- : bool = false
# ((A : t1) < B);;
- : bool = true

Good example. Although I would argue that this is merely an example of why polymorphic compare is evil rather than why the combination of shadowing and disambiguation is evil.

Contributor

lpw25 commented May 15, 2015

Warning 42 completely disables a feature that can be used safely (from the perspective of the criterion I gave above) when warning 41 is used.

# type t1 = A | B;;
type t1 = A | B
# type t2 = B | A;;
type t2 = B | A
# (A < B);;
- : bool = false
# ((A : t1) < B);;
- : bool = true

Good example. Although I would argue that this is merely an example of why polymorphic compare is evil rather than why the combination of shadowing and disambiguation is evil.

@gasche

This comment has been minimized.

Show comment
Hide comment
@gasche

gasche May 15, 2015

Member

But this example holds with all form of type-definition-directed code generation (eg. deriving), no just the runtime-defined magic operators. I think it matters in practice.

Member

gasche commented May 15, 2015

But this example holds with all form of type-definition-directed code generation (eg. deriving), no just the runtime-defined magic operators. I think it matters in practice.

@lpw25

This comment has been minimized.

Show comment
Hide comment
@lpw25

lpw25 May 15, 2015

Contributor

But this example holds with all form of type-definition-directed code generation (eg. deriving), no just the runtime-defined magic operators.

I don't think it does, deriving would create two different operators for comparison with two different names -- they would not be selected based on the types of the constructors and so the dynamic semantics would not depend on the type selected for the constructors.

Contributor

lpw25 commented May 15, 2015

But this example holds with all form of type-definition-directed code generation (eg. deriving), no just the runtime-defined magic operators.

I don't think it does, deriving would create two different operators for comparison with two different names -- they would not be selected based on the types of the constructors and so the dynamic semantics would not depend on the type selected for the constructors.

@gasche

This comment has been minimized.

Show comment
Hide comment
@gasche

gasche May 15, 2015

Member

You're correct, I need modular implicits to exploit this feature without polymorphic comparison operators. In any case, I think this situation is fishy -- I just don't think that "only the names matter" is a valid semantic model of OCaml programs in practice.

Member

gasche commented May 15, 2015

You're correct, I need modular implicits to exploit this feature without polymorphic comparison operators. In any case, I think this situation is fishy -- I just don't think that "only the names matter" is a valid semantic model of OCaml programs in practice.

@yallop

This comment has been minimized.

Show comment
Hide comment
@yallop

yallop May 15, 2015

Member

Right: with modular implicits it's easy to come up with examples (both with and without constructor disambiguation) where adding annotations changes the dynamic semantics.

Member

yallop commented May 15, 2015

Right: with modular implicits it's easy to come up with examples (both with and without constructor disambiguation) where adding annotations changes the dynamic semantics.

@dbuenzli

This comment has been minimized.

Show comment
Hide comment
@dbuenzli

dbuenzli May 15, 2015

Contributor

Regarding the issue at hand. If Error is indeed too problematic we can still define the type using variants (the only drawback would be potentially harder to understand type errors for users).

Contributor

dbuenzli commented May 15, 2015

Regarding the issue at hand. If Error is indeed too problematic we can still define the type using variants (the only drawback would be potentially harder to understand type errors for users).

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky May 15, 2015

dbuenzli: I don't understand what you're proposing. The type is already defined as a variant. Or do you mean polymorphic variants here?

yminsky commented May 15, 2015

dbuenzli: I don't understand what you're proposing. The type is already defined as a variant. Or do you mean polymorphic variants here?

@toots

This comment has been minimized.

Show comment
Hide comment
@toots

toots May 15, 2015

Polymorphic variant would indeed be a good alternative I believe..

toots commented May 15, 2015

Polymorphic variant would indeed be a good alternative I believe..

@dbuenzli

This comment has been minimized.

Show comment
Hide comment
@dbuenzli

dbuenzli May 15, 2015

Contributor

Ugh yes, sorry polymorphic variant.

Contributor

dbuenzli commented May 15, 2015

Ugh yes, sorry polymorphic variant.

@bluddy

This comment has been minimized.

Show comment
Hide comment
@bluddy

bluddy May 15, 2015

What this makes me realize is the value of haskell's Either type. Left and Right are unlikely to pop up in your own code except for a few limited domains, which makes Either a pretty good generic choice. Perhaps we want to go in a similar direction, rather than moving to polymorphic variants and losing some type safety. I mean, a big part of this is forcing programmers to generate and handle both possible results, right?

The other thing this makes me realize is that we need an easy way to hide parts of modules in ocaml.

bluddy commented May 15, 2015

What this makes me realize is the value of haskell's Either type. Left and Right are unlikely to pop up in your own code except for a few limited domains, which makes Either a pretty good generic choice. Perhaps we want to go in a similar direction, rather than moving to polymorphic variants and losing some type safety. I mean, a big part of this is forcing programmers to generate and handle both possible results, right?

The other thing this makes me realize is that we need an easy way to hide parts of modules in ocaml.

@dbuenzli

This comment has been minimized.

Show comment
Hide comment
@dbuenzli

dbuenzli May 15, 2015

Contributor

Le vendredi, 15 mai 2015 à 20:29, Yotam Barnoy a écrit :

Left and Right are unlikely to pop up in your own code except for a few limited domains, which makes Either a pretty good generic choice.

That genericity leads to less readable code if your type is supposed to represent a possibly failing value computation which is what the result type is for.

rather than moving to polymorphic variants and losing some type safety
You don't loose any type safety by using polymorphic variants.

Contributor

dbuenzli commented May 15, 2015

Le vendredi, 15 mai 2015 à 20:29, Yotam Barnoy a écrit :

Left and Right are unlikely to pop up in your own code except for a few limited domains, which makes Either a pretty good generic choice.

That genericity leads to less readable code if your type is supposed to represent a possibly failing value computation which is what the result type is for.

rather than moving to polymorphic variants and losing some type safety
You don't loose any type safety by using polymorphic variants.

@Drup

This comment has been minimized.

Show comment
Hide comment
@Drup

Drup May 15, 2015

Contributor

You don't loose any type safety by using polymorphic variants.

It's not strictly speaking type safety, but it catches less thing since it tends to catch them at use instead of definition. In particular, The anti-typo factor is less important.

result is a rather small type (and there is always the correct type alias), so it's less of an issue, but believe me, it's notable in tyxml ....

Contributor

Drup commented May 15, 2015

You don't loose any type safety by using polymorphic variants.

It's not strictly speaking type safety, but it catches less thing since it tends to catch them at use instead of definition. In particular, The anti-typo factor is less important.

result is a rather small type (and there is always the correct type alias), so it's less of an issue, but believe me, it's notable in tyxml ....

@let-def

This comment has been minimized.

Show comment
Hide comment
@let-def

let-def May 15, 2015

Contributor

So you do loose, but you don't lose.

Contributor

let-def commented May 15, 2015

So you do loose, but you don't lose.

@bluddy

This comment has been minimized.

Show comment
Hide comment
@bluddy

bluddy May 15, 2015

On Fri, May 15, 2015 at 3:00 PM, Daniel Bünzli notifications@github.com
wrote:

Le vendredi, 15 mai 2015 à 20:29, Yotam Barnoy a écrit :

Left and Right are unlikely to pop up in your own code except for a few
limited domains, which makes Either a pretty good generic choice.

That genericity leads to less readable code if your type is supposed to
represent a possibly failing value computation which is what the result
type is for.

It's not less readable once the convention is absorbed. It's less explicit,
but you can say the same about Some and None being used to represent a
simple error state, though they do so just fine since the programmer is
aware of the convention.

bluddy commented May 15, 2015

On Fri, May 15, 2015 at 3:00 PM, Daniel Bünzli notifications@github.com
wrote:

Le vendredi, 15 mai 2015 à 20:29, Yotam Barnoy a écrit :

Left and Right are unlikely to pop up in your own code except for a few
limited domains, which makes Either a pretty good generic choice.

That genericity leads to less readable code if your type is supposed to
represent a possibly failing value computation which is what the result
type is for.

It's not less readable once the convention is absorbed. It's less explicit,
but you can say the same about Some and None being used to represent a
simple error state, though they do so just fine since the programmer is
aware of the convention.

@trefis

This comment has been minimized.

Show comment
Hide comment
@trefis

trefis May 15, 2015

Contributor

2015-05-15 15:13 GMT-04:00 Yotam Barnoy notifications@github.com:

On Fri, May 15, 2015 at 3:00 PM, Daniel Bünzli notifications@github.com
wrote:

Le vendredi, 15 mai 2015 à 20:29, Yotam Barnoy a écrit :

Left and Right are unlikely to pop up in your own code except for a few
limited domains, which makes Either a pretty good generic choice.

That genericity leads to less readable code if your type is supposed to
represent a possibly failing value computation which is what the result
type is for.

It's not less readable once the convention is absorbed. It's less explicit,
but you can say the same about Some and None being used to represent a
simple error state, though they do so just fine since the programmer is
aware of the convention.

Oh yes, None | Some of 'a is clearly as confusing as Blue of 'b | Red of 'a.
Thank god someone finally had the guts to say it!

Contributor

trefis commented May 15, 2015

2015-05-15 15:13 GMT-04:00 Yotam Barnoy notifications@github.com:

On Fri, May 15, 2015 at 3:00 PM, Daniel Bünzli notifications@github.com
wrote:

Le vendredi, 15 mai 2015 à 20:29, Yotam Barnoy a écrit :

Left and Right are unlikely to pop up in your own code except for a few
limited domains, which makes Either a pretty good generic choice.

That genericity leads to less readable code if your type is supposed to
represent a possibly failing value computation which is what the result
type is for.

It's not less readable once the convention is absorbed. It's less explicit,
but you can say the same about Some and None being used to represent a
simple error state, though they do so just fine since the programmer is
aware of the convention.

Oh yes, None | Some of 'a is clearly as confusing as Blue of 'b | Red of 'a.
Thank god someone finally had the guts to say it!

@dbuenzli

This comment has been minimized.

Show comment
Hide comment
@dbuenzli

dbuenzli May 15, 2015

Contributor

Le vendredi, 15 mai 2015 à 21:13, Yotam Barnoy a écrit :

It's not less readable once the convention is absorbed. It's less explicit,

What kind of reasoning is that. So now you need to maintain in your head a map Left -> Ok, Right -> Error. I call that both less explicit and less readable since a hintless convention has to be absorbed. And then you cannot even be sure that some other library writer will have another opinion about which way is right.

you can say the same about Some and None being used to represent a simple error state
Absolutely not, it's both explicit and readable without a mind map. Either there is Some value or there is None.

Contributor

dbuenzli commented May 15, 2015

Le vendredi, 15 mai 2015 à 21:13, Yotam Barnoy a écrit :

It's not less readable once the convention is absorbed. It's less explicit,

What kind of reasoning is that. So now you need to maintain in your head a map Left -> Ok, Right -> Error. I call that both less explicit and less readable since a hintless convention has to be absorbed. And then you cannot even be sure that some other library writer will have another opinion about which way is right.

you can say the same about Some and None being used to represent a simple error state
Absolutely not, it's both explicit and readable without a mind map. Either there is Some value or there is None.

@yminsky

This comment has been minimized.

Show comment
Hide comment
@yminsky

yminsky May 15, 2015

I think it's pretty clear that the type discipline around polymorphic variants is more complicated and less good at catching errors than that around ordinary variants. To me, that seems a sufficient argument for using ordinary variants for such a core concept as Ok | Error. The parallelism with option also argues for using the same mechanism in the type system.

yminsky commented May 15, 2015

I think it's pretty clear that the type discipline around polymorphic variants is more complicated and less good at catching errors than that around ordinary variants. To me, that seems a sufficient argument for using ordinary variants for such a core concept as Ok | Error. The parallelism with option also argues for using the same mechanism in the type system.

@bluddy

This comment has been minimized.

Show comment
Hide comment
@bluddy

bluddy May 15, 2015

What kind of reasoning is that. So now you need to maintain in your head a
map Left -> Ok, Right -> Error. I call that both less explicit and less
readable since a hintless convention has to be absorbed. And then you
cannot even be sure that some other library writer will have another
opinion about which way is right.

This is why it's an agreed-upon convention. In haskell, people use Either
and there's no problem with it whatsoever. You get used to the convention
and move on. And I say that though I'm not a big fan of haskell.

you can say the same about Some and None being used to represent a
simple error state
Absolutely not, it's both explicit and readable without a mind map. Either
there is Some value or there is None.

It's obviously a much easier map. All I'm pointing out is that it's not
explicit about there being an error. We're still mapping concepts here --
it's just a more obvious mapping.

bluddy commented May 15, 2015

What kind of reasoning is that. So now you need to maintain in your head a
map Left -> Ok, Right -> Error. I call that both less explicit and less
readable since a hintless convention has to be absorbed. And then you
cannot even be sure that some other library writer will have another
opinion about which way is right.

This is why it's an agreed-upon convention. In haskell, people use Either
and there's no problem with it whatsoever. You get used to the convention
and move on. And I say that though I'm not a big fan of haskell.

you can say the same about Some and None being used to represent a
simple error state
Absolutely not, it's both explicit and readable without a mind map. Either
there is Some value or there is None.

It's obviously a much easier map. All I'm pointing out is that it's not
explicit about there being an error. We're still mapping concepts here --
it's just a more obvious mapping.

@dbuenzli

This comment has been minimized.

Show comment
Hide comment
@dbuenzli

dbuenzli May 16, 2015

Contributor

Le vendredi, 15 mai 2015 à 21:59, Yaron Minsky a écrit :

To me, that seems a sufficient argument for using ordinary variants for such a core concept as Ok | Error. The parallelism with option also argues for using the same mechanism in the type system.

FWIW I would also be in favour of keeping an ordinary variant for this. Other than that, this issue should be a reminder that using warnings to resolve PL design issues or disputes is a bad idea, it ends up all of us having different expectation and live with a different programming language.

D

Contributor

dbuenzli commented May 16, 2015

Le vendredi, 15 mai 2015 à 21:59, Yaron Minsky a écrit :

To me, that seems a sufficient argument for using ordinary variants for such a core concept as Ok | Error. The parallelism with option also argues for using the same mechanism in the type system.

FWIW I would also be in favour of keeping an ordinary variant for this. Other than that, this issue should be a reminder that using warnings to resolve PL design issues or disputes is a bad idea, it ends up all of us having different expectation and live with a different programming language.

D

@talex5

This comment has been minimized.

Show comment
Hide comment
@talex5

talex5 May 16, 2015

It would be very nice if I could put true: target(4.02) in my _tags file and get the default set of warnings and pervasives for that version. Then new default warnings could be added and pervasive values added (or removed) without existing code generating warnings or errors.

talex5 commented May 16, 2015

It would be very nice if I could put true: target(4.02) in my _tags file and get the default set of warnings and pervasives for that version. Then new default warnings could be added and pervasive values added (or removed) without existing code generating warnings or errors.

@xavierleroy

This comment has been minimized.

Show comment
Hide comment
@xavierleroy

xavierleroy May 16, 2015

Contributor

Adding two cents to an already long discussion:

  • The warning is clearly wrong in the cases mentioned and needs to be improved.
  • As Damien mentioned, not all warnings are appropriate to all programming styles. I, for one, think that shadowing (of constructors or other definitions) is more often useful than wrong, and would never activate the warning under consideration here.
  • I also argue in favor of a regular variant datatype instead of an extensible variant here. Besides the sometimes surprising typings inferred for extensible variants, there is also an efficiency concern: extensible variants take up more memory space and take longer to pattern-match. In many uses this is negligible. However, if one programs in monadic style, values of type "error" are constructed and pattern-matched very often, so the efficiency of regular variants is to be preferred.
Contributor

xavierleroy commented May 16, 2015

Adding two cents to an already long discussion:

  • The warning is clearly wrong in the cases mentioned and needs to be improved.
  • As Damien mentioned, not all warnings are appropriate to all programming styles. I, for one, think that shadowing (of constructors or other definitions) is more often useful than wrong, and would never activate the warning under consideration here.
  • I also argue in favor of a regular variant datatype instead of an extensible variant here. Besides the sometimes surprising typings inferred for extensible variants, there is also an efficiency concern: extensible variants take up more memory space and take longer to pattern-match. In many uses this is negligible. However, if one programs in monadic style, values of type "error" are constructed and pattern-matched very often, so the efficiency of regular variants is to be preferred.
@gasche

This comment has been minimized.

Show comment
Hide comment
@gasche

gasche May 16, 2015

Member

It would be very nice if I could put true: target(4.02) in my _tags file and get the default set of warnings and pervasives for that version.

Patches welcome, that should be easy to implement. My worry would be user expecting this flag to also enable/disable features of the language (some people have asked for this in the past, and I think it's unreasonable in terms of maintenance burden); maybe just a tag such as warnings_of_verson(4.02), or even just supporting warn(4.02) would be better.

Member

gasche commented May 16, 2015

It would be very nice if I could put true: target(4.02) in my _tags file and get the default set of warnings and pervasives for that version.

Patches welcome, that should be easy to implement. My worry would be user expecting this flag to also enable/disable features of the language (some people have asked for this in the past, and I think it's unreasonable in terms of maintenance burden); maybe just a tag such as warnings_of_verson(4.02), or even just supporting warn(4.02) would be better.

@gasche

This comment has been minimized.

Show comment
Hide comment
@gasche

gasche May 16, 2015

Member

I created a PR to keep track of this feature request: PR#6873.

Member

gasche commented May 16, 2015

I created a PR to keep track of this feature request: PR#6873.

@gasche

This comment has been minimized.

Show comment
Hide comment
@gasche

gasche May 17, 2015

Member

The disambiguation bug was just fixed by Jacques Garrigue. @toots , could you recompile and list the remaining Warnings 41?

Member

gasche commented May 17, 2015

The disambiguation bug was just fixed by Jacques Garrigue. @toots , could you recompile and list the remaining Warnings 41?

@toots

This comment has been minimized.

Show comment
Hide comment
@toots

toots May 18, 2015

Thanks for the fix @gasche! I'm only seeing the warning in a couple of cases now. Here's one:

OCAMLOPT -c tools/http.ml
File "tools/http.ml", line 17, characters 7-12:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.

Code:

type error = Socket | Response | UrlDecoding
exception Error of error

let string_of_error e =
  match e with
    | Socket -> "Http: error while communicating to socket"
    | Response -> "Http: invalid answer to request"
    | UrlDecoding -> "Http: URL decoding failed"

(** Error translator *)
let error_translator e =
   match e with
     | Error e -> Some (string_of_error e)
     | _ -> None

Warning occurs in error_translator

toots commented May 18, 2015

Thanks for the fix @gasche! I'm only seeing the warning in a couple of cases now. Here's one:

OCAMLOPT -c tools/http.ml
File "tools/http.ml", line 17, characters 7-12:
Warning 41: Error belongs to several types: exn result
The first one was selected. Please disambiguate if this is wrong.

Code:

type error = Socket | Response | UrlDecoding
exception Error of error

let string_of_error e =
  match e with
    | Socket -> "Http: error while communicating to socket"
    | Response -> "Http: invalid answer to request"
    | UrlDecoding -> "Http: URL decoding failed"

(** Error translator *)
let error_translator e =
   match e with
     | Error e -> Some (string_of_error e)
     | _ -> None

Warning occurs in error_translator

@gasche

This comment has been minimized.

Show comment
Hide comment
@gasche

gasche May 18, 2015

Member

Would writing let error_translator (e : exn) = or match (e:exn) with be an acceptable workaround for you?

(If that's not too much work, listing the location of the other cases may help in documenting the kind of code patterns that users would have to change, under the assumption that the type-declaration remains as is for the 4.03 release.)

Member

gasche commented May 18, 2015

Would writing let error_translator (e : exn) = or match (e:exn) with be an acceptable workaround for you?

(If that's not too much work, listing the location of the other cases may help in documenting the kind of code patterns that users would have to change, under the assumption that the type-declaration remains as is for the 4.03 release.)

@toots

This comment has been minimized.

Show comment
Hide comment
@toots

toots May 18, 2015

Yeah, I can do that. The other case is the following:

exception Error of (term*string)

let invalid t =
  match t.term with
    | Int _ | Bool _ | Float _ | String _ -> false
    | _ -> true

let generic_error t : exn =
  if invalid t then
    match t.term with
      | Var _ -> Error (t,"variables are forbidden in encoding formats")
      | _ -> Error (t,"complex expressions are forbidden in encoding formats")
  else
    Error (t,"unknown parameter name or invalid parameter value")

toots commented May 18, 2015

Yeah, I can do that. The other case is the following:

exception Error of (term*string)

let invalid t =
  match t.term with
    | Int _ | Bool _ | Float _ | String _ -> false
    | _ -> true

let generic_error t : exn =
  if invalid t then
    match t.term with
      | Var _ -> Error (t,"variables are forbidden in encoding formats")
      | _ -> Error (t,"complex expressions are forbidden in encoding formats")
  else
    Error (t,"unknown parameter name or invalid parameter value")
@damiendoligez

This comment has been minimized.

Show comment
Hide comment
@damiendoligez

damiendoligez May 18, 2015

Member

let generic_error t : exn =

To be clear, you only get the warning if the : exn is not there, right?

Member

damiendoligez commented May 18, 2015

let generic_error t : exn =

To be clear, you only get the warning if the : exn is not there, right?

@toots

This comment has been minimized.

Show comment
Hide comment
@toots

toots commented May 18, 2015

Yup.

stedolan added a commit to stedolan/ocaml that referenced this pull request Aug 3, 2017

Merge pull request #147 from ocamllabs/yuasa
Switching to Yuasa (deletion) barrier from Dijkstra (insertion) barrier
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment