Read this: http://jeffq.com/blog/the-ethernet-pause-frame/
Fascinating.
A world of interconnected devices in which we place a kind of implicit trust. There's little visibility for ordinary consumers. It takes a skilled specialist to determine that there are flaws in a product.
It's not that the system is "flaky."
It's that this combination of components each with unexpected edge-case behavior is actually broken.
Rants on the daily grind of building software. This has been moved to https://slott56.github.io. Fix your bookmarks.
Moved
Moved. See https://slott56.github.io. All new content goes to the new site. This is a legacy, and will likely be dropped five years after the last post in Jan 2023.
Tuesday, August 30, 2016
Tuesday, August 23, 2016
On Generator Functions, Yield and Return
Here's the question, lightly edited to remove the garbage. (Sometimes I'm charitable and call it "rambling". Today, I'm not feeling charitable about the garbage writing style filled with strange assumptions instead of questions.)
A definition with a yield statement becomes an iterable generator of (potentially) multiple values. The return statement changes its behavior slightly. It no longer defines the one (and only) return value. In a generator function (one that has a yield) the return statement can be thought of as if it raised the StopIteration exception as a way to exit from the generator.
As can be seen in the example above, both statements are in one function. They both work to provide expected semantics.
The code which gets an error is this:
The "why?" question is should -- perhaps -- be obvious at this point. The return raises an exception; it doesn't provide a value.
The topic, however, remains troubling. The phrase "have both a yield and a return" is bothersome because it fails to recognize that the yield statement has a special role. The yield statement transforms the semantics of the function to make it into a different object with similar syntax.
It's not a matter of having them "both". It's matter of having a return in a generator. This is an entirely separate and trivial-to-answer question.
There are times when programming language semantics are confusing. For example, the ++ operator in C is confusing. Nothing can be done about that. The original definition was tied to the PDP-11 machine instructions. Since then... Well.... Aspects of the generated code are formally undefined. Many languages have one or more places where the semantics are "undefined" or only defined by example.
This is not one of those times.
Here's the real problem I have with the garbage aspect of the email.
If you bring personal baggage to the conversation -- i.e., assumptions based on a comparison between some other language and Python -- confusion will erupt all over the place. Languages are different. Concepts don't map from language to language very well. Yes, there are simple abstract principles which have different concrete realizations in different languages. But among the various concrete realizations, there may not be a simple mapping.
It's essential to discard all knowledge of all previous favorite programming languages when learning a new language.
I'll repeat that for the author of the email.
You won't get anything.
In this specific case, the notion of "function" in Python is expanded to include two superficially similar things. The syntax is nearly identical. But the behaviors are remarkably different. It's essential to grasp the idea that the two things are different, and can't be casually lumped together as "function/iterator".
The crux of the email appears to be a failure to get the Python language rules in a profound way.
someone asked if you could have both a yield and a return in the same ... function/iterator. There was debate and the senior people said, let's actually write code. They wrote code and proved that couldn't have both a yield and a return in the same ... function/iterator. ....
The meeting moved on w/out anyone asking the why question. Why doesn't it make sense to have both a yield and a return. ...
The impact of the yield statement can be confusing. Writing code to mess around with it was somehow unhelpful. And the shocking "proved that couldn't have both a yield and a return in the same ... function" is a serious problem.
(Or a seriously incorrect summary of the conversation; a very real possibility considering the garbage-encrusted email. Or a sign that Python 3 isn't widely-enough used and the emil omitted this essential fact. And yes, I'm being overly sensitive to the garbage. But there's a better way to come to grips with reality and it involves asking questions and parsing details instead of repeating assumptions and writing garbage.)
(Or a seriously incorrect summary of the conversation; a very real possibility considering the garbage-encrusted email. Or a sign that Python 3 isn't widely-enough used and the emil omitted this essential fact. And yes, I'm being overly sensitive to the garbage. But there's a better way to come to grips with reality and it involves asking questions and parsing details instead of repeating assumptions and writing garbage.)
An example
>>> def silly(n, stop=None): for i in range(n): if i == stop: return yield i >>> list(silly(5)) [0, 1, 2, 3, 4] >>> list(silly(5, stop=3)) [0, 1, 2]
This works in both Python 3.5.1 and 2.7.10.
Some discussion
A definition with no yield is a conventional function: the parameters from some domain are mapped to a return value in some range. Each mapping is a single evaluation of the function with concrete argument values.A definition with a yield statement becomes an iterable generator of (potentially) multiple values. The return statement changes its behavior slightly. It no longer defines the one (and only) return value. In a generator function (one that has a yield) the return statement can be thought of as if it raised the StopIteration exception as a way to exit from the generator.
As can be seen in the example above, both statements are in one function. They both work to provide expected semantics.
The code which gets an error is this:
>>> def silly(n, stop=3): ... for i in range(n): ... if i == step: return "boom!" ... yield i
The "why?" question is should -- perhaps -- be obvious at this point. The return raises an exception; it doesn't provide a value.
The topic, however, remains troubling. The phrase "have both a yield and a return" is bothersome because it fails to recognize that the yield statement has a special role. The yield statement transforms the semantics of the function to make it into a different object with similar syntax.
It's not a matter of having them "both". It's matter of having a return in a generator. This is an entirely separate and trivial-to-answer question.
A Long Useless Rant
The email seems to contain an implicit assumption. It's the notion that programming language semantics are subtle and slippery things. And even "senior people" can't get it right. Because all programming languages (other then the email sender's personal favorite) are inherently confusing. The confusion cannot be avoided.There are times when programming language semantics are confusing. For example, the ++ operator in C is confusing. Nothing can be done about that. The original definition was tied to the PDP-11 machine instructions. Since then... Well.... Aspects of the generated code are formally undefined. Many languages have one or more places where the semantics are "undefined" or only defined by example.
This is not one of those times.
Here's the real problem I have with the garbage aspect of the email.
If you bring personal baggage to the conversation -- i.e., assumptions based on a comparison between some other language and Python -- confusion will erupt all over the place. Languages are different. Concepts don't map from language to language very well. Yes, there are simple abstract principles which have different concrete realizations in different languages. But among the various concrete realizations, there may not be a simple mapping.
It's essential to discard all knowledge of all previous favorite programming languages when learning a new language.
I'll repeat that for the author of the email.
Don't Go To The Well With A Full Bucket.
You won't get anything.
In this specific case, the notion of "function" in Python is expanded to include two superficially similar things. The syntax is nearly identical. But the behaviors are remarkably different. It's essential to grasp the idea that the two things are different, and can't be casually lumped together as "function/iterator".
The crux of the email appears to be a failure to get the Python language rules in a profound way.
Tuesday, August 16, 2016
Twelve Important Design Patterns
Read this: http://12factor.net/
Then. After reading it. Read it again to be sure you've got it. It's dense with best practices.
Now that you've read it, make yourself a Quality Engineering checklist.
I. Codebase: One codebase tracked in revision control, many deploys
II. Dependencies: Explicitly declare and isolate dependencies
III. Config: Store config in the environment
IV. Backing services: Treat backing services as attached resources
V. Build, release, run: Strictly separate build and run stages
VI. Processes: Execute the app as one or more stateless processes
VII. Port binding: Export services via port binding
VIII. Concurrency: Scale out via the process model
IX. Disposability: Maximize robustness with fast startup and graceful shutdown
X. Dev/prod parity: Keep development, staging, and production as similar as possible
XI. Logs: Treat logs as event streams
XII. Admin processes: Run admin/management tasks as one-off processes
If your app doesn't follow all of these patterns, you've got technical debt to work off. Start by posting the debt remediation stories in Jira (or whatever you're using.)
I've got config issues left, right, and center. Numerous assumptions include the URL's for RESTful services on which my RESTful services rely: this is not good.
Some of these things, however, are a done deed in the Python/Flask world with no real thinking required.
Other things require some care. And the config is something that I've really got to fix.
Then. After reading it. Read it again to be sure you've got it. It's dense with best practices.
Now that you've read it, make yourself a Quality Engineering checklist.
I. Codebase: One codebase tracked in revision control, many deploys
II. Dependencies: Explicitly declare and isolate dependencies
III. Config: Store config in the environment
IV. Backing services: Treat backing services as attached resources
V. Build, release, run: Strictly separate build and run stages
VI. Processes: Execute the app as one or more stateless processes
VII. Port binding: Export services via port binding
VIII. Concurrency: Scale out via the process model
IX. Disposability: Maximize robustness with fast startup and graceful shutdown
X. Dev/prod parity: Keep development, staging, and production as similar as possible
XI. Logs: Treat logs as event streams
XII. Admin processes: Run admin/management tasks as one-off processes
If your app doesn't follow all of these patterns, you've got technical debt to work off. Start by posting the debt remediation stories in Jira (or whatever you're using.)
I've got config issues left, right, and center. Numerous assumptions include the URL's for RESTful services on which my RESTful services rely: this is not good.
Some of these things, however, are a done deed in the Python/Flask world with no real thinking required.
- Build, release, run - done
- Processes - done
- Port binding - done
- Disposability - done
Other things require some care. And the config is something that I've really got to fix.
Tuesday, August 9, 2016
That Feeling When... You're reading your own documentation because it's useful and (mostly) correct
I'm looking at code (as a man does) and I can't remember if there's a class that does X. There's a lot of code. I wrote almost all of it. And -- maybe it's the gin -- but I just can't recall if there's an X. It seems like there should be.
Scan. Scan. Scroll. Scroll.
Read. Read.
Wait!
I have a pretty good gh-pages branch for this. Sphinx-based. Mostly up-to-date. Let's look there.
Ahhh. So much nicer than scrolling through code. Indexes work.
This whole "documentation" thing is pretty cool. Now I'm actually happy that other people guilted me into doing it.
Scan. Scan. Scroll. Scroll.
Read. Read.
Wait!
I have a pretty good gh-pages branch for this. Sphinx-based. Mostly up-to-date. Let's look there.
Ahhh. So much nicer than scrolling through code. Indexes work.
This whole "documentation" thing is pretty cool. Now I'm actually happy that other people guilted me into doing it.
Tuesday, August 2, 2016
Lamenting the Death of Object-Oriented Programming. (Sigh) Again?
See Goodbye, Object Oriented Programming.
I don't want to say that the entire article is bunk. It's not. It raises a few good points. Points which I thought were pretty well known.
What's aggravating is that this lamentation is overly broad. It treats all languages as if they're Java or C++. That's not true, and as a consequence, the article is less useful than it could be.
Banana Monkey Jungle Problem. Only true if you are sadly mistaken about the unit of reuse. The class as unit of reuse -- across projects -- is false, has been false, and will always be false. The idea of class inheritance for reuse makes perfect sense. Sharing individual classes between projects has never (as far as I know) been a promise of OO programming. Maybe I read the wrong books and missed that promise.
The Triangle Problem. Isn't actually a problem. Python has a defined method resolution order.
The Fragile Base Class Problem. This points out the well known issue with having concrete classes depend on other concrete classes. The SOLID design principles suggest concrete classes should depend on abstractions. Abstractions do not suffer (as much) from the fragile base class problem.
The Hierarchy Problem. I guess the idea that the real world is multi-dimensional can be confusing. If everything has to be force-fit into single inheritance, this would create the hierarchy problem. If we allow multiple inheritance, this problem evaporates.
The Reference Problem. Even C++ has "smart" pointer packages. Java has garbage collection. Python does reference counting. This is only a problem if you go out of your way to deal with pointers in a primitive way.
The part on Polymorphism didn't make any sense. There didn't seem to be a tidy problem. Just a confusingly vague statement that "Interfaces will give you [polymorphism?]. And without all of the baggage of OO". I don't get how interfaces are necessary without the baggage of OO. So, I can't really try to refute this.
In the long run, I guess this was a way to introduce some of the benefits of a functional approach. I'm not sure that this kind of criticism of object-oriented programming is very helpful. It doesn't apply to all OO languages, so it's misleading at best. (At worst, it's simply wrong.)
I think these problems are interesting and can be used to show the benefits of functional programming. But without the actual functional programming examples, this isn't very useful.
I don't want to say that the entire article is bunk. It's not. It raises a few good points. Points which I thought were pretty well known.
What's aggravating is that this lamentation is overly broad. It treats all languages as if they're Java or C++. That's not true, and as a consequence, the article is less useful than it could be.
Banana Monkey Jungle Problem. Only true if you are sadly mistaken about the unit of reuse. The class as unit of reuse -- across projects -- is false, has been false, and will always be false. The idea of class inheritance for reuse makes perfect sense. Sharing individual classes between projects has never (as far as I know) been a promise of OO programming. Maybe I read the wrong books and missed that promise.
The Triangle Problem. Isn't actually a problem. Python has a defined method resolution order.
The Fragile Base Class Problem. This points out the well known issue with having concrete classes depend on other concrete classes. The SOLID design principles suggest concrete classes should depend on abstractions. Abstractions do not suffer (as much) from the fragile base class problem.
The Hierarchy Problem. I guess the idea that the real world is multi-dimensional can be confusing. If everything has to be force-fit into single inheritance, this would create the hierarchy problem. If we allow multiple inheritance, this problem evaporates.
The Reference Problem. Even C++ has "smart" pointer packages. Java has garbage collection. Python does reference counting. This is only a problem if you go out of your way to deal with pointers in a primitive way.
The part on Polymorphism didn't make any sense. There didn't seem to be a tidy problem. Just a confusingly vague statement that "Interfaces will give you [polymorphism?]. And without all of the baggage of OO". I don't get how interfaces are necessary without the baggage of OO. So, I can't really try to refute this.
In the long run, I guess this was a way to introduce some of the benefits of a functional approach. I'm not sure that this kind of criticism of object-oriented programming is very helpful. It doesn't apply to all OO languages, so it's misleading at best. (At worst, it's simply wrong.)
I think these problems are interesting and can be used to show the benefits of functional programming. But without the actual functional programming examples, this isn't very useful.
Subscribe to:
Posts (Atom)