Anonymus functions revisited : tuple actions

K

Kay Schluehr

Hi all,

thanks for Your attention !

I think my proposal was more in mind of Rons modified exec than
Pythons lambda.

When George proposed his unpacking behavoir for list-comps as a pack of
suggar:

1. [x*y-z for (x,y,z=0) in (1,2,3), (4,5), (6,7,8)]

I interpreted it in a subsequent posting in lambda fashion:

2. [(lambda x,y,z=0:x*y-z)(*v) for v in (1,2,3), (4,5), (6,7,8)]

which would be equivalent, if Georges syntax became standard Python.

This motivates a fresh view on tuples as unpacking actions on other tuples
which could be generalized to anonymus functions which are quite similar
to Pythons lambda but are NOT the Python lambda because of different
scoping rules
which was addressed lately by Rons and Georges postings in this thread.

Remember the already admissable expression:
>>> [x*y-z for (x,y,z) in (1,2,3),(4,5,6)] [-1,14]
>>>> x,y
(4,5)

x and y are bound variables that are visible outside the list-comp !

So it would be with (x,y,z) -> (x,y,z) when it is applied to any tuple
(a,b,c) with 3 elements. It does not return (a,b,c) but (x,y,z) with
x = a, y = b, z = c. This is the reason why it is simple to chain
tuple-actions.

In 1. we have actually two different tuple actions:

f = (x,y,z=0) -> (x,y,z)
g = (x,y,z) -> x*y-z

which is chainable by g o f.

If we expand the notion of tuple-action in the list-comp 1. we get

[(x,y,z)->x*y-z for (x,y,z=0)->(x,y,z) in (1,2,3), (4,5), (6,7,8)]

which is still a short form of the explicit

[((x,y,z)->x*y-z)(x,y,z) for (x,y,z=0)->(x,y,z) in (1,2,3), (4,5), (6,7,8)].

There is still no such thing as a free/unbound variable as in real
lambda calculus
that can be bound by another lambda. Tuple actions would obviously sweep
off this
misguided association - o.k not so for Pythonistas ;)

Maybe Guidos aversion against FP is bit misguided too because it is
actually in
the language but hidden as special rules.

Regards Kay
 
B

bruno modulix

Kay said:
Hi all,

thanks for Your attention !

I think my proposal was more in mind of Rons modified exec than
Pythons lambda.

When George proposed his unpacking behavoir for list-comps as a pack of
suggar:

1. [x*y-z for (x,y,z=0) in (1,2,3), (4,5), (6,7,8)]

I interpreted it in a subsequent posting in lambda fashion:

2. [(lambda x,y,z=0:x*y-z)(*v) for v in (1,2,3), (4,5), (6,7,8)]

Argh! Stupid me ! I never thought of using the star operator for this :(
Thanks Kay.

(snip)
 
R

Ron

I think my proposal was more in mind of Rons modified exec than
Pythons lambda.

When George proposed his unpacking behavoir for list-comps as a pack of
suggar:

1. [x*y-z for (x,y,z=0) in (1,2,3), (4,5), (6,7,8)]

I interpreted it in a subsequent posting in lambda fashion:

2. [(lambda x,y,z=0:x*y-z)(*v) for v in (1,2,3), (4,5), (6,7,8)]

Thank you Kay, All of this is really intersting and I'm learning a
lot about the language through these discussions.


The following is an experiment I did this morning. :)

I was surprised it worked as well as it did, although I don't think it
should be used in any real production code. Not in it's present form
anyway.

The idea is to have a container class like a tuple for program code
that can be moved around and used when needed. Very flexable, maybe
if it could be done without the strings and the exec/eval() functions
in it?

Ron_Adam


# codedo.py
import types

