?

Log in

Sleepless in the Saddle
20 most recent entries

Date:2010-03-29 15:57
Subject:Showing iTunes track in XMPP presence
Security:Public

Now that you can connect third party clients to my.OnSIP you can take advantage of some of their powerful features. One of my favorites is the ability of Adium to display my currently playing song from iTunes. I get a lot of questions about how I'm doing that and it's quite simple, so here's how you can do it, too:

Requires

  • XMPP hosting
  • Adium

Steps

  1. Start Adium
  2. Connect to your XMPP account
  3. Open the Status menu
  4. Select ♫ iTunes
  5. As iTunes plays songs your status will now update automatically

2 comments | post a comment



Date:2009-01-27 10:32
Subject:Astrology doesn't work for me, either.
Security:Public

Grab the book nearest you. Right now. Turn to page 56. Find the fifth sentence. Post that sentence along with these instructions in your LiveJournal. Don't dig for your favorite book, the coolest, the most intellectual. Use the CLOSEST.

However, he couldn't.

Now Wait for Last Year, Philip K. Dick

post a comment



Date:2008-09-08 20:06
Subject:erlc says TOO CLEVAR 4 YUOS!
Security:Public

The one time I wanted anonymous function syntax, so I could do pattern matching against the enclosing function:

find_option(Option, Default, Options) ->
    case lists:foldl(fun ({Option, Value}, Acc) -> [Value | Acc];
                         (_, Acc) -> Acc
                     end, [], Options) of
        [] -> Default;
        [Val] -> Val
    end.


In other words, I want the Option in the lambda for lists:fold/3 to match the Option passed into find_option/3. Woulda been nice, huh? Unfortunately:

./mod_jnctn.erl:30: Warning: variable 'Option' is unused
./mod_jnctn.erl:30: Warning: variable 'Option' shadowed in 'fun'

post a comment



Date:2008-08-04 00:38
Subject:http://xkcd.com/458/
Security:Public




post a comment



Date:2008-04-04 18:15
Subject:4 4 40
Security:Public

Fuck.

post a comment



Date:2008-02-11 23:48
Subject:4 hours, 18 minutes.
Security:Public

I just saw the railroads on json.org and wondered how hard it'd be to put it in Erlang. Unfortunately they're no t exactly accurate, but that gave me a convenient excuse to avoid a lot of things, too. I was only going to the minimum spec (with few as few false positives as reasonable) according to those diagrams. White space doesn't get eaten properly, for instance, but I don't really care.

I was surprised at how little I still knew of Erlang, and found myself reaching for the documentation for really basic stuff. I obviously need to do this kind of thing more often. I had to abort an early attempt at using gen_fsm once i realized it was completely unsuitable for what I was trying to do.

-module(json).

-export([parse/1]).

parse(String) ->
    parse_value(String).

parse_value([34 | _] = Rest) -> % 34 = $", damn emacs
    parse_string(Rest);
parse_value([H | _] = String)
  when H == $-; H >= $0, H =< $9 ->
    parse_number(String);
