(Anti)Object-oriented reasoning
Brain dump:
Object-oriented programming is a style of
writing instructions for computers which was invented to make
comprehending these instructions easier. Using a list of things to do,
like a cooking recipe, works well for short tasks, but with a computer
it is required to provide every step of every instruction. Imagine a
recipe that has a step “break two eggs into a bowl, keeping the yolks
separate”. This is a “high-level” instruction, since it doesn’t tell us
what muscles to move, what senses to pay attention to, etc. Making
high-level instructions can be done by declaring “functions”, such as
“break_eggs_into_bowl(egg_number, separate_yolks)”, then specify in this
function exactly how to do this. Once the function is declared we can
simply say “break_eggs_into_bowl(2, true)” in our recipe, rather than
having to specify them all over again. Of course, we can use functions
inside functions, so we could even make the entire recipe a function
like “make_cake(flavour)” which includes running the function
“break_eggs_into_bowl(2, true)” as well as many others. The problem then
becomes how to keep track of all these functions.
Using
“libraries” is one way of doing this; you put everything to do with
images in an “image library” and everything to do with music in a “music
library”, and so on, then when you want to write a program which handles
images you just say ‘I’m going to use the image library’ (eg. in C this
would look something like “#include” and Python would look something
like “import images”). The problem with libraries is that, since they’re
meant to include (“encapsulate”) everything to do with their subject (so
that nobody using it has to care what it does, only what it’s functions
are called) by dragging a library into a program you end up “polluting
the namespace”.
A “namespace” is the embodiment of everything
a program knows about at a certain point in its execution. Before we
declare the “break_eggs_into_bowl” function it is not in our namespace,
by declaring it we put it in the namespace so that later on we can use
its name and the computer will know what we’re talking about. The
problem with libraries is that by asking to include them in our program,
the namespace of our program gets filled with everything from the
library. That might be good in that we can include the image library
then say “display(my_image)”, but we have to be very careful that names
don’t get overwritten. For example we might want to make a spreadsheet
that uses a graph library and an image library, but they might both
declare a “display” function. It would not be obvious to us that our
command “display(my_image)” is broken since it’s actually trying to draw
a graph because both functions have the same name. That’s why we use
more than one namespace in our programs, so that a command “import
image” in Python won’t make everything from the image library directly
accessible, instead it makes the namespace of the image library
accessible so that we can say “image.display(my_image)” and, if we like,
“graph.display(my_graph)”. That makes it much more obvious to us which
one we’re using, and the only names that get put in our namespace are
those of the libraries, which we’ve explicitly asked for. There are no
hidden surprises caused by unknown things overwriting parts of our
program.
An extension of this is to use Objects. An object is
like a library, in that it encapsulates everything about a given topic,
but objects make it easier to understand our programs since we can treat
them as the actual things themselves. Rather than saying “import image”
then “image.display(my_picture)” we can say “my_picture.display()”, ie.
we send a message to “my_picture” asking it to display itself. The
reason this makes understanding code easier is that we make the
assumption that whoever made the object has done it sensibly (the same
assumption we must make about libraries in fact), so that when we ask
our objects what to do, they react in intuitive ways. Another nice thing
about objects is that they know all about themselves, so whereas before
we had “graph.display(my_graph)” and “image.display(my_image)”, if the
graph and the image are objects then we can say “my_graph.display()” and
“my_image.display()” and they both know what to do, even though we’re
asking them the same thing. In fact, we can just say
“something.display()” and it will work whether “something” is a graph or
an image, we don’t have to care. Later on we can make a Web page object
and tell that what to do when asked to “display” and voila our
“something.display()” now works for Web pages too.
Object
oriented programming allows us to write programs as a bunch of
interacting objects, a lot like the real world is. Since we can handle
the real world pretty well, this allows us to transfer those skills to
our programming. However, OO programming is not just limited to creating
models of the real world. For example, the use of “anti objects” can
make programs much easier to write and understand, even though they go
against what we know of the world. Anti-objects are objects in a program
which don’t represent anything that exists, but since we’re making a
computer program there’s nothing to stop us making up such things and
then making them do stuff for us. The classic example is the game
Pacman. If you want to write a Pacman game, the hardest part is trying
to make the ghosts intelligent. They have to chase Pacman (ie. head
towards Pacman), but they also have to work around the walls of the
maze, it’s no good just homing in on Pacman if there’s a wall in the
way. By using the level as an anti-object we can make the job of the
ghosts much easier: rather than having intelligent ghosts that try to
think of the best way to get Pacman, we instead make each part of the
level contain an amount of “pacman-ness”. Wherever Pacman is has 100%
pacman-ness, every wall has 0% pacman-ness and the pacman-ness of every
other square in the level is the average of its neighbours. This makes
the ‘pacman-ness’ diffuse through the level like a ‘smell’, but since it
doesn’t go through the walls the pacman-ness of each square becomes a
good indicator of the distance from that square to pacman, following the
maze. Now the ghosts can be really dumb, since they just have to choose
their next move based on whichever direction has more pacman-ness and it
will always be the best move. In fact, if the ghosts are made to act
like walls (ie. they don’t let pacman-ness through) then once a ghost
has blocked a path leading to pacman, it will also block the pacman-ness
smell. The other ghosts will thus find alternative open routes to pacman
and the player’s escape routes will be blocked off. This guarantees that
the ghosts will beat pacman if the number of pacman’s escape routes is
less than or equal to the number of ghosts. I wrote such a pacman game
in an afternoon, but unfortunately it’s on my laptop which is bust at
the moment so I can’t share it. Still, it was an interesting approach to
the problem, even if the resulting game became too hard to ever beat
even with 1 ghost :P
So, we can encapsulate domain-specific
knowledge inside objects, we can ask many different kinds of objects the
same thing and they can interpret it in their own specific way and we
can invent objects that don’t necessarily exist in the system we’re
modelling, and transfer our logic to them. How might we use this to make
an AI?
Imagine that we generalise the pacman example: rather
than ghosts, we have a more general concept of agents and rather than
pacman we have a more general concept of a goal, of which there can be
many. The space, rather than being a maze which we traverse with
movement, becomes a more general space of every imaginable scenario
which we traverse by taking actions (a generalisation of movement). In
this space we have objects for everything we know about, rather than
just pacman. Each type of object has a ‘smell’ in each scenario, which
means “how likely are you?”; ie. given that scenario, what is the chance
that an agent can obtain or access you? The objects can update their own
likelihoods if they encapsulate enough domain-specific knowledge of
themselves, which can be learned over time. The agents then just decide
which goal/goals they want and take the action which leads to a scenario
where the goal is more likely. The only major complication is that the
scenario space isn’t fixed: every object may add contributions, but
considering that the probabilities will be multiplied (eg. the
probability of having an Internet connection via a computer is the
probability of having a computer * the probability of it having an
Internet connection), thus they fall off rapidly and simple heuristics
can prune the scenario space quite quickly.
In such a system,
our reasoning does not have to depend on what we know, since every
object looks after itself. Thus a separate program can handle the
creation, specialisation and connections between objects (for example
through a hierarchy of Bernoulli trials performed on the sensory input).
A problem in AI is often in choosing what to do. Given our probability
system for sub-goals which extends to whatever granularity we like, we
only need one top-level goal, which I propose is “Maximise your
freedom”. This sounds very philosophical, but essentially it means that,
without being given anything else to do, the program would try to
improve its position in the world so that it can do more things in the
future. This could involve performing tasks for payment, since the
possession of money increases what it is able to do. It could of course
rob a bank to obtain money, but there is a large probability of being
caught and deactivated if that course of action is taken, and being
deactivated stops the program from doing anything, and is thus avoided
(ie. self-preservation). By maximising its freedom, a program will be
more likely to be in a good position for doing tasks asked of it in the
future.
This is just a vague, hand-wavey brain dump, but
would probably be interesting to build…