Install Theme

evolution-is-just-a-theorem:

Reblog with your most controversial coding opinion.

Basically all discussions of software design and methodology make a fundamental mistake by treating a piece of software as “a black box that implements some desired behaviors,” noting that the list of behaviors never uniquely pins down the design of the black box, and then trying to fill the gap with generic principles of “good design” and “good practices” that are supposed to apply generically across all black boxes.

IMO it should be considered actively desirable for your internal representations to match the reality they are trying to model, even when nothing outside of your code will be able to tell (…for now).  It enables you to respond quickly and gracefully to future demands (because these demands come from the real world and inherit structure from it), it makes failures more legible to the user (because the component that failed will look like a part of their existing mental model of the situation), and it will make the code easier to pick up and to maintain because external domain knowledge will automatically double as implementation knowledge (”where is $THING in the code? oh, there it is” vs. “where is $THING in the code? well, actually it’s distributed over these five things plus half of this other one”).

Any decent list of good practices ends up implicitly encouraging this, but in an awkward and over-generalized way, where “imagine you had to change something that can vary independently in the real world, wouldn’t it be nice if that was really easy” motivates conclusions like “everything should always be done in one place by a tiny encapsulated unit with only that job that knows nothing about anything else” instead of “the code should decouple things that are decoupled in reality.”


If you want something more down-to-earth: pipenv is pointless.  It’s basically a version of pip freeze with an extremely slow and useless (see fn2 here) reproducibility feature, combined with a strictly worse version of virtualenvwrapper.

I can’t imagine I’m the first person to have this idea, but: I’m starting to think that, at least with currently existing technology, it’s always a bad idea to think of your software as an “agent” instead of a “tool.”  And, on the flipside, that many (if not all) useless “agents” could be repurposed into useful “tools.”

The distinction I’m making is between two types of software that try to save you time:

A “tool” saves you time by giving you a (literal or figurative) “button” you can press (could be a command line string, whatever) which will trigger a constrained, broadly transparent, broadly predictable (if perhaps quite complicated!) string of automated actions, which you then won’t have to do yourself.

An “agent” instead tries to do some task for you entirely on its own, up to and including deciding when the “button” should be pushed, and then pushing it.  A tool is never used by default, only when you push its button (perhaps by implication, through pushing the button of a higher-level tool).  Agents push their own buttons, whether you want them to or not, and usually the only way to push back against an agent that is behaving annoyingly or uselessly is to turn it off entirely, depriving yourself of all its functionality even when you do want it.

Agents and tools are often quite similar in their actual capabilities, and in what they do after the button is pushed.  But agents are more opaque to the user, often on purpose (to make them seem “smart” and/or effortless to operate).  And the model of time-saving is subtly, but importantly different in the two cases.

A tool wants to multiply your capabilities.  It wants to let you do more in any given ten minutes by giving you a button that will instantly do something that used to take you (say) five minutes.  Now, every time you want to do that thing, it’s as if you’re getting five extra minutes for free.

An agent wants to replace you.  It wants to let you do more in any given ten minutes by making a conceptual breakdown of your work, completely automating a subset of it, and posing as a new coworker who handles that entire subset so you don’t have to.

Why are agents worse than tools?  First, because we’re really good at making computers do complicated-yet-constrained tasks, but we’re really bad at making them anticipate our needs.  “Deciding when to push the button” is usually very hard to automate – and strangely pointless to automate, too, when it takes all of half a second to push one.

And second, because the space of conceivable, useful tools is much larger.  As long as you leave room for some human volition, you can have all sorts of great ideas that can multiply human productivity by orders of magnitude.  If you insist at the outset that the human is going to be cut off from the system, then you won’t even think about any of the ideas that necessarily involve a human participant, even if they’re great ones.

Think about compilers and interpreters.  Once upon a time, you (more or less) had to write byte code if you wanted to program a computer.  These days, the journey from “concept in your head” to “code you can run” takes orders of magnitude less time, because we now have tools that will automatically translate much higher-level descriptions into byte code.  After their buttons are pressed, these tools do all sorts of very complicated, very fancy things entirely on their own, in a way that is quite “smart” if you want to frame it that way – it seems to me that GCC and LLVM are as worthy of the title “AI” as anything on the market these days.  But these things don’t pose as coworkers or assistants, they only run when you tell them to, and they limit their behavior to an easily comprehensible scope.