parse_value([${ | Rest]) ->
    parse_object(Rest);
parse_value([$[ | Rest]) ->
    parse_array(Rest);
parse_value("true" ++ Rest) ->
    {true, Rest};
parse_value("false" ++ Rest) ->
    {false, Rest};
parse_value("null" ++ Rest) ->
    {null, Rest}.

parse_string([34 | Rest]) ->
    parse_string(Rest, []).

parse_string([34 | Rest], Acc) ->
    {lists:reverse(Acc), Rest};
parse_string([$\\, $b | Rest], Acc) ->
    parse_string(Rest, [$\b | Acc]);
parse_string([$\\, $f | Rest], Acc) ->
    parse_string(Rest, [$\f | Acc]);
parse_string([$\\, $n | Rest], Acc) ->
    parse_string(Rest, [$\n | Acc]);
parse_string([$\\, $r | Rest], Acc) ->
    parse_string(Rest, [$\r | Acc]);
parse_string([$\\, $t | Rest], Acc) ->
    parse_string(Rest, [$\t | Acc]);
parse_string([$\\, $u, H1, H2, H3, H4 | Rest], Acc)
   when ((H1 >= $0 andalso H1 =< $9)
         orelse (H1 >= $a andalso H1 =< $f)
         orelse (H1 >= $A andalso H1 =< $F))
andalso ((H2 >= $0 andalso H2 =< $9)
         orelse (H2 >= $a andalso H2 =< $f)
         orelse (H2 >= $A andalso H2 =< $F))
andalso ((H3 >= $0 andalso H3 =< $9)
         orelse (H3 >= $a andalso H3 =< $f)
         orelse (H3 >= $A andalso H3 =< $F))
andalso ((H4 >= $0 andalso H4 =< $9)
         orelse (H4 >= $a andalso H4 =< $f)
         orelse (H4 >= $A andalso H4 =< $F)) ->
    parse_string(Rest,
                 [hex_to_int(H4), hex_to_int(H3), hex_to_int(H2), hex_to_int(H1) | Acc]);
parse_string([$\\, Next | Rest], Acc) ->
    parse_string(Rest, [Next | Acc]);
parse_string([H | Rest], Acc) ->
    parse_string(Rest, [H | Acc]).

parse_number([Sign | Rest]) when Sign == $- ->
    {V, R} = parse_number(Rest),
    {-1 * V, R};
parse_number([H | _] = String) when H >= $0, H =< $9 ->
    parse_int(String, 0).

parse_int([], Acc) ->
    {Acc, []};
parse_int([Sep | _] = Rest, Acc) when Sep == $\ ; Sep == $,; Sep == $]; Sep == $} ->
    {Acc, Rest};
parse_int([H | String], Acc) when H == $. ->
    parse_frac(String, Acc);
parse_int([H | String], Acc) when H == $e; H == $E ->
    parse_exponent(String, Acc);
parse_int([H | String], Acc) when H >= $0, H =< $9 ->
    parse_int(String, Acc * 10 + H - $0).

parse_frac(String, OldAcc) ->
    parse_frac(String, OldAcc, 0).

parse_frac([], Int, Acc) ->
    {Int / math:pow(10, Acc), []};
parse_frac([Sep | _] = Rest, Int, Acc) when Sep == $\ ; Sep == $,; Sep == $]; Sep == $} ->
    {Int / math:pow(10, Acc), Rest};
parse_frac([H | String], Int, Acc) when H == $e; H == $E ->
    {V, _} = parse_frac([], Int, Acc),
    parse_exponent(String, V);
parse_frac([H | String], Int, Acc) when H >= $0; H =< $9 ->
    parse_frac(String, Int * 10 + H - $0, Acc + 1).

parse_exponent([Sign | String], K) when Sign == $- ->
    parse_exponent_digits(String, K * -1);
parse_exponent([Sign | String], K) when Sign == $+ ->
    parse_exponent_digits(String, K);
parse_exponent(String, K) ->
    parse_exponent_digits(String, K).

parse_exponent_digits(String, K) ->
    parse_exponent_digits(String, K, 0).

parse_exponent_digits([], K, Acc) ->
    {K * math:pow(10, Acc), []};
parse_exponent_digits([Sep | _] = Rest, K, Acc) when Sep == $\ ; Sep == $,; Sep == $]; Sep == $} ->
    {K * math:pow(10, Acc), Rest};
parse_exponent_digits([H | Rest], K, Acc) when H >= $0; H =< $9 ->
    parse_exponent_digits(Rest, K, Acc * 10 + H - $0).

parse_object(Rest) ->
    parse_object(Rest, dict:new()).

parse_object(String, Acc) ->
    {Key, R1} = parse_string(String),
    [$: | R2] = R1,
    {Val, R3} = parse_value(R2),
    case R3 of
        [$} | R4] ->
            {dict:append(Key, Val, Acc), R4};
        [$, | R4] ->
            parse_object(R4, dict:append(Key, Val, Acc));
        _ -> {error, syntax_error, parse_object, String, Acc}
    end.
    
parse_array(Rest) ->
    parse_array(Rest, []).

