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

object-like notation for module functions #6012

Closed
vicuna opened this issue May 10, 2013 · 12 comments
Closed

object-like notation for module functions #6012

vicuna opened this issue May 10, 2013 · 12 comments

Comments

@vicuna
Copy link

vicuna commented May 10, 2013

Original bug ID: 6012
Reporter: @lefessan
Assigned to: @gasche
Status: resolved (set by @gasche on 2013-05-17T16:36:29Z)
Resolution: suspended
Priority: normal
Severity: feature
Category: ~DO NOT USE (was: OCaml general)

Bug description

  I have been recently translating a lot of code from C++ to OCaml, but without using OCaml objects (for my purpose, they trigger errors that are hardly understandable by beginners...). I usually end up translating a lot of calls of the form:

self.meth(args) 

to

ClassModule.meth self args

  While it is easy to do it using replace-regexp, I was wondering if it would not be worth adding a simple syntax for that, that could also be used in a more general context to reduce the size of code to look like object-oriented code without using objects. The idea is that a common idiom in OCaml is to put all the functions working on a specific type in a module, and thus, if we can associate syntactically an identifier with such a module, we can use some object-oriented notation to access all these functions for this identifier (here, I will use "->" for that).

In the above example, when "self" would be introduced in the context, it would be associated with ClassModule (like a type annotation):

let f (self -> ClassModule) =
   ...
   self->meth1 args;
   self->meth2 args;
   self->meth3 args;
   ...

With arrays:

let list_plus_length ( l -> List ) =
  let len = l->length in
  l-->map (fun i -> i + len)  (* --> put "l" as the second argument of List.map *)

Note that it is not purely syntactic, it must be done with the knowledge of scopes, i.e. during typing. An extension would be to associate modules with types, so that type knowledge would trigger the correct use of functions :

type string -> String

let len (s : string) = s->length

Another advantage of such a notation would be that editors could use their knowledge of the module associated with the identifier to propose completions within that module.

Anybody else interested in having such a notation ?

@vicuna
Copy link
Author

vicuna commented May 10, 2013

Comment author: @dbuenzli

I would be against such an addition. I think there are already enough notations in OCaml, the language is already sufficiently complex. Moreover it would encourage an imperative style of programming.

I think you should consider using the M.() notation for that need.

ClassModule.(self meth1 args; self meth2 args; self meth3 args)