Imagine if people in the days of byte code had thought about “automated programming” in the agent model instead of the tool model, with the goal of entirely replacing parts of the programmer’s workflow.  Would they have invented programming languages at all?  “Why translate into byte code from a language humans find easier, when the goal is to write code without the human needing to lift a finger?”

Compilers and interpreters are complex tools, and they are wonderful.  Are there any agents that are similarly wonderful?  When a software feature is marketed as being “smart” (which seems to be a term of art for “agent”), doesn’t that usually mean “useless”?

(The phone app that came with my sleep tracker has a feature called “Smart Coach.”  Each day, in semi-random fashion, it gives me a new piece of advice based somehow on my recent data.  The software capabilities behind the advice look like they might well be very useful to me, but they have been rendered useless by wrapping them in an agent.

“Smart Coach noticed you tend to sleep more on weekends.”  Okay – but how much, and over what time period, and is there any reason you told me that just now?  An ability to see my data averaged by day-of-week (which the app is already computing, apparently) would be so much more useful than Smart Coach.  “Smart Coach noticed you got less deep sleep last night than usual for your age cohort.”  Okay, so how much deep sleep does my age cohort get, and just how much less am I getting?  The developers put lots of interesting information at my fingertips, and then systematically hid it from me, because they wanted an agent.)

light-rook replied to your post “Does anyone know why scikit-learn is (pervasively) designed to raise…”
It’s a wrapper for stuff written in c, right? So they’re trying to prevent segfaults which are less readable

Not C per se, just sometimes cython.  I was thinking about this sort of thing, but I just don’t buy it as an objection – even if there are places where things get low level, the checks should only come right before those parts (they are much more frequent than that).

And, more importantly, even if the constraint is “we need data that’s exactly like this, at this point,” there are ways for developers to respond to that constraint other than “insist the user of our API satisfy it themselves, and break if they happen not to.”  Like, you can surround your low level code with Python/numpy stuff that intelligently processes it into the required form, to get the behavior expected by Python/numpy users.  This can be frustrating to write, sure – it’s similar to what I’m doing to get around sklearn – which I guess might be why it isn’t there?

But also it’s just less frustrating to see this error the first time than scratch your head if the assumptions are violated in some subtle way

That’s what warnings are for!

eaglesnotforks replied to your post “Does anyone know why scikit-learn is (pervasively) designed to raise…”

I don’t know, but I hate it and it’s made my past month exceedingly frustrating. So…solidarity, I guess.

At this point I’m half-seriously wondering if it would save more time just to make a fork of sklearn that turns all the frustrating exceptions into warnings, rather than spending hours and hours on workarounds.

Of course it wouldn’t get updated (or rather, I wouldn’t want to deal with the headache of merging updates into it and de-exceptionizing them), but I’m not sure anything in a future sklearn update is going to have much value for me?  It already does a whole bunch of more-or-less standard stuff and that’s all I want out of it.

Does anyone know why scikit-learn is (pervasively) designed to raise exceptions whenever anything is a little unusual in a way that could maybe cause problems down the line, even if it’s not causing problems now?

Like, it has all these checks for things like whether data has the right type(s), or the right number or dimensions, or the right range of values in it, or whatever – where “right” usually means “necessary for doing something that is commonly done with this value at some point.”  But instead of applying these checks only when it’s about to do something that requires those properties, it applies them continually, again and again, inside every tiny moving part, even parts that have no reason of their own to care about the properties.  And instead of raising warnings, it raises exceptions, i.e. it just stops and refuses to continue.

This requires some pretty ridiculous workarounds if you want to do a lot of things that would be straightforward in other big Python libraries or just Python itself.  Is there some secret good reason it’s built this way?

In the Kingdom of Javaland, where King Java rules with a silicon fist, people aren’t allowed to think the way you and I do. In Javaland, you see, nouns are very important, by order of the King himself. Nouns are the most important citizens in the Kingdom. They parade around looking distinguished in their showy finery, which is provided by the Adjectives, who are quite relieved at their lot in life. The Adjectives are nowhere near as high-class as the Nouns, but they consider themselves quite lucky that they weren’t born Verbs.

Because the Verb citizens in this Kingdom have it very, very bad.

In Javaland, by King Java’s royal decree, Verbs are owned by Nouns. But they’re not mere pets; no, Verbs in Javaland perform all the chores and manual labor in the entire kingdom. They are, in effect, the kingdom’s slaves, or at very least the serfs and indentured servants. The residents of Javaland are quite content with this situation, and are indeed scarcely aware that things could be any different.