parse_array(String, Acc) ->
    {Val, R1} = parse_value(String),
    case R1 of
        [$] | R2] ->
            {lists:reverse(Acc), R2};
        [$, | R2] ->
            parse_array(R2, [Val | Acc]);
        _ -> {error, syntax_error, parse_array, String, Acc}
    end.
    
hex_to_int(C) when C >= $0 andalso C =< $9 ->
    C - $0;
hex_to_int(C) when C >= $a andalso C =< $f ->
    C - $a + 10;
hex_to_int(C) when C >= $A andalso C =< $F ->
    C - $A + 10.

post a comment



Date:2008-01-22 21:13
Subject:Another winner from Cupertino
Security:Public

post a comment



Date:2008-01-10 22:17
Subject:ABCs for My Daughter
Security:Public

A is for Atom
B is for Binary
C is for Choice
D is for Diameter
E is for Entropy
F is for Fraenkel
G is for Gravity
H is for Heisenberg
I is for Impedence
J is for Joules
K is for Konstant
L is for Logic
M is for Mass
N is for Neutron
O is for Oscillate
P is for Particle
Q is for Quark
R is for Resistance
S is for Seconds
T is for Time
U is for Universal
V is for Vector
W is for WIMP
X is for Xeon
Y is for Yttrium
Z is for Zermelo

post a comment



Date:2007-12-13 11:05
Subject:Amber Alert
Security:Public

post a comment



Date:2007-12-04 17:40
Subject:Attaching Behavior in JS
Security:Public

There are plenty of libraries out there for attaching scripted behavior to HTML elements. Unfortunately, we're using Prototype, which lacks support for this. While we could integrate one of these libraries, in theory, I believe it's not worth the side-effects: possible destabilization due to conflicts, learning a new library, and additional download time for our clients.

Instead, I wrote one up myself. It doesn't have to do very much: scan the DOM when the page loads and monitor the DOM for dynamic updates. I've chosen to do strict class-based behavior. To enable behavior on an element, you must do three things:

  1. On the JavaScript side, you code up the actual behavior (for instance, submitting an Ajax request for a link, instead of following it normally).
  2. On the HTML side of things, you have to add a class to your element so our DOM watcher can know where to attach it. For instance, you would specify a class of "async_link" on the A tags where you want an Ajax request submitted.
  3. Finally, you have to inform your DOM watcher that a given CSS class has a given JS behavior. With the module I've written, this is as simple as:
    DOMWatcher.EventHandlers.async_link = AsyncLink.Watcher;

Well, that's the theory anyway. On to the code.

First, let's create a module to contain all this code, along with the public API methods:

var DOMWatcher = function () {
  return {
    EventHandlers: {},

    scanDocument: function () {
      attachBehavior();
    },

    addWatcher: function (klass, watcher) {
      DOMWatcher.EventHandlers[klass] = watcher;
      if (document.body) {
        attachBehavior();
      }
    },

    removeWatcher: function (klass, watcher) {
      delete DOMWatcher.EventHandlers[klass];
    }
  };
}();

Simple enough. A method to scan the document and attach behavior, and a couple of accessors to add and remove behavior after the document has been loaded.

To define the element behaviors themselves, I've gone with a simple map of CSS classes to behavior objects, which is stored in DOMWatcher.EventHandlers. The format of this object is straightforward: a behavior object may contain a setup method, which is called with a single argument of the element to be initialized, and methods starting with 'on' which specify the event on this element to attach behavior.

Here's a simple example:

var AlertLink = {
  setup: function () {
    this._alert = 'Hello World!';
  },

  onclick: function (event) {
    alert(this._alert);
  }
};
DOMWatcher.EventHandlers.alert_link = AlertLink;

During execution of behavior methods, I want the this object to be set to the element for which the behavior applies. It's not terribly important - we could just pass it in, but I like this way better.

So now that we know what we want the code to look like, lets have a go at the attachBehavior function which makes all this possible:

