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

Allow "functional record update" for objects #6314

Closed
vicuna opened this issue Feb 2, 2014 · 9 comments
Closed

Allow "functional record update" for objects #6314

vicuna opened this issue Feb 2, 2014 · 9 comments

Comments

@vicuna
Copy link

vicuna commented Feb 2, 2014

Original bug ID: 6314
Reporter: tom.j.ridge
Assigned to: @garrigue
Status: assigned (set by tom.j.ridge on 2014-02-03T13:54:45Z)
Resolution: open
Priority: normal
Severity: feature
Category: language features
Monitored by: @gasche @hcarty @alainfrisch

Bug description

With records, one can functionally update a field e.g. as

{ r with some_field=new_value }

And new_value may, of course, be a function.

The request is for similar functionality for objects.

@vicuna
Copy link
Author

vicuna commented Feb 2, 2014

Comment author: jpdeplaix

I agree, this would be useful.

@vicuna
Copy link
Author

vicuna commented Feb 3, 2014

Comment author: @garrigue

What you are asking for is not completely clear.

The simple one (and closest to functional update) would be to allow just to override methods externally.
I.e., you just build a new immediate object forwarding most calls to the original object, and redefining some methods (but not visible from the original ones). Actually this is just a kind of syntactic sugar, but requiring to know the type of the object.

Another meaning would be to copy the object, changing the method table.
Doable too, but rather different, as in this case the state is not shared.
Moreover this require that methods have exact types: this would be unsound if methods were allowed to have subtypes in subclasses (as per the Abadi-Cardelli book).

Finally you might want to update the method table of the current object. But in that case this is destructive update, not functional, and this would break completely the semantics.

Also, for soundness reasons, in none of this case you would be allowed to access value fields from newly defined methods (they do not appear in the object interface).

@vicuna
Copy link
Author

vicuna commented Feb 3, 2014

Comment author: tom.j.ridge

Without knowing much about objects, I believe I am asking for

"The simple one (and closest to functional update) would be to allow just to override methods externally."

But, the scenario I am in means that I don't know exactly which methods are available on the object (but I know one method that is - the method I want to override). This means that I can't just make a new object (with methods delegating to the old object except for the "updated" method).

I think this makes semantic sense, but I'm not sure.

@vicuna
Copy link
Author

vicuna commented Feb 3, 2014

Comment author: tom.j.ridge

Also, it would be super-useful if there was support for adding in new methods e.g.

{{ myobj with some_new_method=some_fun }}

if this makes sense.

@github-actions
Copy link

This issue has been open one year with no activity. Consequently, it is being marked with the "stale" label. What this means is that the issue will be automatically closed in 30 days unless more comments are added or the "stale" label is removed. Comments that provide new information on the issue are especially welcome: is it still reproducible? did it appear in other contexts? how critical is it? etc.

@github-actions github-actions bot added the Stale label May 13, 2020
@gasche gasche removed the Stale label May 13, 2020
@gasche
Copy link
Member

gasche commented May 13, 2020

Yes, the first semantics proposed by @garrigue seems like the way to go, and it sounds not too hard to implement. We haven't spent much effort lately improving the object system (which makes sense given how little it is used by most OCaml projects), but the feature still makes sense and we can easily see how it would be useful for some use-cases (both to override or to extend).

(This goes back to a more general discussion about whether we should have polymorphic records, or make objects easier to use as polymorphic records.)

@garrigue
Copy link
Contributor

I suppose that this means having polymorphic operations of the form:

update_m : (self:(< m : 'a; .. > as 'obj) -> 'a) -> 'obj -> 'obj

that actually update the virtual table.
With the current implementation, this seems relatively easy.
Still, this would not be as efficient as one could hope: one needs to replace all other methods by closures calling the same method on the original object.
The alternative semantics of making it behave like inheritance seems unsafe, at it would allow to change the behaviour of an internal method on a running object, with has security implications.

Adding a method would be harder, and cannot be done polymorphically (i.e. that would actually be type-directed syntactic sugar).

@garrigue
Copy link
Contributor

Just for fun here is an implementation of functional update.

diff --git a/stdlib/camlinternalOO.ml b/stdlib/camlinternalOO.ml
index 0188c148cd..a60d385c61 100644
--- a/stdlib/camlinternalOO.ml
+++ b/stdlib/camlinternalOO.ml
@@ -455,6 +455,33 @@ let lookup_tables root keys =
   | Empty ->
     build_path (Array.length keys - 1) keys root
 
+(**** method update ****)
+
+let redirect obj n = ret (fun _self -> sendself obj n)
+
+let copy_table (tbl : closure array) (obj : obj) (n : tag) (m : closure) =
+  let len : int = Obj.magic (Array.unsafe_get tbl 0) in
+  let tbl' = Array.make (len*2+2) (Obj.magic len) in
+  Array.unsafe_set tbl' 1 (Array.unsafe_get tbl 1);
+  for i = 1 to len do
+    let ofs = 2 * i in
+    let tag = Array.unsafe_get tbl (ofs+1) in
+    Array.unsafe_set tbl' (ofs+1) tag;
+    Array.unsafe_set tbl' ofs
+      (if Obj.repr tag == Obj.repr n then m else redirect obj ofs);
+  done;
+  tbl'
+
+let update_method (tag : [> ]) (obj : < .. > as 'a) (m : 'a -> Obj.t) : 'a =
+  if not (Obj.is_int (Obj.repr tag)) then invalid_arg "Oo.update_method";
+  let tag : tag = Obj.magic tag in
+  let obj = Obj.repr obj in
+  let mets : closure array = Obj.obj (Obj.field obj 0) in
+  let mets' = copy_table mets (Obj.obj obj) tag (ret (Obj.magic m)) in
+  let obj' = Obj.new_block Obj.object_tag 2 in
+  Obj.set_field obj' 0 (Obj.repr mets');
+  set_id (Obj.magic obj')
+
 (**** builtin methods ****)
 
 let get_const x = ret (fun _obj -> x)
diff --git a/stdlib/camlinternalOO.mli b/stdlib/camlinternalOO.mli
index b6ffc70d7f..d407bd215a 100644
--- a/stdlib/camlinternalOO.mli
+++ b/stdlib/camlinternalOO.mli
@@ -151,3 +151,12 @@ type stats =
     methods : int;
     inst_vars : int }
 val stats : unit -> stats
+
+(** {1 Method update} *)
+
+val update_method : [> ] -> (< .. > as 'a) -> ('a -> Obj.t) -> 'a
+
+(*
+let update_m (obj : < m : 'a ; .. >) (m : 'a) =
+  CamlinternalOO.update_method `m obj (fun _ -> Obj.repr m);;
+*)

@github-actions
Copy link

This issue has been open one year with no activity. Consequently, it is being marked with the "stale" label. What this means is that the issue will be automatically closed in 30 days unless more comments are added or the "stale" label is removed. Comments that provide new information on the issue are especially welcome: is it still reproducible? did it appear in other contexts? how critical is it? etc.

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

3 participants