class code(tuple):
"""
Inline Code Storage Class

name = code(('expression','expression',...))
varables = name.do([locals()],['invars'],'outvars')

This is experimental.

Warning: This is experimental! This class has not
been tested. It also uses exec, and eval(), which
can be a security risk.
"""
def do(self, *args ):
if type(args[0]) == type({}):
parentnames = args[0]
else:
parentnames = globals()
if len(args)>1:
argslist = args[1].split(',')
else:
argslist = args
for a in argslist:
if parentnames.has_key(a):
exec a+'=parentnames[a]'
for c in self:
exec(c)
return eval(args[-1]) # The last argument are the return
varable(s).


if __name__ == '__main__':
"""
Test it. This is only what works, not what doesn't.
"""

# Left to Right order.
y=3
print code(('y=y*2','x=y**2')).do('x')

# *** Define and use later! ***
mybutton_action = code(('z=y*2','x=z**2','result=x+2'))
y = 1
print mybutton_action.do('y','result')
y = 10
print mybutton_action.do('y','result')
y = 100
print mybutton_action.do('y','result')

# Return multiple values.
toxyz = code(('x*=2','y*=2','try:z\nexcept:z=0','z*=2'))
x = 2
y = 3
#z = 4
a, b, c = toxyz.do('x,y,z')
print a, b, c

# 1. [x*y-z for (x,y,z=0) in (1,2,3), (4,5), (6,7,8)]
print code(('r=[]','for x,y,z in
[(1,2,3),(4,5,0),(7,8,9)]:r.append(x*y-z)')).do('r')

# or... trailing comma needed here to make a uni-tuple.
print code(('r=list([x*y-z for x,y,z in
(1,2,3),(4,5,0),(7,8,9)])',)).do('r')

# post process list before returning.
print code(('r = [ x for x in range(1,11) ]','r=r*2')).do('r')

# From within a function:
# We need to pass locals() to so it can find the variables.
def fn1():
x = 5
y = 10
lfunction = code(('z = x*2+y',)).do(locals(),'x,y','z')
print lfunction
fn1()
 
K

Kay Schluehr

Ron said:
I think my proposal was more in mind of Rons modified exec than
Pythons lambda.

When George proposed his unpacking behavoir for list-comps as a pack of
suggar:

1. [x*y-z for (x,y,z=0) in (1,2,3), (4,5), (6,7,8)]

I interpreted it in a subsequent posting in lambda fashion:

2. [(lambda x,y,z=0:x*y-z)(*v) for v in (1,2,3), (4,5), (6,7,8)]

Thank you Kay, All of this is really intersting and I'm learning a
lot about the language through these discussions.

Thanks, Ron !
The following is an experiment I did this morning. :)

I was surprised it worked as well as it did, although I don't think it
should be used in any real production code. Not in it's present form
anyway.

The idea is to have a container class like a tuple for program code
that can be moved around and used when needed. Very flexable, maybe
if it could be done without the strings and the exec/eval() functions
in it?

I personally don't like using exec and eval for stuff different from
evaluating user input.

You rely much on "evaluate statement on the line" by adapting
conventional Python syntax. I think one can go a bit further breaking
the syntactical prejudices and apply tuple-actions :)

Playing a bit with tuple-actions shows that the concept is quite
powerfull and can be used to create simple statements.

First of all the semantics has to be patched:

We have

(x,y,z=0) -> (x,y,z)

as a tuple assignment

((x,y,z=0)->(x,y,z))(a,b,c) = (x=a,y=b,z=c)

But it is not clear what

(x,y,z=0) -> x*y-z

actually means?


Proposal:

(x,y=0) -> x*y => ((x,y=0)->x*y) (a,b) -> (x=a,y=b),a*b
(x,y=0) -> (x*y) => ((x,y=0)->(x*y))(a,b) -> (x=a*b,y=b)

So (x,y=0) -> x*y is appending the result to the argument tuple.

Remark: this is isomorph to

(x,y=0,res=None) -> ((x,y),x*y)

but it becomes harder now to identify

(x,y,res=None) -> ((x,y),x*y)
with
x*y

Provide a compiler-hint:

(x,y,()) -> x*y

Now we are ready for a few examples:


default value:
(i) -> (0) # i = 0

inplace increment:
(i) -> i+1 # i = i+1