var DOMWatcher = function () {
  var ATTRIBUTE_BOUND = '_DOMWatcher_bound';

  function attachBehavior(target) {
    var elements, elt, klass, i, length, handler, method;

    target = target || document.body;
    elements = target.getElementsByTagName('*');

    for (i = 0, length = elements.length; i < length; i++) {
      elt = $(elements[i]);
      for (klass in DOMWatcher.EventHandlers) {
        if (DOMWatcher.EventHandlers.hasOwnProperty(klass) &&
            !elt[ATTRIBUTE_BOUND] && elt.hasClassName(klass)) {
          elt[ATTRIBUTE_BOUND] = true;

          handler = DOMWatcher.EventHandlers[klass];
          if (handler.setup) {
            handler.setup.call(elt);
          }

          for (method in handler) {
            if (method.substring(0, 2) == 'on') {
              Event.observe(elt, method.substring(2, method.length),
                            handler[method].bindAsEventListener(elt));
            }
          }
        }
      }
    }
  }

  ...

}();

Let's break that down: first we grab the node from which to begin scanning, defaulting to document.body, if it wasn't passed in, and grab all its children:

target = target || document.body;
elements = target.getElementsByTagName('*');
  

Once we have all the child elements, we can iterate over them, looking for classes with behavior defined:

for (i = 0, length = elements.length; i < length; i++) {
  elt = $(elements[i]);
  for (klass in DOMWatcher.EventHandlers) {
    if (DOMWatcher.EventHandlers.hasOwnProperty(klass) &&
        !elt[ATTRIBUTE_BOUND] && elt.hasClassName(klass)) {
      elt[ATTRIBUTE_BOUND] = true;

      ...
    }
  }
}
  
Note that we're using elt[ATTRIBUTE_BOUND] in order to store whether or not we've already attached behavior to this element, as an optimization to prevent reattaching behavior.

With the element stored in our temporary elt variable and a behavior class in klass, we can now run the setup routine and attach event handlers from DOMWatcher.EventHandlers:

...

handler = DOMWatcher.EventHandlers[klass];
if (handler.setup) {
  handler.setup.call(elt);
}

for (method in handler) {
  if (method.substring(0, 2) == 'on') {
    Event.observe(elt, method.substring(2, method.length),
                  handler[method].bindAsEventListener(elt));
  }
}

...
  

Now let's set up the scan to happen when the document is finished loading, so our behavior will get attached when the page is ready:

Event.observe(window, 'load', DOMWatcher.scanDocument);
  

And we're almost done. We also need to handle the case of Ajax updates to the DOM. Unfortunately, there's no good cross-browser way to do this, as only a few support DOMNodeInserted. Notably, IE does not, and has no equivalent that we could use in its stead. Also, Prototype has no facilities to support this, and in fact makes it quite painful to try and do it cleanly.

Luckily, JavaScript is incredibly dynamic, and has no real security model, so what we can do instead of events is scan the document when certain Prototype functions are called. Since we use Prototype exclusively, this is only a matter of figuring out which calls update the DOM, and wrapping them to add a call to DOMWatcher.scanDocument:

(function () {
  var oldReplace = Element.Methods.replace;
  var oldUpdate = Element.Methods.update;
  var oldInsertion = Abstract.Insertion.prototype.initialize;

  Element.Methods.replace = function (element, html) {
    oldReplace(element, html);
    DOMWatcher.scanDocument();
  };
  Element.replace = Element.Methods.replace;

  Element.Methods.update = function (element, html) {
    oldUpdate(element, html);
    DOMWatcher.scanDocument();
  };
  Element.update = Element.Methods.update;

  Abstract.Insertion.prototype.initialize = function (element, content) {
    oldInsertion.call(this, element, content);
    DOMWatcher.scanDocument();
  };
})();
  

It's worth noting that I used three temporary variables, because IE had issues when I tried to use a single variable inside an iterative function. Oh well. People would probably find this version easier to read anyway.

And that's all there is to it. Really. With this foundation in place, we now have the ability to add behaviors to elements fairly cleanly, which encourages a nice separation of code from HTML and from other code by use of the module pattern.

Coming up: using DOMWatcher to automatically enable and disable links and forms.

