Loading ...
Sorry, an error occurred while loading the content.
 

Re: simple object model (was: [self-interest] selfing Self)

Expand Messages
  • Jecel Assumpcao Jr
    ... That is a great idea, which is why all these languages use it. I think Rebol also has something like this. In fact, I think that what we now have in Self
    Message 1 of 7 , Apr 1, 2001
      On Friday 30 March 2001 16:39, Steve Dekorte wrote:
      > How about a hash table? NewtonScript, JavaScript, and Lua use simple
      > hash tables for "objects".

      That is a great idea, which is why all these languages use it. I think
      Rebol also has something like this.

      In fact, I think that what we now have in Self is equivalent to this -
      just add a set of hash table-like objects to mirrors. The problem I am
      trying to attack is the existence of three levels:

      1) base level: objects look like menus, morphs, strings, etc...

      2) reflective level: objects look like hash tables (or collections of
      slots)

      3) implementation level: you have the actual memory format of objects
      with tricks such as "maps" to help make cloned objects smaller

      Since hash tables are a very abstract data type, saying that objects
      are built from them still gives me no clue about the implementation
      level.

      One complication in Self 4 is that levels 1 and 2 are in Self, but 3 is
      in C++ (which requires different skills and tools). Self/R is meant to
      simplify things by having level 3 in Self as well.

      What I am investigating here is the possibility of bringing levels 2
      and 3 closer together (ideally level 2 would be a slightly "sugar
      coated" version of level 3).

      -- Jecel
    • Jecel Assumpcao Jr
      ... Er... that should be hash table-like methods to mirrors . -- Jecel
      Message 2 of 7 , Apr 1, 2001
        On Sunday 01 April 2001 18:07, Jecel wrote:
        > In fact, I think that what we now have in Self is equivalent to this
        > - just add a set of hash table-like objects to mirrors.

        Er... that should be "hash table-like methods to mirrors".

        -- Jecel
      • Steve Dekorte
        ... I m not sure what you mean here. If you mean: 1) method invocation 2) reflection 3) object model Then scripting languages like python and lua unite the
        Message 3 of 7 , Apr 2, 2001
          Jecel Assumpcao Jr wrote:
          > 1) base level: objects look like menus, morphs, strings, etc...
          >
          > 2) reflective level: objects look like hash tables (or collections of
          > slots)
          >
          > 3) implementation level: you have the actual memory format of objects
          > with tricks such as "maps" to help make cloned objects smaller

          > What I am investigating here is the possibility of bringing levels 2
          > and 3 closer together (ideally level 2 would be a slightly "sugar
          > coated" version of level 3).

          I'm not sure what you mean here.
          If you mean:
          1) method invocation
          2) reflection
          3) object model

          Then scripting languages like python and lua unite the first 2:

          1) table.slotName() = call the returned function

          2) a = table.slotName (get slot value, be it a function or data)
          table.slotName = b (set slot value to data or function)

          And Lua brings 3 (the implementation of the object model) up to the
          level of 1&2 (the language itself) by providing "hooks" that let you
          define what happens when a table lookup fails:

          function index(table, slotName)
          local parent = rawgettable(table, "_parent")
          if parent then return parent[slotName] end
          error("object "..tostring(table).." has no slot
          named:"..tostring(slotName))
          end

          You can likewise define the clone method, so you're free to do things
          like NewtonScript style slot creation on write if you like, to conserve
          memory and instantiating time at the expense of slot lookup time.

          It doesn't get much more "light-weight" than this.

          Steve
        • Uladzimir Liashkevich
          ... Yes. It is actually what you should do in this case. It looks not so easy, so I don t like that approach. ... In my opinion, mirrors should not be used in
          Message 4 of 7 , Apr 5, 2001
            > > When I read about Self, I felt the same as in the case of Smalltalk.
            > > Later I discovered the absense of some needful possibilities. One of
            > > them is constructing objects on the fly. One cannot write something
            > > like this: (| x:Y: = (| :aX. :aY | ^(| x <- aX. y <- aY |) ) |).
            >
            > While this doesn't work because slot initializers ("aX" and "aY" in
            > this case) are always evaluated in the context of the lobby object, you
            > can get the same results with:
            >
            > (| x:Y: = (| :aX. :aY. new |
            > new: (| x. y. parent* = traits clonable |) copy.
            > new x: aX.
            > new y: aY.
            > new)
            > |)

            Yes. It is actually what you should do in this case. It looks not so
            easy, so I don't like that approach.

            >
            > The reason I used 'copy' above is that I am supposing that you don't
            > want successive calls to this method to always return the exact same
            > object, but just the same kind of object. If that is the case but you
            > don't want the new object to be clonable, we could use the '_Clone'
            > primitive instead of 'copy' and eliminate 'parent*'.
            >
            > If you wanted 'x' and 'y' to be constant slots in the new object (you
            > didn't show that in your example) then things do get much more
            > complicated. It is still possible to do this using mirrors, however.

            In my opinion, mirrors should not be used in ordinary applications. In
            addition, this solution doesn't look acceptable for frequent use.
            It would be nice to have a construction equivalent to (| x = aX. y = aY
            |) that is evaluated at run-time on every occurance.

            >
            > But I was indeed trying to make this kind of thing simpler.
            >
            > > So
            > > now I'm in process of thinking out a more flexible but yet powerful
            > > language. By now I have implemented two experimental versions of
            > > Self. But I need something different.
            >
            > Me too, but more in the spirit of Self than the Self-inspired languages
            > of the early 1990s.
            >
            > > > Like a simple object model: objects are just flat association lists
            > > > (I'll use Lisp notation below) -
            > > >
            > > > (x 3 y 4)
            > >
            > > flat list has an array structure rather than linked list?
            >
            > By "flat" I meant compared to this form of association list:
            >
            > ((x 3) (y 4))

            There is one problem with that. When you clone the object then the whole
            structure should be duplicated, thus clones don't have any shared
            attributes. In Self all objects have a special _map_ field that is
            shared by all clones and that can be used in determining object 'type'.
            Type information is used by the virtual machine for excluding expensive
            lookups.
            In your case there no any type information about the object. So virtual
            machine will have to perform lookup every time *any* message is sent to
            the object.
            So I would suggest to preserve the idea of using _map_ in each object
            transparently.

            >
            > But it is a *very* interesting question if these should be arrays or
            > linked lists -
            >
            > Lists: + can be shared as the continuation (CDR) of several other
            > lists
            > + can be scanned using a single pointer
            > - since you can point into the "middle" of a list there are
            > many aliasing problems
            > - can't be indexed into

            This is the next problem.
            Imagine that every slot-access operation requires scanning through the
            list of slots.

            > + can be as space efficient as arrays with special encoding
            > + easy to split and put together
            >
            > Arrays: - need three items for scanning: base, index and size
            > + no need to waste more than one virtual address per
            > array
            > + access to any element is equally fast

            I vote for arrays. :-)

            >
            > It is easy to convert one into the other when you need to do some kind
            > of processing for which the normal format is not very good.
            >
            > > Do you want to use the same semantics for a objects, arrays and
            > > hashes? Like, (1 2 3) - array, (x 3 y 4) - object or hash?
            > > Please describe that semantics a bit more.
            >
            > Every object is a list, but there are lists which are not formatted
            > objects (the code examples below, for instance). So I must treat (1 2
            > 3) as an object which does not explicitly list the messages it
            > understands. In fact, the object "1" has this problem as well, but at
            > least the number objects have special tags so they don't look like they
            > might be a regular object. I am still figuring this out.
            >
            > > > append '(z 9) '(x 3 y 4)
            > >
            > > Is this a message-sending construction? What is the receiver?
            >
            > No, this was a pseudo Lisp functional notation. This would be more
            > proper
            >
            > '(z 9) append: '(x 3 y 4)
            >
            > though the problem above shows itself here: how does the '(z 9) object
            > understand the 'append:' message? We would have to force it to be
            > treated as a plain list, which is getting into the reflective level and
            > similar to requesting a mirror for an object in Self.

            For example, you may consider introducing implicit parent for all
            objects. I.e. even empty object () would have a parent 'traits
            basicBehavior'.

            Also I think that syntax (x 3. y 4) is more consistent.
            Because code constructions use dot as a separator too.

            >
            > This needs some careful design.
            >
            > > How to distinguish objects and methods?
            >
            > I am thinking of making methods internally be objects that have a
            > special <code> "slot"
            >
            > ... factorial: (n 0
            > <code> (n < 1 ifTrue:False: 1 '(n * factorial: n-1)
            > )
            > ) ....
            >
            > I have other ideas for blocks than quoted lists, but this will do for
            > now. Hmmm... if instead of "<code>" I used "|" then this would look
            > more like Smalltalk...

            I thought about using the second variant too.

            >
            > Note that though the argument "n" has a default value of 0, it will be
            > replaced once this code object is cloned to create a context object.
            >
            > > Did you get rid off keyworded selectors like a:B:C:?
            >
            > I used one in the above example, but at the low level the keywords
            > aren't spread among the arguments. In a higher level graphical editor I
            > would expect you to be able to write something like
            >
            > n < 1 ifTrue: 1 False: [n * factorial: n - 1]
            >
            > My idea is that the arguments would be underlined, but that is hard to
            > show using ASCII in this email.

            What notation should be produced by parser - Polish or direct?
            How are you planning to handle 'resend.' and '_parent_.' constructions?


            Uladzimir.
          • Jecel Assumpcao Jr
            ... It is important to find out just how much this kind of thing is needed. A language should make common things easy, rare things possible. Of course, there
            Message 5 of 7 , Apr 5, 2001
              On Thursday 05 April 2001 10:57, Uladzimir Liashkevich wrote:
              > > [new object with slots filled from arguments]
              >
              > Yes. It is actually what you should do in this case. It looks not so
              > easy, so I don't like that approach.

              It is important to find out just how much this kind of thing is needed.
              A language should make common things easy, rare things possible. Of
              course, there is a great danger of circular reasoning here: something
              very useful could be rarely used because our current languages make it
              so hard (or inefficient) to do.

              > In my opinion, mirrors should not be used in ordinary applications.

              Very true.

              > In addition, this solution doesn't look acceptable for frequent use.
              > It would be nice to have a construction equivalent to (| x = aX. y =
              > aY |) that is evaluated at run-time on every occurance.

              Where would this be used? Note that quoting conventions can become very
              complex for the kind of expression you want. Some languages have
              something similar for creating function creating functions.

              I just noticed that since the assignment primitive returns the modified
              object, the non constant slot, non cloning version of the example could
              have been written more simply as

              ... ^ ( (| x. y |) x: sX) y: sY

              > So I would suggest to preserve the idea of using _map_ in each object
              > transparently.

              Well, I did mention a not so transparent map-like variation:

              ((x : y :) 3 4)

              The embedded list with the two indirect slots works as both a map and
              as an anonymous parent (by the way, except for assignable parents and
              directed resends do we actually gain anything by making parents visible
              at the base level?). If you clone this, you will only take up three
              words. And the shared list at the begining of this object and its clone
              would be a sort of type information.

              > For example, you may consider introducing implicit parent for all
              > objects. I.e. even empty object () would have a parent 'traits
              > basicBehavior'.

              In Self the primitive interface is much cleaner than in Smalltalk,
              since they look like normal message sends and use the normal send
              bytecodes. But then things become a little strange - how do you look
              them up? They have to be found in some hidden table deep inside the
              virtual machine or else you couldn't send primitive messages to objects
              like ().

              > Also I think that syntax (x 3. y 4) is more consistent.
              > Because code constructions use dot as a separator too.

              I wasn't thinking of having this in the lowest level of code, but use
              an array of arrays instead:

              ((stuff 1) (stuff 2) (last stuff))

              The user would not have to write it like this, of course (this is how
              it works in Logo, for example).

              > > My idea is that the arguments would be underlined, but that is hard
              > > to show using ASCII in this email.
              >
              > What notation should be produced by parser - Polish or direct?

              Direct (infix). It really doesn't make a difference for a compiler, but
              for an interpreter it would seem that you have to go with Polish or
              RPN. Except I have a very nice interpreter I designed in 1983 which
              sends the message *before* evaluating the arguments, so it can execute
              infix code very well. The advantage is that by the time the value of an
              argument has been calculated, the place where it will ultimately go has
              already been created and so there is no need to save it on a local
              stack only to later copy it to another stack.

              I have recently found out that the last Lisp machine at LMI worked
              exactly like this (it had an OPEN instruction and a separate CALL
              instruction) so I can borrow their ideas in my hardware design. So not
              only would my "machine code" (the arrays - thank you for your vote :-)
              be very readable, but my "microcode" would as well.

              > How are you planning to handle 'resend.' and '_parent_.'
              > constructions?

              At the low level these things can be handled by primitives since they
              are infrequent enough.

              resend.copySize: n

              could be translated into

              _PerformResend: 'copySize:' With: n

              though this particular primitive is missing in Self 4.1.2 - you can
              only do non-directed resends of unary messages. Actually, you can do
              directed resends using the '_Perform:DelegatingTo:With:' type
              primitives, as far as I can tell.

              Thanks for your comments,
              -- Jecel
            Your message has been successfully submitted and would be delivered to recipients shortly.