conditional expression:
(i) -> i<3 # i,res = i,i<3

simple transformation:
(res) -> (res+i**2) # res = res+i**2


Define a While loop as a function:

def While( par, cond, change, action):
par(None) # create default
res = 0
while cond(par)[1]:
action(res)
change(par)
return res

Let's apply it to some tuple actions:

While((i)->(0), (i)->i<3, (i)->(i+1), (res)->(res+i**2))

and evaluate While stepwise:

1. par(None) <=> (i)->(0)(None) # (i) = (0)
2. cond(par)[1] <=> (i)->i<3(0) # (i,c) = (0,True)
3. action(res) <=> (res) -> (res+i**2)(0) # (res) = (0)
4. change(par) <=> (i)->(i+1)(0) # (i) = (1)
5. cond(par)[1] <=> (i)->i<3(1) # (1,c) = (0,True)
6. action(res) <=> (res) -> (res+i**2)(0) # (res) = (1)
7. change(par) <=> (i)->(i+1)(1) # (i) = (2)
5. cond(par)[1] <=> (i)->i<3(2) # (2,c) = (0,True)
6. action(res) <=> (res) -> (res+i**2)(1) # (res) = (5)
7. change(par) <=> (i)->(i+1)(2) # (i) = (3)
5. cond(par)[1] <=> (i)->i<3(2) # (2,c) = (0,False)
break

=> res = 5


If we customize the other control flow primitives For and If it should
be possible to create a little language only by using this primitives.

It is obvious by definition of our While that we can replace arguments
on the fly:

conds = [(i)->i<3, (i)->i+2<7, (i)->i>=0]

[ While((i)->(0), cond, (i)->(i+1), (res)->(res+i**2)) for cond in
conds]

=> [5,29,0]


Wouldn't it be fun to use in Python?

Only drawback: does not look like executable pseudo-code anymore :(


Regards Kay
 
G

George Sakkis

Kay Schluehr said:
[snipped]

Wouldn't it be fun to use in Python?

Only drawback: does not look like executable pseudo-code anymore :(


Regards Kay

I don't know if it would be fun, but it certainly doesn't look accessible to mere mortals :) I'm
not sure if the mind boggling is more due to the syntax with the '->' and all or the semantics, but
it goes in the oppposite direction from my initial proposal (having defaults in for loops) with
respect to readability.

Regards,
George
 
R

Ron

Kay Schluehr said:
[snipped]

Wouldn't it be fun to use in Python?

Only drawback: does not look like executable pseudo-code anymore :(


Regards Kay

I don't know if it would be fun, but it certainly doesn't look accessible to mere mortals :) I'm
not sure if the mind boggling is more due to the syntax with the '->' and all or the semantics, but
it goes in the oppposite direction from my initial proposal (having defaults in for loops) with
respect to readability.

Regards,
George

Hi George, I think I got the default variable functions working now.

if isa('name'): do something

Checks locals, globals, and builtins for the varable name.

variable = ifno('name', object)

Sets a variable name to an object if it does not exist, or sets it to
the 'variable name's object if it does. (The target doesn't have to
match.)

They are slower than try/except, but can be used in expressions. Like:

x,y,z = x,y, ifno('z',0)


#---Here's the code---------------------

import sys

def isa(v):
"""
Check if a varable exists in the current
(parent to this function), global, or
builtin name spaces.

use: bool = isa( str )
returns True or False
"""
plocals = sys._getframe(1).f_locals
if plocals.has_key(v) or globals().has_key(v) or \
__builtins__.locals().has_key(v):
return True
return False


def ifno(v, obj=None):
"""
Check if a varable does not exists, return a
default value, otherwise return the varable obj.

use: obj = ifno( str [,obj=None] )
if str exist, returns str's object
if str does not exist, returns specified object
"""
plocals = sys._getframe(1).f_locals
if plocals.has_key(v):
return plocals[v]
if globals().has_key(v):
return globals()[v]
if __builtins__.locals().has_key(v):
return __builtins__.locals()[v]
return obj


def test():
"""
Test isa() and ifno() functions:
"""

# Totally useless routine. ;)
import random
for n in range(25):

# Delete a random x,y,z coordinate to
# simulate an unrealiabe data source.
d = random.choice([1,2,3])
if d==1:
if isa('x'): del x
elif d==2:
if isa('y'): del y
else:
if isa('z'): del z

# Replace the missing Varible with a random number.
r = int(random.random()*100)
x, y, z = ifno('x',r), ifno('y',r), ifno('z',r)
print x, y, z


if __name__ == '__main__':
test()

#-------------------------------------
 
R

Ron_Adam

I personally don't like using exec and eval for stuff different from
evaluating user input.

I lean the other way. I never want to use user impute for eval and
exec. Way too risky. But limited use, that is not user input, should
be ok. The programmer can do anything he/she wants anyways, so exec or
eval is just another tool in that respect.

But I am trying to find a way to limit the code objects. To make it
safe I need to hide all the name spaces except locals within the
function. Not sure if it's possible.. um, not easy. I know it's
possible.
You rely much on "evaluate statement on the line" by adapting
conventional Python syntax. I think one can go a bit further breaking
the syntactical prejudices and apply tuple-actions :)