post a comment



Date:2007-09-23 09:10
Subject:cocoa -> js
Security:Public

I thought I'd start trying to develop a decent MVC/GUI framework for JS, since none exist at the moment. I was originally thinking of porting Rails, but Rails itself is fatally flawed as a generic MVC framework. On the other hand, Cocoa is not.

The primary way you link up components and do the high-level architecture of Cocoa applications is with NIBs, which are descriptions of the UI look and behavior. Since we already have HTML, we don't need to duplicate the canvas painting or anything, and can merely focus on linkages for behavioral reasons.

So here's the CurrencyConverter application, done up with JIB/JS instead of Cocoa/ObjC

First, create the view in your favorite HTML editor.

<div class="jib" jib="/javascript/currency_converter.js">
  <form>
    <label for="nativeCurrency">Native</label>
    <input id="nativeCurrency"/>
    <br/>

    <label for="exchangeRate">Exchange Rate</label>
    <input id="exchangeRate"/>
    <br/>

    <label for="foreignCurrency">Foreign</label>
    <span id="foreignCurrency" class="value"></span>
    <br/>

    <ul class="controls">
      <li><a id="cancelButton">cancel</a></li>
      <li><a id="convertButton">convert</a></li>
    </ul>
  </form>
</div>


Next, create the JIB file to load (from the jib attribute of the above div). The JIB file consists of the JIB itself, plus any implementations it might need. So here's the controller code:

var CurrencyController = function () {};
CurrencyController.prototype = Object.extend(new AppKit.Controller(), {
  cancel: function (sender, event) {
    alert('This is kind of meaningless.');
  },

  convert: function (sender, event) {
    if (this.nativeCurrency.value && this.exchangeRate.value) {
      var foreignAmt = this.nativeCurrency.value * this.exchangeRate.value;
      this.foreignCurrency.innerHTML = foreignAmt.toFixed(2);
    }
  }
});


and here's the JIB definition itself. The idea is that this can be auto-generated from tools at some point in the future, but for now we can do it by hand fairly easily:

var currencyConverterJib = function () {
  var controller = new CurrencyController();
  controller.setBindings({
    nativeCurrency: AppKit.Jib.Outlet('nativeCurrency'),
    exchangeRate: AppKit.Jib.Outlet('exchangeRate'),
    foreignCurrency: AppKit.Jib.Outlet('foreignCurrency'),
  });

  AppKit.Jib.Connection(AppKit.Jib.Outlet('nativeCurrency'), 'nextResponder',
                        AppKit.Jib.Outlet('exchangeRate'));
  AppKit.Jib.Connection(AppKit.Jib.Outlet('nativeCurrency'), 'onblur',
                        AppKit.Jib.Action(controller, 'convert'));
  AppKit.Jib.Connection(AppKit.Jib.Outlet('exchangeRate'), 'nextResponder',
                        AppKit.Jib.Outlet('nativeCurrency'));
  AppKit.Jib.Connection(AppKit.Jib.Outlet('exchangeRate'), 'onblur',
                        AppKit.Jib.Action(controller, 'convert'));

  AppKit.Jib.Connection(AppKit.Jib.Outlet('convertButton'), 'onclick',
                        AppKit.Jib.Action(controller, 'convert'));
  AppKit.Jib.Connection(AppKit.Jib.Outlet('cancelButton'), 'onclick',
                        AppKit.Jib.Action(controller, 'cancel'));
};


The AppKit.Jib.{Outlet, Action, Connection} methods are fairly straightforward, and only really used in this style for easier automation.

One of the things I added was a "nextResponder", like the one in Cocoa. This allows me to specify how the tab key moves you around forms. To make this work, I process every element w/in a JIB's HTML view, and extend it via some built-in methods. One of which hooks up to 'onblur' and looks at the 'nextResponder' for an element.

There's more to come, but I wanted to post this up today, since I got the CurrencyConverter working, and maybe some people will be inspired to start taking JS to where it should be.

post a comment



Date:2007-08-30 20:52
Subject:Money As Debt
Security:Public