Verbs in Javaland are responsible for all the work, but as they are held in contempt by all, no Verb is ever permitted to wander about freely. If a Verb is to be seen in public at all, it must be escorted at all times by a Noun.

Of course “escort”, being a Verb itself, is hardly allowed to run around naked; one must procure a VerbEscorter to facilitate the escorting. But what about “procure” and “facilitate?” As it happens, Facilitators and Procurers are both rather important Nouns whose job is is the chaperonement of the lowly Verbs “facilitate” and “procure”, via Facilitation and Procurement, respectively.

The King, consulting with the Sun God on the matter, has at times threatened to banish entirely all Verbs from the Kingdom of Java. If this should ever to come to pass, the inhabitants would surely need at least one Verb to do all the chores, and the King, who possesses a rather cruel sense of humor, has indicated that his choice would be most assuredly be “execute”.

The Verb “execute”, and its synonymous cousins “run”, “start”, “go”, “justDoIt”, “makeItSo”, and the like, can perform the work of any other Verb by replacing it with an appropriate Executioner and a call to execute(). Need to wait? Waiter.execute(). Brush your teeth? ToothBrusher(myTeeth).go(). Take out the garbage? TrashDisposalPlanExecutor.doIt(). No Verb is safe; all can be replaced by a Noun on the run.

In the more patriotic corners of Javaland, the Nouns have entirely ousted the Verbs. It may appear to casual inspection that there are still Verbs here and there, tilling the fields and emptying the chamber pots. But if one looks more closely, the secret is soon revealed: Nouns can rename their execute() Verb after themselves without changing its character in the slightest. When you observe the FieldTiller till(), the ChamberPotEmptier empty(), or the RegistrationManager register(), what you’re really seeing is one of the evil King’s army of executioners, masked in the clothes of its owner Noun.

(Steve Yegge, “Execution in the Kingdom of Nouns”)

theaudientvoid:

nostalgebraist:

This is probably getting into some programmer holy war bullshit and I’m sure there are like 10000 Usenet posts explaining why I am capital-w Wrong, but

I don’t like the idea of computations on NaN values returning results that might have come from actual numbers.

The idea of a NaN (as I understand it) is “there was supposed to be a number here, but for some reason we don’t have one.”  Could be an operation with a mathematically undefined result; could be an operation with a defined result but not in the number system you’re using (like sqrt(-1) when you are representing real or rational numbers); could be a missing value in real-world data.

Doesn’t matter, because all these should be treated the same way: by doing something numbers would not do (either through an error or returning NaN), so that the user does not get a result that could logically follow for some actual numbers but might not logically follow for whatever numbers the NaNs were “supposed to be” (if any).

Compare NaN to anything else and get False?  Nope, for all you know the number that was supposed to be there equaled the other guy.

Compare NaN to NaN and get False?  This is legendarily confusing, which would perhaps be defensible if it were a downstream consequence of the confusingness of NaN itself – but no, for all you know those numbers were supposed to be equal.  (Tell me sqrt(-1) is not equal to sqrt(-1), I dare you)

Convert NaN to boolean and get True, because you have to equal zero to be False, and (NaN == 0) == False?  How many times do I have to tell you there isn’t a number there, we don’t know what it is, we don’t know what ought to happen when we put it through your function

No no wait, to be fair this doesn’t always happen!!  You could be in JavaScript, where if convert NaN to boolean, you get … False, because NaN is “falsy.”  No it isn’t, it isn’t “falsy,” it isn’t “truthy,” it isn’t anything-y, we don’t know what it is okay

Stata thinks missing values are smaller than any numeric value.

oh my god

(via theaudientvoid)

This is probably getting into some programmer holy war bullshit and I’m sure there are like 10000 Usenet posts explaining why I am capital-w Wrong, but

I don’t like the idea of computations on NaN values returning results that might have come from actual numbers.

The idea of a NaN (as I understand it) is “there was supposed to be a number here, but for some reason we don’t have one.”  Could be an operation with a mathematically undefined result; could be an operation with a defined result but not in the number system you’re using (like sqrt(-1) when you are representing real or rational numbers); could be a missing value in real-world data.

Doesn’t matter, because all these should be treated the same way: by doing something numbers would not do (either through an error or returning NaN), so that the user does not get a result that could logically follow for some actual numbers but might not logically follow for whatever numbers the NaNs were “supposed to be” (if any).

Compare NaN to anything else and get False?  Nope, for all you know the number that was supposed to be there equaled the other guy.