Playing a bit with tuple-actions shows that the concept is quite
powerfull and can be used to create simple statements.

First of all the semantics has to be patched:

We have

(x,y,z=0) -> (x,y,z)

as a tuple assignment

((x,y,z=0)->(x,y,z))(a,b,c) = (x=a,y=b,z=c)

But it is not clear what

(x,y,z=0) -> x*y-z

actually means?

I think I'm following you, but I'm not sure how much of it is
diagramic or meant to be actual code.

Are you suggesting to use the -> as a operator? If so then this is
backwards to the = operator.

x*y-z <- (x,y,z=0)

Would be more consistent to current syntax. Then the <- would have
the meaning of translate instead of assign. Or possibly an operator
for adapt? Guido wants to use the -> in function definitions to
specify return value types. Although I see problems with that too.
Proposal:

(x,y=0) -> x*y => ((x,y=0)->x*y) (a,b) -> (x=a,y=b),a*b
(x,y=0) -> (x*y) => ((x,y=0)->(x*y))(a,b) -> (x=a*b,y=b)

So (x,y=0) -> x*y is appending the result to the argument tuple.

Remark: this is isomorph to

(x,y=0,res=None) -> ((x,y),x*y)

but it becomes harder now to identify

(x,y,res=None) -> ((x,y),x*y)
with
x*y

Provide a compiler-hint:

(x,y,()) -> x*y

I'm not following you completely here. It appears your trying to
create a system to map different arguments to equations in a indirect
way.

Now we are ready for a few examples:


default value:
(i) -> (0) # i = 0

inplace increment:
(i) -> i+1 # i = i+1

conditional expression:
(i) -> i<3 # i,res = i,i<3

Lost me again, what is res?
simple transformation:
(res) -> (res+i**2) # res = res+i**2


Define a While loop as a function:

def While( par, cond, change, action):
par(None) # create default
res = 0
while cond(par)[1]:
action(res)
change(par)
return res

Let's apply it to some tuple actions:

While((i)->(0), (i)->i<3, (i)->(i+1), (res)->(res+i**2))

and evaluate While stepwise:

1. par(None) <=> (i)->(0)(None) # (i) = (0)
2. cond(par)[1] <=> (i)->i<3(0) # (i,c) = (0,True)
3. action(res) <=> (res) -> (res+i**2)(0) # (res) = (0)
4. change(par) <=> (i)->(i+1)(0) # (i) = (1)
5. cond(par)[1] <=> (i)->i<3(1) # (1,c) = (0,True)
6. action(res) <=> (res) -> (res+i**2)(0) # (res) = (1)
7. change(par) <=> (i)->(i+1)(1) # (i) = (2)
5. cond(par)[1] <=> (i)->i<3(2) # (2,c) = (0,True)
6. action(res) <=> (res) -> (res+i**2)(1) # (res) = (5)
7. change(par) <=> (i)->(i+1)(2) # (i) = (3)
5. cond(par)[1] <=> (i)->i<3(2) # (2,c) = (0,False)
break