Money As Debt
"Money As Debt" on Google Video
Paul Grignon's 47-minute animated presentation of "Money as Debt" tells in very simple and effective graphic terms what money is and how it is being created. It is an entertaining way to get the message out. The Cowichan Citizens Coalition and its "Duncan Initiative" received high praise from those who previewed it. I recommend it as a painless but hard-hitting educational tool and encourage the widest distribution and use by all groups concerned with the present unsustainable monetary system in Canada and the United States.

post a comment



Date:2007-08-22 15:54
Subject:Sharpton likes the Co-Opt, too!
Security:Public
Mood:appalled

As previously reported, a statement from Al Sharpton’s National Action Network said the civil rights activist was “appalled” to see the cartoon, and cited the drawing as “further proof that the hip hop community and those who market them must be held accountable for the destruction they are causing in the black community.”


BLACK JOURNALISTS REACT TO EDITORIAL CARTOON: Editor issues apology after picture causes uproar.



I don't have words yet for this. I may not ever.

post a comment



Date:2007-07-17 00:16
Subject:I smell a sequel
Security:Public

The state originally estimated that 64,000 people—mostly state employees—had personal data stored on the laptop. However, not long after the theft, the state bumped that number to around 229,000, then 500,000, and it's now close to 1 million. The expanded number of people whose personal data was lost included hundreds of thousands of state taxpayers who had not yet cashed tax refund checks, hundreds of Ohio lottery winners who had not yet cashed winnings checks, and thousands of others who either had not cashed various payments or whose direct deposits failed to go through.


Ohio bumps data theft estimate up to 1 million (Ars Technica)



Let me get this straight: 22-year-old female intern leaves laptop in car over weekend and loses data on at least one million people who haven't picked up their money from the government.



007'll be all over this case. Will it be Russians or the vaguely middle-eastern this time?

Server: www.livejournal.com

post a comment



Date:2007-07-12 21:27
Subject:welcome to the jungle, jim
Security:Public
Mood:ecstatic

Galaxy Zoo is the coolest thing to happen since the Fonz hooked up with Elvis in James Dean's Porche.

post a comment



Date:2007-07-02 16:48
Subject:open closures
Security:Public
Mood:confused

#rubyonrails has failed me. I can't say I'm surprised, since this is completely bizarre:

http://pastie.caboo.se/75488

Someone, anyone, please tell me how this works.

post a comment



Date:2007-07-02 11:10
Subject:testing, testing, is this thing on?
Security:Public

One of my jobs at DoubleClick was developing an application which calculated eCPM (effective Cost-Per-thousand-iMpressions) based on the CTR (click-through rate) of a CPC (Cost Per Click) ad. This isn't anywhere near as simple as it sounds, mainly because we were interested in the future eCPM rate, so we could bias ad delivery effectively.

When the job was "finished" and we rolled out to beta, we noticed that eCPM rates we were calculating were three orders of magnitude too low. Sharp readers may have already guessed why: I forgot to multiply by a thousand when converting from cost per click to cost per thousand impressions.

That's not the point, though. The point is the answer to the question: "How did this occur?" DoubleClick employs extensive product functional and integration tests which are managed by separate groups from the principle engineers. They should have known the spec and been able to catch this fairly early on.

The problem was that they didn't know the spec. No one did, really - we developed a lot of it ad hoc, and when changes were necessary an email flurry went out and everyone got informed. But in the flurry it was easy to get confused as multiple people responded with multiple sets of conflicting information. In that environment you almost always get an ad hoc "leader", too - the person who knows the right answer in the middle of all the conflicting information. That leader in this case, was me.

So, to shorten this up: we have a group of testers who were getting their specification from one of the developers, who introduced a bug, and this bug persisted until the beta test, at which point our statisticians brought it to our attention. The statisticians were involved during development, but were mostly hands-off about how it worked. During beta they noticed ad delivery wasn't working as predicted and we found the bug in fairly short order.

I would like to stress that the lesson here is what happens when your testers mingle too much with your coders.