Compare NaN to NaN and get False?  This is legendarily confusing, which would perhaps be defensible if it were a downstream consequence of the confusingness of NaN itself – but no, for all you know those numbers were supposed to be equal.  (Tell me sqrt(-1) is not equal to sqrt(-1), I dare you)

Convert NaN to boolean and get True, because you have to equal zero to be False, and (NaN == 0) == False?  How many times do I have to tell you there isn’t a number there, we don’t know what it is, we don’t know what ought to happen when we put it through your function

No no wait, to be fair this doesn’t always happen!!  You could be in JavaScript, where if convert NaN to boolean, you get … False, because NaN is “falsy.”  No it isn’t, it isn’t “falsy,” it isn’t “truthy,” it isn’t anything-y, we don’t know what it is okay

My attempt to install tensorflow-fold thus far:

(1) follow steps on the repo’s installation page, which only gives instructions for binary installation, and links to the file for a specific build rather than a general binaries page

(2) try “import tensorflow-fold,” and get

undefined symbol: _ZN10tensorflow6tensor5SplitERKNS_6TensorERKNS_3gtl10ArraySliceIxEE

(3) google, find a thread where someone else had the problem, it turns out their version of tensorflow was too new to be compatible with the binary, they are advised to downgrade

(4) downgrade to tensorflow 1.0, now fold works but the activation function I used in my code isn’t in tensorflow 1.0

(5) google search with terms like “tensorflow fold install,” after clicking on several Q&A threads I click on something that turns out to be the fold repo’s “how to install from source” page, which apparently exists although there weren’t any links to it on the other documentation pages

(6) installation instructions tell me I need something called “Bazel” (”Google’s own build tool, now publicly available in Beta”), so I install that

(7) apparently I am supposed to use Bazel to … make a pip wheel for fold, so I can install it with pip locally?  like, it’s not a registered PyPI package, but it still needs to get pip installed?

(8) 