=> res = 5


If we customize the other control flow primitives For and If it should
be possible to create a little language only by using this primitives.

It is obvious by definition of our While that we can replace arguments
on the fly:

conds = [(i)->i<3, (i)->i+2<7, (i)->i>=0]

[ While((i)->(0), cond, (i)->(i+1), (res)->(res+i**2)) for cond in
conds]

=> [5,29,0]


Wouldn't it be fun to use in Python?

Only drawback: does not look like executable pseudo-code anymore :(


Regards Kay

I think I get the gist of what you are trying to do, but I can't
follow it entirely.

Well it looks like an interesting puzzle, but you'll need to go a
little slower for some of us. I tired though. :)

Ron_Adam
 
K

Kay Schluehr

Ron_Adam said:
I lean the other way. I never want to use user impute for eval and
exec. Way too risky.

Well a Python console does nothing else. I love that and sometimes I
would like to develop the console a bit further in the direction of
Mathematicas console where special formatted text- as well as image
output is possible. Security problems of Python is another issue.
But limited use, that is not user input, should
be ok. The programmer can do anything he/she wants anyways, so exec or
eval is just another tool in that respect.

But I am trying to find a way to limit the code objects. To make it
safe I need to hide all the name spaces except locals within the
function. Not sure if it's possible.. um, not easy. I know it's
possible.

A castrated InteractiveInterpreter object with restricted access to os,
file, eval, exec, sys.modules, __class__, import?
I think I'm following you, but I'm not sure how much of it is
diagramic or meant to be actual code.

Are you suggesting to use the -> as a operator?
Yes.

If so then this is
backwards to the = operator.

x*y-z <- (x,y,z=0)

Would be more consistent to current syntax. Then the <- would have
the meaning of translate instead of assign. Or possibly an operator
for adapt? Guido wants to use the -> in function definitions to
specify return value types. Although I see problems with that too.

Me too. I have written something about it in Guidos Artima blog a
couple of weeks ago but I do mention now that also my thoughts about
this topic were a bit short-sighted. Now I will wait for PyPy and it's
annotator/type inferencer to become finished and run on CPythons speed
( if possible ).

I have to think about the "<-" syntax but I'm a little in hurry - going
to travel the next days.

[...]
I'm not following you completely here. It appears your trying to
create a system to map different arguments to equations in a indirect
way.

That's true. An important aspect that has to made clearer:

diagrammtic:
x -> (=,(x,.))

(x -> (=,(x,.)))(a) => x=a

But

(x,y,()) x*y

Lost me again, what is res?

A mistake.

(i)->i<3 should be a short form for (i,res=None) -> (i,i<3).

But the interpreter should not create a variable on it's own. But I
don't know how to prevent creation of a new name. Maybe the expression
should not be shortened to

(i) -> i<3

but to

(i,res=None) -> i<3 ?

[...]
I think I get the gist of what you are trying to do, but I can't
follow it entirely.

Well it looks like an interesting puzzle, but you'll need to go a
little slower for some of us. I tired though. :)

It's all developed during this discussion. Sometimes I'm a bit
surprised were it goes.

To make my intention clear for another time, also for George who
mistrusts these exercises alltogether. I want to derive a syntax and
semantics for anonymus functions ( called "tuple-actions" ) that are
generalizations of rules that are already used implicitely within
Python e.g. tuple-unpacking. This is done by progressive interpretation
and extension. They are not there by means of an accident, what Guido
claims about the current lambda which he feels to be sticked onto the
language.

Regards,
Kay
 
G

George Sakkis

Kay Schluehr said:
To make my intention clear for another time, also for George who
mistrusts these exercises alltogether. I want to derive a syntax and
semantics for anonymus functions ( called "tuple-actions" ) that are
generalizations of rules that are already used implicitely within
Python e.g. tuple-unpacking. This is done by progressive interpretation
and extension. They are not there by means of an accident, what Guido
claims about the current lambda which he feels to be sticked onto the
language.