I'd like to reinforce this by letting you know that I suck at math. So do lots of coders. But coding is math. There's no way you can dice it that it isn't. Where coding is different is in the praxis. Coders can, in near-real-time, debug their algorithms; they can test small aspects of it to ensure correctness; they can try a bunch of different methods. Pretty much as fast as you can think it, you can try it. In short: coders can make mistakes, and get used to making mistakes, and find ways to work around making mistakes (testing).

I make lots of mistakes. Not huge ones, most of the time, but little ones. Like flipping a sign. Multiplying instead of exponentiation. Like forgetting to multiply by one thousand. It's that kind of thing that flunks me on math tests. It's that kind of thing that gets me in trouble in my code. But code is much much more forgiving than math tests.

To compensate for my lack of insane correctness, I try to test like crazy. I spend far far more time writing tests than I do writing the code they exectute. The weird thing is, if I write the test first, the code takes even less time to write, because I have a working example. I'm not sure if the aggregate is better or not, but I code fast enough that I can still do both before most people finish the code.

So I'm a bad mathematician, which would make me a bad programmer, except that I have this enormous crutch of test cases, which make me a great programmer. I don't suppose it should be odd that cybernetics should enhance the programmer so much.

post a comment



Date:2007-07-01 10:57
Subject:three things you need
Security:Public
Mood: sleepy

With a slight re-wording, this would probably apply to most other fields, as well. I got this while listening to some professional musicians about what it means to be good. So without further ado, here are the three things you'll need to be a good programmer:


Resource Efficiency

This is probably what most programmers focus on. You need to be able to get the job done within a reasonable amount of time, space, or whatever.


Depth of Field

To be effective, you can't be trolling docs all the time. The productivity difference between you no longer needing the docs and needing them is more than night and day. This is the ability most reliant on experience with your current language and environment.


Elegance

It's not enough to get the job done. You should be leaving a legacy. People need to look at your code and go, "that's how it is supposed to be done."



If you only have one, it's probably "Depth of Field." You've been using the same language and environment your entire life - you might not even know there's a difference between language and environment. The second one is more variable, but it'll probably be "Resource Efficiency". Most people tend to really over-do it at this stage, under the (wrong!) assumption that programs should be as efficient as possible.

If you have any two of these three, you're an ok programmer. If you have all three, you're world-class. It takes a lot to have all three. Personally, I only have the first and the third. I switch languages and environments too much to have much depth of field in whatever I'm currently working on. Except for brief interludes with perl and c, anyway.

On the other hand, it's hard to get elegance without breadth of field. Knowing lisp doesn't just make you a better lisp programmer - it makes you a better programmer. C people just don't recurse as often as they should, and their code is a mess as a result. Recursion is more elegant than iteration most of the time because it cleanly separates leaf behavior from tree behavior. As a result, it has a tendency to force you to simplify your code to those terms.

So anyway, I'm looking forward to really learning ruby and rails, at least to the point where I can stop reading the damn docs every few minutes.

post a comment



Date:2007-06-18 20:17
Subject:emacs, the better browser?
Security:Public
Mood: pleased

I haven't felt comfortable posting here at the times when I'm most likey to be able to. I also find the web interface to be kind of slow and kludgey.

Wouldn't it be nice if I could post in emacs.

Of course it would.

I should point out that you'll also need http-emacs, which should have at least been mentioned in the docs.

I grabbed 'em both out of CVS/SVN, made the mods to my .emacs (nee init.el)


(add-to-list 'load-path "~/src/elisp/ljupdate")
(add-to-list 'load-path "~/src/elisp/http-emacs")

(mapcar (lambda (fn) (autoload fn "ljupdate" "Livejornal updater." t))
        '(lj-login lj-logout lj-compose))
(mapcar (lambda (fn) (autoload fn "http-post" "Load HTTP transfer." t))
        '(http-post http-get))



Coming up: 37 signals of the end of time.

post a comment



Date:2007-06-01 19:55
Subject:REST
Security:Public

It's about the objects, stupid!

Resource == object
HTTP method == method!

We've been doing this for a long time now. What's so hard about it?

post a comment


browse
my journal