Now maybe the M.() notation should just fix the precedent it introduced that its parens cannot be replaced by begin end (which if I'm not mistaken was true in the expression language before). So that you could write

ClassModule.begin
self meth1 args;
self meth2 args;
self meth3 args;
end

Though it should be noted that M.() should not be abused, as it has its own problems see #5980.

@vicuna
Copy link
Author

vicuna commented May 10, 2013

Comment author: @lefessan

I don't see what you mean by "encouraging imperative style", the notation is for calling functions, as shown by my (functional) example on lists, so it does encourage either style of programming.

I think there are two caveats with using M.() notation:

(1) it opens the whole M module, so that there can be collisions with identifiers used in the arguments. With my notation, only the method is prefixed with the module name, so there can be no mistake.

(2) it is not attached to a given identifier. If I have

obj1->meth1 args1;
obj2->meth2 args2;
obj1->meth1 args1;
obj2->meth2 args2;

where obj1 and obj2 use two different modules, my notation is still shorter and unambiguous, while other notations have either to explicit the modules everywhere, or hope there is no collisions between them.

@vicuna
Copy link
Author

vicuna commented May 10, 2013

Comment author: @gasche

I would tend to agree with Daniel that this is maybe not a good use of the (little) free syntactic space with have left. The fact that it overlaps in purpose with the object or record projection on one side, and the local opening on the other is suspicious.

On top of Daniel's suggestion to use local module opening for that, you could make use of a reverse application operator as well:

let open Module in
self|>meth1 foo;
self|>meth2 foo;
self|>meth3 bar;

Daniel : parenthesis (vs. begin/end) have had a special status in the annotation/coercion syntaxes as well: (foo : t), (foo :> t) and (foo : t :> t). I'm not for or against "Foo.begin ... end" (it's fine if it doesn't add ambiguities in the grammar), but I would prefer the more explicit "let open Foo in" for non-one-liners anyway.

@vicuna
Copy link
Author

vicuna commented May 10, 2013

Comment author: @dbuenzli

My point about encouraging imperative style was that this idea seems to stem from imperative code you are disatisfied with. If your idea is to add new notation for calling function I really think it's not only pointless, but also harmful.

It' just going to increase the cognitive burden on code readers and lead to generalized confusion (funny you mention beginners in your first message). This will just be another opportunity to dilute OCaml's programming style and make it a harder language to understand in general.

The difference between --> and -> maybe easy to miss and the whole idea seems rather ad-hoc from a syntactic perspective. What do you for fold_left ?

l--->fold_left f acc

and for fold_right are you going to permit that :

l-->fold_right f acc

Now think about the difference between (if you don't have both things side-by-side)

l0--->fold_left2 f acc l1
fold_left f acc l0 l1

and

l0---->fold_left2 f acc l1
fold_left f acc l1 l0

If you want to increase the power of the language for obfuscated OCaml contests, that could be a good addition.

@vicuna
Copy link
Author

vicuna commented May 10, 2013

Comment author: @dbuenzli

Side note: your original problem may not be unrelated to the fact that usually when you do bindings to a library written in another language you end up programming with the idioms and style of that other language.

Making thin bindings is (mostly) the easy part. Building on top of these an (as light as possible) abstraction that exposes that functionality naturally in your language is where the challenge resides.

@vicuna
Copy link
Author

vicuna commented May 10, 2013

Comment author: @bobzhang

I am for such proposal, 'let open M in' is too dangerous for real world usage, we can reuse '|>' syntax for functional style.

let (m:M) =
m |> map |> fold

Here no 'let open'

@vicuna
Copy link
Author

vicuna commented May 10, 2013

Comment author: @lefessan

I don't think having an additional notation would be a problem for beginners, when it is easy to understand. It is easy to explain how this new "->" notation works, which is not the case when using objects and their error messages...

The "--->" notation was just to generalize "->" to arbitrary position. Personally, I only care about "->" that adds the "object" as the first argument. Still, I don't think "-->" is so ugly, and the existence of a shorter notation does not force programmers to use it for cases like fold_left2 !

@vicuna
Copy link
Author

vicuna commented May 13, 2013

Comment author: @garrigue

First question: is your problems with objects only errors, or are they really a bad fit.
(I ask this because C++ code sometimes only uses classes as modules...)
But if we are talking about object-oriented code, what is the problem with error messages?
Wouldn't it be more productive to improve error messages than to introduce yet a new syntax for an encoding of objects into modules?
In particular, since the times of O'Labl, I think that the main problem with objects and beginners is allowing calling methods on objects of unknown types, since this creates complex inferred types, and may easily lead to clashes between object types. In O'Labl there was a warning when you did that.
Outside of this case (i.e. inferred object types), do you really have cases of hard to understand error messages?

As for the need to use scope information obtained during typing, I think this is mostly about making it more robust (have an error if the definition of l has been shadowed). Otherwise this can be completely syntactic, and you can already do that with Camlp4. The error message in case of shadowing will just be less explicit. But in fact making it completely syntactic is nicer in some situations:

let f ( l -> List) =
let l = l --> map succ in
l --> filter (fun x -> x > 2)

Just by keeping the same name you can go on using the syntax in functional code.
(Not that I particularly like it...)

@vicuna
Copy link
Author

vicuna commented May 13, 2013

Comment author: @lefessan

@garrigue:
1/ yes, part of my main problem with objects is type-inference on unknown object types. I have not checked it again recently, but when I used objects an application with 3.12, I could generate error messages that took me a lot of time to understand and to fix. I know that the fix would be "always annotate with types", but how do you enforce that on beginners ? Maybe the warning from olabl should be brought back, but anyway, it would not be by default (for backward compatibility of warn-errors), and so, would not be of any help for beginners.

My other problem with objects is that I think late binding of virtual methods makes code unreadable, you have to always search among all the classes in a hierarchy to understand which code is really executed. It is nice for prototyping fast, but makes the code harder to maintain on the long term. So, when I can, I just avoid it.

2/ Indeed, it can be done with camlp4, and you have a point that it makes sense to have a purely syntactic behavior. Anyway, my initial question was "does anybody else feel the need for it ?", because if many would have replied yes, it would have been interesting to have it in the standard syntax instead of just an extension.

@vicuna
Copy link
Author

vicuna commented May 13, 2013

Comment author: @garrigue

Maybe the warning from olabl should be brought back, but anyway, it would not be by default (for backward compatibility of warn-errors), and so, would not be of any help for beginners.

I think there is an agreement that blanket -warn-error A in published code is a very bad idea.
This should not stop us from adding new warning if some style appears to be counter-productive.

@vicuna
Copy link
Author

vicuna commented May 13, 2013

Comment author: @alainfrisch

My personal opinion is that the proposed feature does not bring enough to the language to justify the introduction of a new concept and syntax.

@vicuna
Copy link
Author

vicuna commented May 17, 2013

Comment author: @gasche

Being in a bug-triaging mood I'm tempted to mark this bug as something else than "new". As it looks there is more opposition to the feature than support, I'm preparing for it to be thrown in the dustbin of history by marking it "suspended", but that's more because all the other "status" choice were less convincing than because I think there is no more discussion to be had on the issue -- I'm hoping it will die down by itself, but strong arguments in either direction are welcome.

(On a strictly personal level I don't really like the feature, but have no strong argument either against -- it's just that I think we can find better use for whatever notations could be chosen. I think the "purely syntactic" version would be better: what we are discussing is a "notation" in the spirit of Coq's Notation mechanism or Agda's more recent "syntax" binding feature. A classification which is maybe not a compliment.)

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

No branches or pull requests

2 participants