Saturday, June 25, 2011

Unified Python

After all these days at EuroPython, there is a thought that keep me thinking. It is about how Python have different ways to represent what it could be considered the same thing.

On today's talk, Alex Martelli pointed out that "def" and "lambda" are actually the same concept. This was part of a more complete idea about that both of them have the wrong name ("function" should be the right), and that lambda actually should disappear, but that's another question.

Also, yesterday, Raymond Hettinger reminded that class are actually dictionaries, something that most Pythonistas know, but which also made me thought.

Then, there is something that I never saw very clearly, and it is the subtle difference between an instance and a dictionary, and how trivial it can be in some case, the difference between person['name'] and person.name.

So, I wanted to do an experiment on how it could look Python, if it would try to merge all this entities in ones single format, and even some other things like avoiding assignments that doesn't follow the assignment pattern (I mean class or function definition here, where instead of my_func = [...] it's used def my_func[...]).

Next, there is how the most stupid example I could invent looks like, but first some definitions to make it easier to understand the idea.

map: could be also "class", "dict", "obj", "hash",... and it's the structure for dictionaries, classes and instances.
seq: a list or tuple, any linear sequence of values.
func: a function or callable, that in Python is defined by "def" or "lambda".


foods = seq:
"meat"
"milk"
"bread"

sounds = map:
"bark" = "woof woof"
"mew" = "meow meow"

animal = map:
"step_size" = None
"sound" = None

"move" = func(self, num_steps):
print("I've moved {} units".format(num_steps * self.step_size))

"talk" = func(self):
print(sounds.{self.sound})

"eat" = func(self, food):
print("I'm eating {}".format(food))

cat = map(animal):
"step_size" = 80
"sound" = "mew"

"eat" = func(self, food):
print("I only eat {} if I want to".format(food))


azrael = map(cat):
"owner_name" = "Gargamel"

azrael.move(5)
for food in foods:
azrael.eat(food)


Of course, there are too many things that should be considered before being able to implement this syntax, but can give an idea on how it could look a more unified approach of Python syntax.

See how the syntax for "sounds", which would be a dictionary, "cat", which would be a class, and "azrael", which would be a instance, is exactly the same.

Being used to Python syntax, it's difficult to say if this syntax could be readable, so far I just find it weird. But what looks clear, is that this syntax would make the language simpler, from the implementation point of view, and probably from the programmer point of view, who would probably need to forget some OP concepts first.

Whatever is the conclusion the reader can get from this example, I think it's quite interesting seeing how a class can look exactly the same way as a dictionary, and how an instance can look exactly as a subclass of the base class.

4 comments:

  1. This seems to bear quite a bit of similarity to prototypal inheritance.

    ReplyDelete
  2. Technically, "procedure" is the correct term, not "function". For some reason (perhaps fewer syllables) people in the past erroneously decided to use "function" to describe almost any type of block of code in any language. Procedures involve "how to" knowledge, whereas functions are math constructs that are definitions or "what this is" knowledge.

    In any case, this topic has been covered before: consult "Structure and Interpretation of Computer Programs" (SICP). SICP shows that if your language truly supports procedures as "first class", then you pass it around just like any other data. I can use the number 3, or I can assign it to a variable name X. The same is true with procedures. I can use lambda to define a procedure and use it directly just like I can use the number 3 directly, or I can assign that procedure to a name.

    Both Python and Lisp have first class procedures in some form or another, but the basic lesson from SICP is that there is no difference between a named procedure and an anonymous one: a named procedure is merely an anonymous one that is explicitly assigned to a name. It should be obvious, but I'm surprised when I see that people miss that point. There is even more to the story: actually, all procedures have a name, even "anonymous" ones. Their name *is* their definition (it's just a long one)! In the same way that we might see the number 3 as defined as 1+1+1, we usually think of 3 as "3". But if we could pass around 1+1+1 everywhere instead, it should make no difference to the result. Put another way, a name is a reference to what the name means, so they are in some sense equivalent. Hence, without being assigned a name explicitly, the name would be the definition. So why is this useful? Have you ever though how you could write a recursive *anonymous* procedure? Look up "Y Combinator" and read between the lines...

    ReplyDelete
  3. "def" and "lambda" aren't quite the same -- "def" is roughly "lambda" plus assignment. That means that "lambda" is composable (i.e., I can write "lambda x : something (lambda y: something else)") but "def" isn't.

    ReplyDelete
  4. This reminds me JavaScript syntax. Somehow I feel not very well when I think about this.
    This names: map, seq, func and the notation - I seen this before ... Haskell or maybe not?
    Somehow Python with def, dict, list and others is more readable and clear for me then presented approach - sorry, but this not convince me in this form. Good idea but somehow not very "pythonic" in my opinion.

    ReplyDelete