Beefy Boxes and Bandwidth Generously Provided by pair Networks
P is for Practical
 
PerlMonks  

Re: How to write programs?

by mstone (Deacon)
on May 27, 2005 at 05:52 UTC ( [id://460950]=note: print w/replies, xml ) Need Help??


in reply to How to write programs?

I tend to work declaratively, and backwards.

Broadly speaking, there are two kinds of programming: declarative and imperative. Declarative programming lists a sequence of results, and leaves the reader (or the computer) to work out the operations that produce those results. Imperative programming lists a sequence of operations, and leaves the reader (or the computer) to work out the results. Most of what we think of as 'software' falls under the imperative style, while things like SQL and mathematical proods fall under the declarative style.

Thing is, those two styles link directly to the two questions that are most important to programmers: What? and How? "What does this code do?" is a declarative question. It asks you to identify the result produced by the code, or more generally, to describe the relationship between what goes into the code and what comes out. "How does this code work?" is an imperative question. It asks you to define the procedure that connects what goes in to what comes out.

Now, as I mentioned earlier, most code is written imperatively, but it's a lot easier to design code declaratively.

The trouble with imperative code is that it's easy to get bogged down in the details. It makes you to choose a way of representing your data, often before you know exactly what kind of data you really need to represent. Then it makes you write all sorts of representation-support code just so you can work with the values you want to use as inputs and outputs. With all that furniture in the way, the actual logic of your program.. the stuff that actually handles the work of mapping the inputs to the correct outputs.. can get hard to see.

Choosing a representation early also makes it difficult to change your basic data structures if you decide there's a better way of doing things later on. People will often stick with a representation that doesn't really do what they want, just because they don't want to throw away the code they've already written.

Declarative programming keeps you from having to code up a representation. Instead, you define a collection of types.. sets that contain valid input values, and valid output values.. then you use those to define the relationships between your inputs (the things you have) and your outputs (the things you need).

You define types by listing assertions that will always be true for any item in the set:

  • Positive integers are all.. well.. integers.
  • They're all strictly greater than zero.
  • The distance between any two non-equal positive integers will also be a positive integer.
  • The sum of any two positive integers is also a positive integer.
  • .. and so on.

And the beauty of declarative design is that you don't have to start off with a big long encyclopedia of assertions for each type. You can start with a few very rough ideas, then go back and refine them.. either by making them more specific or adding more assertions to the list.. whenever the new rules become necessary. You're free to evolve your types as the design grows, and you get a better idea of what's actually going on, because you aren't handcuffed to any specific representation.

The idea of building a design backwards harks back to the old programming maxim: "start with the part that produces output."

The output is the part of the program you can see. It's the where you find out whether you got the result you expected or not. If you start by generating output in its simplest and most trivial form, you know at least that much of the program works. Then you can work your way back through the design, adding one layer of complexity at a time, and verifying that the new code actually does what you expect.

That keeps you from creating those really frustrating situations where you've written a whole bunch of code blind, then run into bug after bug after bug when you actually try to run it. It's also a good mood-maintenance trick. It's exhausting to have to write tons of code before you have anything that will actually run. It's much more satisfying to be able to run something every five minutes and see yourself making progress along the way.

The back-to-front approach also lends itself nicely to declarative design, because the first version of any declarative design is basically:

{inputs}-->(program)-->{outputs}

If we start with 'inputs' being 'nothing' and 'outputs' being "hello, world.\n", the imperative version ends up being pretty easy to code. If we want to make things more complicated by saying the program has to write its output to a web browser rather than to STDOUT, we end up doing some more twiddling with "Content-type" headers, HTML formatting, and so on. But the basic principle ends up being the same.

Back-to-front design also helps you decide exactly what inputs you actually need in order to generate the output you want. If we were to refine the design above to use an HTML template that lives in an external file, the new diagram might end up looking like so:

{filename} | +-->(file reader) | +-->{HTML template} | {output message}--+-->(formatter)->{program output}

and at this point, the assertions for the 'HTML template' type require us to think about error handling. If we add a rule that says, "every 'HTML template' must be valid markup," and interpret the diagram above to mean that the 'file reader' part of the program must always return an 'HTML template', then we have to find some way to guarantee valid markup if the file doesn't exist, or the program can't open the file because of permissions problems, and so on. If we really wanted to get finicky about it, we'd even parse and validate the contents of the file if we could read it, just to make sure the file actually contains valid HTML.

So that leads to a further-refined diagram like so:

{filename} + {hardcoded error template} | +-->(file reader) | +-->{HTML template} | {output message}--+-->(formatter)->{program output}

where we assert that 'hardcoded error template' is also a member of 'HTML template'.

Then we'd go to work on the message itself:

{inputs}-->(message generator)-->{output message}

and so on.

The longer you keep refining the diagram, the closer you get to a set of requirements that are trivially easy to program. And once you know exactly which parts of the program need what information, it's much easier to define object classes or data structures that will carry the information where it needs to go. That's the appropriate time to worry about data representation issues, algorithms, and all the imperative stuff.

So.. my basic policy is to start with "What?" and work my way back to "How?".

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://460950]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others pondering the Monastery: (6)
As of 2024-04-25 15:36 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found