Path: csiph.com!x330-a1.tempe.blueboxinc.net!usenet.pasdenom.info!aioe.org!rt.uk.eu.org!newsfeed.xs4all.nl!newsfeed5.news.xs4all.nl!xs4all!post.news.xs4all.nl!not-for-mail Return-Path: X-Original-To: python-list@python.org Delivered-To: python-list@mail.python.org X-Spam-Status: OK 0.001 X-Spam-Evidence: '*H*': 1.00; '*S*': 0.00; 'python,': 0.01; 'attribute': 0.07; 'behave': 0.07; 'currently,': 0.07; 'function,': 0.07; 'lately': 0.07; 'ugly': 0.07; 'python': 0.08; '(there': 0.09; '*args': 0.09; 'const': 0.09; 'stack,': 0.09; 'wrappers': 0.09; 'exception': 0.12; 'syntax': 0.15; 'examples': 0.15; '(ideally': 0.16; '__init__': 0.16; 'closures': 0.16; 'evaluated.': 0.16; 'expression,': 0.16; 'expression.': 0.16; 'lambda': 0.16; 'lot;': 0.16; 'metaclass': 0.16; 'paragraph,': 0.16; 'scope.': 0.16; 'symbolic': 0.16; 'to:name:python-ideas': 0.16; "wouldn't": 0.17; '>>>': 0.18; 'functions,': 0.18; 'wrap': 0.18; 'sfxlen:2': 0.19; '(which': 0.19; 'checked': 0.20; 'later': 0.21; 'to:2**1': 0.21; 'input': 0.21; '(or': 0.22; 'etc,': 0.23; 'objects,': 0.23; 'originally': 0.24; 'code': 0.25; 'creating': 0.25; 'somewhere': 0.25; 'function': 0.27; "i'm": 0.27; 'message- id:@mail.gmail.com': 0.28; 'expressions': 0.28; 'print': 0.29; 'class': 0.29; '(and': 0.29; '3,000': 0.30; 'objects.': 0.30; 'outer': 0.30; 'received:209.85.210.46': 0.30; 'received:mail- pz0-f46.google.com': 0.30; 'robust': 0.30; 'solved': 0.30; 'usable': 0.30; '(e.g.': 0.30; "i've": 0.31; 'version': 0.31; 'values': 0.32; 'break': 0.32; 'objects': 0.32; 'thanks': 0.32; 'to:addr:python-list': 0.33; 'agree': 0.33; 'object': 0.33; 'there': 0.33; 'certain': 0.34; '(including': 0.34; 'calling': 0.34; 'anything': 0.34; 'forces': 0.34; 'hacking': 0.34; 'something': 0.35; 'things': 0.35; '(for': 0.35; 'uses': 0.36; 'pull': 0.36; 'to:name:python-list': 0.36; 'passed': 0.37; 'with.': 0.37; 'but': 0.37; 'received:google.com': 0.37; 'using': 0.37; 'skip:_ 10': 0.37; 'could': 0.37; 'cases,': 0.38; 'somewhat': 0.38; 'some': 0.38; 'received:209.85': 0.38; 'difficult': 0.39; "i'd": 0.39; 'point': 0.39; 'subject:from': 0.39; 'else': 0.39; 'subject: (': 0.39; 'received:209': 0.39; 'subject:: ': 0.39; 'to:addr:python.org': 0.40; 'issues': 0.40; 'most': 0.60; 'huge': 0.60; 'design': 0.61; 'your': 0.61; 'solicit': 0.64; 'believe': 0.65; 'act': 0.65; 'greetings,': 0.66; 'special': 0.67; 'relevant': 0.70; 'impressed': 0.73; 'validation': 0.73; 'access,': 0.74; 'to:addr:python-ideas': 0.77; 'constraint': 0.84; 'expects': 0.84; 'flexible,': 0.84; 'mock': 0.84; 'nathan': 0.84; 'circuit': 0.93 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=gamma; h=mime-version:date:message-id:subject:from:to:content-type; bh=mZuetqUvkQ6Ow8WR3RDCIOrsO80iS4yI8iC6RYJSpy0=; b=L9lk4GK14LZKLVzG4mQUbkU58imr985XIltl0n5FEaQ0/MJS4YW6GLfCmMLDWEUsfk aNOsy2exDRqy6vC3ZD4r4Z+YMr7/2Otg5ZhbaWjm9Oxv3B4FxlC4PWvCrpDJD9tIEykx 2avHbYCiTOUAqpotkiF0ap+LRv44784amCM3o= MIME-Version: 1.0 Date: Thu, 12 Jan 2012 15:45:48 -0500 Subject: Symbolic expressions (or: partials and closures from the inside out) From: Nathan Rice To: python-list , python-ideas Content-Type: text/plain; charset=ISO-8859-1 X-BeenThere: python-list@python.org X-Mailman-Version: 2.1.12 Precedence: list List-Id: General discussion list for the Python programming language List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Newsgroups: comp.lang.python Message-ID: Lines: 94 NNTP-Posting-Host: 2001:888:2000:d::a6 X-Trace: 1326401152 news.xs4all.nl 6862 [2001:888:2000:d::a6]:59247 X-Complaints-To: abuse@xs4all.nl Xref: x330-a1.tempe.blueboxinc.net comp.lang.python:18882 Greetings, I have been writing a lot of code lately that involves creating symbolic expressions of one form or another, which are then fully evaluated at a later time. Examples of this include Elementwise, where I create expressions that act on every member of an iterable (there is a much improved new version coming soon, by the way), and a design by contract/validation lib I'm working on (which shall remain nameless :D) that uses symbolic expressions in the *args of the metaclass __new__ method to generate a constraint class which validates input using __instancecheck__. I do most of this with lambdas, a little hacking with closures and FunctionType(), and chainable objects. I am very impressed that python is this flexible, but there are some issues with the approach that I would like to rectify, namely: 1. Because of the early binding behavior of most things in Python, if I want to include isinstance(X, someclass) in a symbolic expression, I have to wrap it in a lambda (or use .apply(), in the case of Elementwise). This is not a huge deal for me, but it forces me to create wrappers for lots of functions (e.g. isinstance_(X, someclass)) and/or have users wrap every such function they want to use in a symbolic expression. Having to do this also bloats the code a lot; the github version of Elementwise is over 3,000 LoC at this point (including prodigious documentation, but still...). 2. Python expects that certain functions, such as int(), str(), etc, will have a specific return type. While in general I agree with this, it makes Elementwise somewhat inconsistent (and it will do the same to anything else that wants to work with symbolic expressions). I'm interested in fixing both issues. I believe both issues I've had could be solved by having a robust "symbolic object". These objects would basically usable like ordinary objects, however upon any attribute access or other form of interaction, the object would basically short circuit the calling function, and return a symbolic object directly to the outer scope. The symbolic object would behave like a generator function frozen at the point of attribute access, and upon send()-ing (or whatever method), it would behave exactly as if the values sent had been the ones passed in originally (ideally without consuming the generator). I have thought about ways to approximate this behavior python currently, and while I could hack something together using inspect to pull relevant info from the stack, then break out using a special exception (potentially passing a generator with state as an exception arg), this approach strikes me as VERY brittle, implementation dependent, ugly and difficult to work with. Additionally, you would need to catch the special exception somewhere in the stack, so this trick wouldn't work on the first thing in an expression to be evaluated. As an aside, I'd like to solicit some feedback on the validation syntax I've been working on. Currently, I have code that support things like: X = SymbolicObject() const = Constraints(X * 2 + 1 >= 5, X % 2 != 0) const2 = Constraints(X[-1] == "h") const3 = Constraints(X[-1].upper() == "H") >>> print isinstance(3, const) True >>> print isinstance(2, const) False >>> print isinstance(1, const) False >>> print isinstance("bleh", const2) True >> print isinstance("bleh", const3) True Callables are supported as well, so if you wanted to do something like: Constraints(isinstance(X.attr, someclass), somefunc(X[-2].attr, args)) You could approximate that with: Constraints(lambda x: isinstance(x.attr, someclass), lambda x: somefunc(x[-2].attr, args)) As I mentioned in the first paragraph, Constraints is a metaclass, so your validations are checked using __instancecheck__. I'm also considering having __init__ generate mock objects (for certain straight-forward cases, anyhow). Thanks for your time, Nathan