You also need to build a pip wheel for TensorFlow. Unfortuately this means we need to rebuild all of TensorFlow, due to known Bazel limitations (#1248).

(9) the machine is currently building tensorflow from source, something it has never had to do before, because it just had a … pip wheel … for it … … 

Like I know this is research code, I’m not complaining, it’s just been such a weird ride, man

nightpool:

nostalgebraist:

[cutting because dear god did this get long fast]

Thanks, this is also helpful.

I am wary of composability as an ideal, for reasons I stated here.  As you get more and more objects and methods involved in performing a given task, you’re allowing the actual code that performs that task to be spread more and more widely across the code base, and requiring the reader to trace back more steps in order to have full comprehension of what any line is actually doing.  And if you want to follow what the code is doing closely, you have to jump around nonlinearly more and more.  @gattsuru​ used the phrase “ravioli code” for this, and Googling it, it seems like other people have made the same complaint, e.g.:

I should have noted why I think that Ravioli Code is a bad thing (and hence that those who think it is good style are doing a disservice to their trade). The problem is that it tends to lead to functions (methods, etc.) without true coherence, and it often leaves the code to implement even something fairly simple scattered over a very large number of functions. Anyone having to maintain the code has to understand how all the calls between all the bits work, recreating almost all the badness of Spaghetti Code except with function calls instead of GOTO. It is far better to ensure that each function has a strong consistent description (e.g. “this function frobnicates the foobar”, which you should attach to the function somehow - in C, by a comment because there’s no stronger metadata scheme) rather than splitting it up into smaller pieces (“stage 1 of preparing to frobnicate the foo part of the foobar”, etc.) with less coherence. The principal reason why this is better is precisely that it makes the code easier overall to understand.

Of course, it makes sense to group things together if they actually tend to get re-used together (like having x and y coordinates be attributes of the same object), or to put some code in a function of its own if it forms a distinct conceptual block.  But my understanding is that the “ifs” in the previous sentence should be read as “if-and-only-ifs”; the point of abstractions like functions and objects is to group some things together as well as to separate them from other things, in order to exploit actual regularities of the task or conceptual domain in your code.

If, instead, you try to make everything as modular as possible all the time, you’re no longer making useful “these form a group, apart from these other things” distinctions; you’re just splitting everything up as finely as it possibly can be.

The first thing is that it’s all about the abstractions you can make and how leaky those abstractions are. The idea of ISP is to have strong boundaries at the interface level so you don’t have to worry too much about what the code on the “other side” is doing—obviously this is easier in a strongly typed language, but I don’t think there’s ever going to be any confusion about what section.students() returns, for example.

the quoted post was talking about just functions, not objects, so I don’t know how germane it is here. if you don’t have objects, you don’t have interfaces, and you don’t have interface segregation. (modules don’t really count, imo. you need objects)

The other thing that Sandi tried to nail into my head was that, when you’re designing code, you can’t predict the future. This means you should never write code that is more complicated then what you need to solve the task in front of you today. Never refactor your code until you have a reason to. And I don’t think anyone here is saying “you should split everything up as finely as it can be split”. I’m saying “split things up such that it makes your code declarative, readable, and understandable”.

In re: “you’re only going to be able to say “I understand what this is doing insofar as I understand exactly what all of these abstractions do” from your other post: that’s…. how coding works? you’re never going to understand the “full system”. It’s too big for that. I don’t know anything about transistor design, for example. I know a little bit about assemblers and objcode, but if I had to think about such things when I was writing Ruby, I’d go absolutely crazy. And so on up the stack. I know a fair bit of SQL by now, but I didn’t know any of it when I started writing ActiveRecord code (and active record is pretty bare metal as far as ORMs go!), and that was okay. I definitely didn’t know the full codebase when I joined my job, but I was able to understand enough of the bits I was looking at to push two bugfixes on my first day, in a 140k loc app. Abstractions make the world go round. (also, at this point, we can also get into unit testing and similar ways of assuring yourself of code correctness that don’t rely on understanding the whole world)

The quoted post mentioned methods, so the poster is definitely not assuming we don’t have objects.  And Googling around, people do seem to use the term “ravioli code” in a germane way, e.g. (from these slides)

image

Although some people use it as a laudatory rather than derogatory term (as here, although that also contains “Risotto code” for “a confusing lot of very, very little objects all interacting together”).

Uh, pasta aside, I just mean to say that this seems like a topic of actual disagreement, and not just me misunderstanding something.  See also gattsuru’s post.

Re “Abstractions make the world go round”: there’s a distinction between abstractions everyone can already be trusted to understand, and abstractions we’ve introduced that have to be newly learned when someone reads the code.  There are lots of references to abstractions in your first block of code, but they’re Python and Beautiful Soup abstractions, and I was already familiar with them when I read it.  But in your second block of code, I of course was not familiar with with the “Section” and “Student” abstractions.  Becoming familiar with them – in the way that I am familiar with Python and Beautiful Soup – would require figuring out things like the following:

  1. the Student objects returned by section.students() actually originate from scraping the page for reviews; we don’t actually have a unique object for each student, and if the same student writes multiple reviews for some reason, we’ll have more than one Student corresponding to them
  2. student.reviews() returns a list of strings, but the identically named section.reviews() returns a dict of id/list-of-string pairs.  On the other hand, section.students() and section.reviews_for() return lists, not dicts.  There is no way (afaik) to infer whether we are getting a list or a dict from the method names.
  3. whereas student.reviews() gives you a list of strings, and section.reviews() gives you a dict of id/list-of-string pairs, section.reviews_for(student) gives you a list of Student objects.  We have three (!) different data structures these two classes can give us if we innocuously ask them for things called “reviews.”

(3 is true if section.reviews_for does the thing I think it does, but I’m not sure?  I think “for s in students()” should be “for s in self.students()”, in which case we indeed do get a list of Students.)

I don’t mean to pick on the specific choices you made in your second block, which after you you said was just a sketch (and may be more intuitive to people who know things I don’t).  But however we design the second block, we have to make a lot of weighty choices about what we are putting behind each abstract wall.  What are we calling a “review” and a “student”?  What will the reader expect these things to be?

We are also making these kinds of choices when we name variables in imperative programming, but if we declare variables right before we use them, the reader can hold the definitions in their short-term memory, like they’re reading a book.  In your first block, after I read the first line I know that by “student” we actually mean “a thing we get out of page.select,” and if I ever forget, I can just say “hey, what were we doing again?” and look up a few lines.  I never have to wonder what the author of this code thought a “student” should “transparently” be.

(This can all be made easier with docstrings, but in this example that amounts to writing a comment explaining literally every line of code you write, which is clearly overkill.  The code explains itself, if you put it all in close proximity.)

I realize I’m being obnoxious here, and possibly fixating on the example at the expense of seeing the broader point.  I do see the advantages of lots of modularity for large codebases worked on by many people.  But for a simple scraping task like the one in your example, I don’t understand how the benefits outweigh the costs.

I also admit I’m someone who doesn’t do enough OOP even when it’s obviously useful for the task at hand, so I may not have developed the right habits yet.