Regards,
Kay


I'd be glad to see a well-formulated view of the proposed tuple actions, provided I (and suspect
others) can get through the syntax burden first. Expressions such as "While((i)->(0), (i)->i<3,
(i)->(i+1), (res)->(res+i**2))" do not remind me the python I know and love. Alternatively, if it's
not easy to come up with a grokable syntax, I would still be ok with it if it remained "under the
hood" for the most part, like other obscure features such as metaclasses and descriptors; for
instance I wouldn't mind at all if the traditional control flow primitives were implemented as
functions using your idea, as long as I didn't have to know the details (and as long as other
things, e.g. execution speed, were the same).

As for the forthcoming - as it seems - deprecation or removal of lambdas, I'm in the minority that
thinks it's a step in the wrong direction. The way they ended up in the language may be a historic
accident, but I would favor a solution that improved them instead of removing them. The notion of
'anonymous functions' alone reveals a misconception in my opinion: that functions *have to* have a
name to be 'normal'. Still, a name is just a binding to an object that exists independently of the
name. We don't find 'anonymous strings', tuples, lists, etc. bad, so why should functions be
different ? In this respect, functions are not 100% first class objects. Instead of removing
lambdas, I would prefer to see them upgraded, perhaps with a new syntax, accepting more than one
expressions and statements instead of a single expression. Sure, one can define named functions
locally to avoid polluting the global scope, but why should he ? Just imagine how would python look
like with a similar rule for other primitive types. Anyway, I guess I can live without lambdas but
I'll miss them.

George
 
R

Ron_Adam

It's all developed during this discussion. Sometimes I'm a bit
surprised were it goes.

I enjoy exploring ideas this way. Many times it leads to dead ends or
you just end up with a long way back to where you started, but
sometimes you get a surprise, and almost always a deeper understanding
of the subject. :)
To make my intention clear for another time, also for George who
mistrusts these exercises alltogether. I want to derive a syntax and
semantics for anonymus functions ( called "tuple-actions" ) that are
generalizations of rules that are already used implicitely within
Python e.g. tuple-unpacking. This is done by progressive interpretation
and extension. They are not there by means of an accident, what Guido
claims about the current lambda which he feels to be sticked onto the
language.

Looking at the syntax of lambda, I think I agree with Guido.

result = lambda *args: expression

It's works like a function, but is formatted like a for or if
statement. It should have been something like this.

result = lambda{ *args: expression}

Another interesting possibility by exploring ideas and concepts. :)

Using a dictionary instead of ()'s to pass the arguments and
expressions. This would simplify parsing it, because it could be
handled as an an object instead of having to parse the args and
expression first.


What if you could:

x = lambda{ x, y: x+y}
Hmm comma creates a problem here. so...

x = lambda{ (x,y): x+Y }

This is more consistent with python syntax and makes more since. the
args are in a tuple as they would be in function.

x = lambda{ (x,y): x+y } is same as x = function(x,y): return x+y


Could this work too?:

x, y, z = lambda{ (x,y): x+y, (x,z):x+z, (x,v):x+v }

Short hand for:

x,y,z = lambda{(x,y):x+y}, lambda{(x,z):x+z, lambda{(x,v):x+v}


For compatibility purposes, You would need to give it a different
name:

af, afn, ann, lamb, lam, lm, ?

Or just call it what it is.. function{(args):expression}

Then it would be easy to explain, teach, and remember.


Ron_Adam
 
R

Reinhold Birkenfeld

Ron_Adam said:
LOL, :)

Is that to discourage people from wanting to use them as block
designators?

Well, it certainly expresses one basic design principle behind the language.

Together with "import this", I consider this to be the Holy Grail of
Python ;)

Reinhold
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,796
Messages
2,569,645
Members
45,371
Latest member
TroyHursey

Latest Threads

Top