Hi Marko,

> Actually, following on #A4, you could also write those as multiple decorators:
> @snpashot(lambda _, some_identifier: some_func(_, some_argument.some_attr)
> @snpashot(lambda _, other_identifier: other_func(_.self))

Yes, though if we’re talking syntax using kwargs would probably be better.
Using “P” instead of “_”: (I agree that _ smells of ignored arguments)

@snapshot(some_identifier=lambda P: ..., some_identifier2=lambda P: ...)

Kwargs has the advantage that you can extend multiple lines without repeating 
@snapshot, though many lines of @capture would probably be more intuitive since 
each decorator captures one variable.

> Why uppercase "P" and not lowercase (uppercase implies a constant for me)?

To me, the capital letters are more prominent and explicit- easier to see when 
reading code. It also implies its a constant for you- you shouldn’t be 
modifying it, because then you’d be interfering with the function itself.

Side node: maybe it would be good to have an @icontract.nomutate (probably use 
a different name, maybe @icontract.readonly) that makes sure a method doesn’t 
mutate its own __dict__ (and maybe the __dict__ of the members of its 
__dict__). It wouldn’t be necessary to put the decorator on every read only 
function, just the ones your worried might mutate.

Maybe a @icontract.nomutate(param=“paramname”) that ensures the __dict__ of all 
members of the param name have the same equality or identity before and after. 
The semantics would need to be worked out.

> On Sep 26, 2018, at 8:58 AM, Marko Ristin-Kaufmann <marko.ris...@gmail.com> 
> wrote:
> 
> Hi James,
> 
> Actually, following on #A4, you could also write those as multiple decorators:
> @snpashot(lambda _, some_identifier: some_func(_, some_argument.some_attr)
> @snpashot(lambda _, other_identifier: other_func(_.self))
> 
> Am I correct?
> 
> "_" looks a bit hard to read for me (implying ignored arguments).
> 
> Why uppercase "P" and not lowercase (uppercase implies a constant for me)? 
> Then "O" for "old" and "P" for parameters in a condition:
> @post(lambda O, P: ...)
> ?
> 
> It also has the nice property that it follows both the temporal and the 
> alphabet order :)
> 
>> On Wed, 26 Sep 2018 at 14:30, James Lu <jam...@gmail.com> wrote:
>> I still prefer snapshot, though capture is a good name too. We could use 
>> generator syntax and inspect the argument names.
>> 
>> Instead of “a”, perhaps use “_”. Or maybe use “A.”, for arguments. Some 
>> people might prefer “P” for parameters, since parameters sometimes means the 
>> value received while the argument means the value passed.
>> 
>> (#A1)
>> 
>> from icontract import snapshot, __
>> @snapshot(some_func(_.some_argument.some_attr) for some_identifier, _ in __)
>> 
>> Or (#A2)
>> 
>> @snapshot(some_func(some_argument.some_attr) for some_identifier, _, 
>> some_argument in __)
>> 
>> —
>> Or (#A3)
>> 
>> @snapshot(lambda some_argument,_,some_identifier: 
>> some_func(some_argument.some_attr))
>> 
>> Or (#A4)
>> 
>> @snapshot(lambda _,some_identifier: some_func(_.some_argument.some_attr))
>> @snapshot(lambda _,some_identifier, other_identifier: 
>> some_func(_.some_argument.some_attr), other_func(_.self))
>> 
>> I like #A4 the most because it’s fairly DRY and avoids the extra punctuation 
>> of 
>> 
>> @capture(lambda a: {"some_identifier": some_func(a.some_argument.some_attr)})
>> 
>> On Sep 26, 2018, at 12:23 AM, Marko Ristin-Kaufmann <marko.ris...@gmail.com> 
>> wrote:
>> 
>>> Hi,
>>> 
>>> Franklin wrote:
>>>> The name "before" is a confusing name. It's not just something that
>>>> happens before. It's really a pre-`let`, adding names to the scope of
>>>> things after it, but with values taken before the function call. Based
>>>> on that description, other possible names are `prelet`, `letbefore`,
>>>> `predef`, `defpre`, `beforescope`. Better a name that is clearly
>>>> confusing than one that is obvious but misleading.
>>> 
>>> James wrote:
>>>> I suggest that instead of “@before” it’s “@snapshot” and instead of “old” 
>>>> it’s “snapshot”.
>>> 
>>> 
>>> I like "snapshot", it's a bit clearer than prefixing/postfixing verbs with 
>>> "pre" which might be misread (e.g., "prelet" has a meaning in Slavic 
>>> languages and could be subconsciously misread, "predef" implies to me a 
>>> pre-definition rather than prior-to-definition , "beforescope" is very 
>>> clear for me, but it might be confusing for others as to what it actually 
>>> refers to ). What about "@capture" (7 letters for captures versus 8 for 
>>> snapshot)? I suppose "@let" would be playing with fire if Python with 
>>> conflicting new keywords since I assume "let" to be one of the candidates.
>>> 
>>> Actually, I think there is probably no way around a decorator that 
>>> captures/snapshots the data before the function call with a lambda (or even 
>>> a separate function). "Old" construct, if we are to parse it somehow from 
>>> the condition function, would limit us only to shallow copies (and be 
>>> complex to implement as soon as we are capturing out-of-argument values 
>>> such as globals etc.). Moreove, what if we don't need shallow copies? I 
>>> could imagine a dozen of cases where shallow copy is not what the 
>>> programmer wants: for example, s/he might need to make deep copies, hash or 
>>> otherwise transform the input data to hold only part of it instead of 
>>> copying (e.g., so as to allow equality check without a double copy of the 
>>> data, or capture only the value of certain property transformed in some 
>>> way).
>>> 
>>> I'd still go with the dictionary to allow for this extra freedom. We could 
>>> have a convention: "a" denotes to the current arguments, and "b" denotes 
>>> the captured values. It might make an interesting hint that we put "b" 
>>> before "a" in the condition. You could also interpret "b" as "before" and 
>>> "a" as "after", but also "a" as "arguments".
>>> 
>>> @capture(lambda a: {"some_identifier": 
>>> some_func(a.some_argument.some_attr)})
>>> @post(lambda b, a, result: b.some_identifier > result + 
>>> a.another_argument.another_attr)
>>> def some_func(some_argument: SomeClass, another_argument: AnotherClass) -> 
>>> SomeResult:
>>>     ...
>>> "b" can be omitted if it is not used. Under the hub, all the arguments to 
>>> the condition would be passed by keywords.
>>> 
>>> In case of inheritance, captures would be inherited as well. Hence the 
>>> library would check at run-time that the returned dictionary with captured 
>>> values has no identifier that has been already captured, and the linter 
>>> checks that statically, before running the code. Reading values captured in 
>>> the parent at the code of the child class might be a bit hard -- but that 
>>> is case with any inherited methods/properties. In documentation, I'd list 
>>> all the captures of both ancestor and the current class.
>>> 
>>> I'm looking forward to reading your opinion on this and alternative 
>>> suggestions :)
>>> Marko
>>> 
>>>> On Tue, 25 Sep 2018 at 18:12, Franklin? Lee 
>>>> <leewangzhong+pyt...@gmail.com> wrote:
>>>> On Sun, Sep 23, 2018 at 2:05 AM Marko Ristin-Kaufmann
>>>> <marko.ris...@gmail.com> wrote:
>>>> >
>>>> > Hi,
>>>> >
>>>> > (I'd like to fork from a previous thread, "Pre-conditions and 
>>>> > post-conditions", since it got long and we started discussing a couple 
>>>> > of different things. Let's discuss in this thread the implementation of 
>>>> > a library for design-by-contract and how to push it forward to hopefully 
>>>> > add it to the standard library one day.)
>>>> >
>>>> > For those unfamiliar with contracts and current state of the discussion 
>>>> > in the previous thread, here's a short summary. The discussion started 
>>>> > by me inquiring about the possibility to add design-by-contract concepts 
>>>> > into the core language. The idea was rejected by the participants mainly 
>>>> > because they thought that the merit of the feature does not merit its 
>>>> > costs. This is quite debatable and seems to reflect many a discussion 
>>>> > about design-by-contract in general. Please see the other thread, "Why 
>>>> > is design-by-contract not widely adopted?" if you are interested in that 
>>>> > debate.
>>>> >
>>>> > We (a colleague of mine and I) decided to implement a library to bring 
>>>> > design-by-contract to Python since we don't believe that the concept 
>>>> > will make it into the core language anytime soon and we needed badly a 
>>>> > tool to facilitate our work with a growing code base.
>>>> >
>>>> > The library is available at http://github.com/Parquery/icontract. The 
>>>> > hope is to polish it so that the wider community could use it and once 
>>>> > the quality is high enough, make a proposal to add it to the standard 
>>>> > Python libraries. We do need a standard library for contracts, otherwise 
>>>> > projects with conflicting contract libraries can not integrate (e.g., 
>>>> > the contracts can not be inherited between two different contract 
>>>> > libraries).
>>>> >
>>>> > So far, the most important bits have been implemented in icontract:
>>>> >
>>>> > Preconditions, postconditions, class invariants
>>>> > Inheritance of the contracts (including strengthening and weakening of 
>>>> > the inherited contracts)
>>>> > Informative violation messages (including information about the values 
>>>> > involved in the contract condition)
>>>> > Sphinx extension to include contracts in the automatically generated 
>>>> > documentation (sphinx-icontract)
>>>> > Linter to statically check that the arguments of the conditions are 
>>>> > correct (pyicontract-lint)
>>>> >
>>>> > We are successfully using it in our code base and have been quite happy 
>>>> > about the implementation so far.
>>>> >
>>>> > There is one bit still missing: accessing "old" values in the 
>>>> > postcondition (i.e., shallow copies of the values prior to the execution 
>>>> > of the function). This feature is necessary in order to allow us to 
>>>> > verify state transitions.
>>>> >
>>>> > For example, consider a new dictionary class that has "get" and "put" 
>>>> > methods:
>>>> >
>>>> > from typing import Optional
>>>> >
>>>> > from icontract import post
>>>> >
>>>> > class NovelDict:
>>>> >     def length(self)->int:
>>>> >         ...
>>>> >
>>>> >     def get(self, key: str) -> Optional[str]:
>>>> >         ...
>>>> >
>>>> >     @post(lambda self, key, value: self.get(key) == value)
>>>> >     @post(lambda self, key: old(self.get(key)) is None and 
>>>> > old(self.length()) + 1 == self.length(),
>>>> >           "length increased with a new key")
>>>> >     @post(lambda self, key: old(self.get(key)) is not None and 
>>>> > old(self.length()) == self.length(),
>>>> >           "length stable with an existing key")
>>>> >     def put(self, key: str, value: str) -> None:
>>>> >         ...
>>>> >
>>>> > How could we possible implement this "old" function?
>>>> >
>>>> > Here is my suggestion. I'd introduce a decorator "before" that would 
>>>> > allow you to store whatever values in a dictionary object "old" (i.e. an 
>>>> > object whose properties correspond to the key/value pairs). The "old" is 
>>>> > then passed to the condition. Here is it in code:
>>>> >
>>>> > # omitted contracts for brevity
>>>> > class NovelDict:
>>>> >     def length(self)->int:
>>>> >         ...
>>>> >
>>>> >     # omitted contracts for brevity
>>>> >     def get(self, key: str) -> Optional[str]:
>>>> >         ...
>>>> >
>>>> >     @before(lambda self, key: {"length": self.length(), "get": 
>>>> > self.get(key)})
>>>> >     @post(lambda self, key, value: self.get(key) == value)
>>>> >     @post(lambda self, key, old: old.get is None and old.length + 1 == 
>>>> > self.length(),
>>>> >           "length increased with a new key")
>>>> >     @post(lambda self, key, old: old.get is not None and old.length == 
>>>> > self.length(),
>>>> >           "length stable with an existing key")
>>>> >     def put(self, key: str, value: str) -> None:
>>>> >         ...
>>>> >
>>>> > The linter would statically check that all attributes accessed in "old" 
>>>> > have to be defined in the decorator "before" so that attribute errors 
>>>> > would be caught early. The current implementation of the linter is fast 
>>>> > enough to be run at save time so such errors should usually not happen 
>>>> > with a properly set IDE.
>>>> >
>>>> > "before" decorator would also have "enabled" property, so that you can 
>>>> > turn it off (e.g., if you only want to run a postcondition in testing). 
>>>> > The "before" decorators can be stacked so that you can also have a more 
>>>> > fine-grained control when each one of them is running (some during test, 
>>>> > some during test and in production). The linter would enforce that 
>>>> > before's "enabled" is a disjunction of all the "enabled"'s of the 
>>>> > corresponding postconditions where the old value appears.
>>>> >
>>>> > Is this a sane approach to "old" values? Any alternative approach you 
>>>> > would prefer? What about better naming? Is "before" a confusing name?
>>>> 
>>>> The dict can be splatted into the postconditions, so that no special
>>>> name is required. This would require either that the lambdas handle
>>>> **kws, or that their caller inspect them to see what names they take.
>>>> Perhaps add a function to functools which only passes kwargs that fit.
>>>> Then the precondition mechanism can pass `self`, `key`, and `value` as
>>>> kwargs instead of args.
>>>> 
>>>> For functions that have *args and **kwargs, it may be necessary to
>>>> pass them to the conditions as args and kwargs instead.
>>>> 
>>>> The name "before" is a confusing name. It's not just something that
>>>> happens before. It's really a pre-`let`, adding names to the scope of
>>>> things after it, but with values taken before the function call. Based
>>>> on that description, other possible names are `prelet`, `letbefore`,
>>>> `predef`, `defpre`, `beforescope`. Better a name that is clearly
>>>> confusing than one that is obvious but misleading.
>>>> 
>>>> By the way, should the first postcondition be `self.get(key) is
>>>> value`, checking for identity rather than equality?
>>> _______________________________________________
>>> Python-ideas mailing list
>>> Python-ideas@python.org
>>> https://mail.python.org/mailman/listinfo/python-ideas
>>> Code of Conduct: http://python.org/psf/codeofconduct/
_______________________________________________
Python-ideas mailing list
Python-ideas@python.org
https://mail.python.org/mailman/listinfo/python-ideas
Code of Conduct: http://python.org/psf/codeofconduct/

Reply via email to