Arc Forumnew | comments | leaders | submitlogin
Suggest PG: Settable Function objects
7 points by almkglor 5909 days ago | 14 comments
Currently, function objects can also be used to encapsulate environments so that they can be accessed in a table-like manner:

  (= foo
    (with (var1 1 var2 2 var3 3 var4 4)
      (fn (v)
        (case v
          var1 var1
          var2 var2
          var3 var3
          var4 var4))))
   foo!var1
However, the problem is that this abstraction breaks when setting:

  (= foo!var1 42)
Of course, we could just use tables, but sometimes we don't want to just store data - sometimes we want to propagate the change in the data. For example, kennytilton's Cells would want to know if and when the user changes some cell (currently he just uses accessor form - i.e. (field obj) instead of (obj 'field) - and defines a bunch of defset - but this means that using obj!field will be a "back door".). Also, we might want to encapsulate an environment instead of a table, so that, say, a bunch of other functions (accessible by obj!field) would have different effects based on a non-global (also accessible via obj!field).

What I'm proposing is overloading the (annotate <with-something> <something-else>). As it is, annotated functions will still operate as functions, regardless of annotation. What I'm proposing is to overload the second argument - if it's a function, this function is the "setterfunction" for the tagged function.

For example:

  (= foo
    (with (var1 1 var2 2 var3 3 var4 4)
      (annotate
        ; value first, so that variable-arity functions can be done.
        (fn (newv v)
          (case v
            ; this amount of boilerplate will, of course, be
            ; macroized somewhere.
            var1 (= var1 newv)
            var2 (= var2 newv)
            var3 (= var3 newv)
            var4 (= var4 newv)))
        (fn (v)
         (case v
           var1 var1
           var2 var2
           var3 var3
           var4 var4)))))
then:

   (= foo!var1 42)
   => 42
   foo!var1
   => 42
This will allow us to encapsulate away cases where a data structure will want to perform some internal calculations. For example, we could do something like this:

  (= weird-stuff
     (let (input1 input2 output output-valid)
       (annotate
         (fn (newv v)
           (case v
             input1 (= output-valid nil input1 newv)
             input2 (= output-valid nil input2 newv)))
         (fn (v)
           (case v
             input1 input1
             input2 input2
             output (if output-valid output
                             (= output-valid t
                                output (some-really-complex-calculation input1 input2))))))))


2 points by nlavine 5909 days ago | link

Good idea! I have one question: can we generalize this?

We already have objects like tables which act one way in functional position and another way in argument position. Can we say that you're suggesting adding a third way = setter target position?

If so, can we extend annotate in a general manner to let you define objects which have all three aspects? (And in the future, maybe, we could let people define their own, but I'm not sure yet how this would work, or what else exactly you would use it for, so we should wait on that.) Here's your example, modified a bit:

  (= foo
      (with (var1 1 var2 2 var3 3 var4 4)
        (annotate
          (fn (v)                 ; functional position aspect
           (case v
             'var1 var1
             'var2 var2
             'var3 var3
             'var4 var4))
          (fn (newv v)            ; setter target aspect
            (case v
              'var1 (= var1 newv) ; (I think these would have to be symbols)
              'var2 (= var2 newv)
              'var3 (= var3 newv)
              'var4 (= var4 newv))
          (fn ()                  ; object aspect
            (list var1 var2 var3 var4)))))

-----

1 point by almkglor 5908 days ago | link

Hmm. Maybe a better generalization is to take advantage of this code in ac.scm:

   (define (ar-tagged? x)
     (and (vector? x) (eq? (vector-ref x 0) 'tagged)))
annotate creates a vector with 3 entries:

  (annotate 'foo (fn () nil))
  => #3(tagged,foo,<procedure>)
Let's then create a basic function which retains the type in the second entry of the vector, but adds a fourth entry containing a hash.

  (attach '= (fn (v) (settercode v))
    (fn () (readercode)))
  => #4(tagged,fn,<procedure>,#hash((= . <procedure>)))
The benefit here is we could add other aspects of the object - for example, getting a length, or determining the keys so we can traverse the object.

Hmm. Might actually implement this. Am planning a persistent table, such that assignments to persistent-table!key will automatically update a file on the server (which, incidentally, can also encapsulate a database instead)

-----

1 point by almkglor 5909 days ago | link

minor nitpick: 'case already has implicit (quote ...) on each of the objects in the case position.

-----

1 point by nlavine 5908 days ago | link

Oh, right. Good call. (feels abashed)

-----

2 points by cchooper 5909 days ago | link

I may be missing the point, but is there any reason why you can't do this:

  (= setters (table))

  (with (var1 1 var2 2 var3 3 var4 4)
    (= foo (fn (v) (case v
                     var1 var1
                     var2 var2
                     var3 var3
                     var4 var4)))
    (= (setters foo) (fn (v val) (case v 
                                   var1 (= var1 val)
                                   var2 (= var2 val)
                                   var3 (= var3 val)
                                   var4 (= var4 val)))))


  (setters.foo 'var1 5)

  foo!var1
  => 5
? Obviously quite 'macroable' if required.

-----

3 points by almkglor 5909 days ago | link

garbage collection.

When all references to foo are discarded, 'setter retains a reference to foo, preventing it from being discarded.

-----

2 points by cchooper 5909 days ago | link

Good point. How about this?

  (= settersym (uniq))
  
  (= foo
    (with (var1 1 var2 2 var3 3 var4 4)
      (fn (v)
        (if (is v settersym) (fn (v val) (blah blah setter code)
            (case v
              var1 var1
              var2 var2
              var3 var3
              var4 var4)))))

  (mac set-value (f v val) `((,f ,settersym) ,v ,val))

  (set-value foo 'val1 5)
Attaching data to functions is something I've wanted to do for a long time, so I support the general idea, but I enjoy the challenge of doing it with macros :)

-----

4 points by almkglor 5909 days ago | link

This gets exceedingly complicated if we want to implement, say, 2-d matrices:

  (= matrix
    (let matdata '((1 0)
                   (0 1))
      (fn (v (o j nil)) ; ugly hack
          (if (is v settersym) (fn (val i j) (= ((matdata j) i) val))
              ((matdata j) v)))))
> Attaching data to functions is something I've wanted to do for a long time, so I support the general idea, but I enjoy the challenge of doing it with macros :)

Yes, but then it becomes non-standard. That's why I said "Request PG", hopefully it should be standardized into = forms.

-----

1 point by jivestgarden 5908 days ago | link

It is actually not complicated at all if one just defines a function (let us call it matrix) that returns settable functions. Then one could write code like this

  (let m (matrix '((1 0) (0 1)))
     (= (m 0 1) 42))
Similar things could (and in my opinion: should) also be done for each, len, and other basic functions and macros to enable iterations through, and manipulation of, stateful functions.

-----

1 point by almkglor 5908 days ago | link

Someone still has to write the function 'matrix, and it still has to look like that.

-----

2 points by jivestgarden 5907 days ago | link

It´s not so difficult as it looks: is is just a function that returns a function that returns a function. If you are used to object orientation, think of matrix as instansiation, m as an object, and the returned setter as a method. With the arc abbreviations, you could also write (m.settersym val 0 0) rather than having to modify =, but personally I would like to have some setter macro to make the syntax more natural (By the way: this is exactly how you would do a matrix implmentation in C++, only with templates in stead of macros)

-----

2 points by almkglor 5907 days ago | link

I am concerned about how it looks, which is my main objection to this.

-----

1 point by nex3 5907 days ago | link

You could always define a macro to abstract out the nastiness...

-----

1 point by almkglor 5907 days ago | link

^^ Again, somebody has to write the nastiness ^^. The main difficulty really is the fact that you have a polymorphic function that dispatches based on number of parameters as well as their contents.

Although I suppose you could actually use --warning-blatant-self-proclamation-- my p-m: macro:

  (p-m:def matrix
    (,(s (is s settersym)))
      writerfunction
    (i j)
      (readerfunction i j)
    x (err "argument error"))

-----