MacBook, defective by design banner

title:
Put the knife down and take a green herb, dude.


descrip:

One feller's views on the state of everyday computer science & its application (and now, OTHER STUFF) who isn't rich enough to shell out for www.myfreakinfirst-andlast-name.com

Using 89% of the same design the blog had in 2001.

FOR ENTERTAINMENT PURPOSES ONLY!!!
Back-up your data and, when you bike, always wear white.
x

MarkUpDown is the best Markdown editor for professionals on Windows 10.

It includes two-pane live preview, in-app uploads to imgur for image hosting, and MultiMarkdown table support.

Features you won't find anywhere else include...

You've wasted more than $15 of your time looking for a great Markdown editor.

Stop looking. MarkUpDown is the app you're looking for.

Learn more or head over to the 'Store now!

Monday, June 18, 2018

So this is impressively stand-up… I bought an SD copy of Killing Eve on the iTunes store for $2, just to see if I’d like it. It’s pretty good, and I might like to watch it all.

One of the reasons I like using iTunes for this sort of series checking is that I can pull a Complete My Season, putting the price of individual episodes towards buying a discounted whole season later, if I want. It encourages trying before you buy, which, ultimately, encourages buying.

But what if I want HD? Turns out Complete My Season has me covered. That $1.99 I spent on an SD episode unexpectedly can also be applied to an HD season purchase! The HD was originally $19.99, now $18 after a $1.99 purchase. SD was $13.99, now $12.

I mean, I guess that makes some sense. You can download the SD version when you buy the HD package, so the SD:HD::SD purchase::HD purchase syllogism works, but it’s still impressive to see.

Anyhow, kudos to Apple on that one.

Complete My Season prices after buying one SD episode

Labels: ,


posted by ruffin at 6/18/2018 08:28:00 PM
0 comments
Tuesday, June 12, 2018

MDN has an excellent sample to show the differences between for... in, for... in with hasOwnProperty, and for... of.

Good thing we had more prepositions, I guess.

Object.prototype.objCustom = function() {}; 
Array.prototype.arrCustom = function() {};

let iterable = [3, 5, 7];
iterable.foo = 'hello';

for (let i in iterable) {
  console.log(i); // logs 0, 1, 2, "foo", "arrCustom", "objCustom"
}

for (let i in iterable) {
  if (iterable.hasOwnProperty(i)) {
    console.log(i); // logs 0, 1, 2, "foo"
  }
}

for (let i of iterable) {
  console.log(i); // logs 3, 5, 7
}

Labels:


posted by ruffin at 6/12/2018 08:54:00 PM
0 comments
Friday, June 08, 2018

I keep piling up TONS of stuff in Instapaper with the up-until-now mostly unfulfilled plan to catch up on these "important" articles that deserve some consideration later.

I never get there. I found myself wishing I could print out all my Instapaper articles as a [real, bound, paper] book.

Did you know Instapaper lets you export your stuff as ebooks and even a printable pdf (though the last keeps timing out on me)? The only thing I don't like so far is that the ebooks it creates only go back a little ways in my history. I guess I should catch up, archive things, then make another book. Or make a book, archive its contents, then make another.

But this is the killer feature for Instapaper. Put the content I want to read closely into a format conducive to close reading.

Accessing the Instapaper download interface

Now many of my articles are cut off, but that's a general issue with Instapaper. When it works, it works well.

Example content from Instapaper eBook

(Not saying I'd recommend using Edge to read ebooks; just what was close at hand. Time to OneDrive them over to the iPhone, I suppose.)

Labels:


posted by ruffin at 6/08/2018 11:37:00 AM
0 comments

I'm apparently late to the JavaScript Fatigue is a Thing game.

Too Many Tools.

At work this past quarter, we painstakingly started three new projects at work. I say “painstakingly” because every project required decisions to be made around tooling depending on the scope & needs.

Ultimately, the problem is that by choosing React (and inherently JSX), you’ve unwittingly opted into a confusing nest of build tools, boilerplate, linters, & time-sinks to deal with before you ever get to create anything.

Labels: ,


posted by ruffin at 6/08/2018 09:49:00 AM
0 comments
Wednesday, June 06, 2018

I've been trying to sell a client on minimizing libraries in React for a while, and have been suggesting different vanilla state management architectures in place of Redux or MobX.

Interesting, then, to come across this article today:

You Don’t Need Redux, MobX, RxJS, Cerebral:

I know what you’re thinking: Redux alternatives are a dime a dozen. But this isn’t yet another library. In fact, it’s not a library at all.

It’s just a simple pattern: Meiosis.

Why a Pattern?

Using a pattern instead of a library means that you have more freedom. You are not dependent on a library’s features, bugfixes, and release dates. You are not worried about backward compatibility, deprecation, upgrade migration paths, or project abandonment. You are never waiting for a missing feature.

Yes, please. I need to read up on what they're proposing. It sounds like it might skip some of the advantages of having a singleton state object, like Redux and some MobX implementation do, but this is good momentum.

Labels: , ,


posted by ruffin at 6/06/2018 10:03:00 AM
0 comments
Tuesday, May 29, 2018

What's Component<any, any> in Typescripted React? Props and state. I mean, you knew that, but here's some confirmation. Took me a while for my desire for confirmation and ability to google successfully to match up.

From github.io:

Sometimes you’ll see definitions like class MyComponent extends React.Component<{}, {}>. This means that this component doesn’t use props or state—they must be empty. class MyComponent extends React.Component<any, any> says that you can pass anything for props and state and it will do no type checking.

What a weird mishmash of C# and JavaScript this TypeScript is. I mean, I like it, but it feels like an incomplete language that allows shortcuts borrowed directly from two different languages. I'm not saying I dislike it, but TypeScript is a strange pastiche of programming paradigms.

Labels: , ,


posted by ruffin at 5/29/2018 12:01:00 PM
0 comments
Monday, May 28, 2018

I'm not sure why, but Promises in JavaScript took a while for me to grok. I mean, the premise is simple: You have a function, and once it's done, whenever that is, the appropriate chained methods will be executed, like .then if it's successful and .catch if it isn't, with the appropriate parameters given. That's easy, and you can start using Promise workflows quickly.

But how do you make a Promise? Strangely, many tutorials approach this as if all Promises were sui generis (sui generis apparently means "of its own kind" -- as in something that can't be reduced to something simpler. Or, as the Wikipedia might put it, "[E]xamples are sui generis [when] they simply exist in society and are widely accepted without thoughts of where they come from or how they were created").

Let's correct that approach. MDN's most basic example of creating a Promise is pretty straightforward:

const myFirstPromise = new Promise((resolve, reject) => {
  // do something asynchronous which eventually calls either:
  //
  //   resolve(someValue); // fulfilled
  // or
  //   reject("failure reason"); // rejected
});

Do something, then resolve or reject it. That's simple. We're used to doing just that in callback-land excepting the Promise wrapper.

Take this example code from a pretty good, very basic tutorial on Promises from Wesley Handy here.

function getData() {
    return new Promise((resolve, reject)=>{
        $.ajax({
            url: `http://www.omdbapi.com/?t=The+Matrix`,
            method: 'GET'
        }).done((response)=>{
                //this means my api call suceeded, so I will call resolve on the response
                resolve(response);
        }).fail((error)=>{
                //this means the api call failed, so I will call reject on the error
                reject(error);
        });
    });
}

The behind the curtain magic happens in the formulation of resolve and reject. You set those up with this:

getData()
    .then(data => console.log(data))
    .catch(error => console.log(error));

If you're not careful, your Promise tutorial might accept that that's magic, and not bother asking Mitch Pileggi how the trick was done. What you're doing is wrapping all of your .thens and catches into two [possibly composite] functions. This is that "syntactic sugar" everyone loves to talk about.

Unraveling a Promise

The quick getData call, above, looks like this outside of the Promise-sugar.

function doIt(resolve, reject) {
    $.ajax({
        url: `http://www.omdbapi.com/?t=The+Matrix`,
        method: 'GET'
    }).done((response)=>{
        //this means my api call succeeded, so I will call resolve on the response
        resolve(response);
    }).fail((error)=>{
        //this means the api call failed, so I will call reject on the error
        reject(error);
    });
}


var ajaxSuccess = function (data) {
    console.log(data);
}

var ajaxFail = function (error) {
    console.log(error);
}

doIt(ajaxSuccess, ajaxFail);

That's it. That's what that call looks like outside of a Promise. There's no real Promise-centric benefit for this simple case, imo.


Unraveling a Promise with a finally

More interesting would be if that example also had a finally, like this:

getData()
    .then(data => console.log(data))
    .catch(error => console.log(error))
    .finally(() => console.log("Finally gets no arguments"));

That looks like this, with the same doIt function as earlier (ie, doIt doesn't change):

function doIt(resolve, reject) {
    $.ajax({
        // Same as above...
}

// Here's the new function we want to use after `doIt` completes, no matter what.
var ajaxFinally = function () {
    console.log("Finally gets no arguments");
}

// Now back to our success/fail functions with *one* change for each...
var ajaxSuccess = function (data) {
    console.log(data);
    ajaxFinally();    // and now we add it to BOTH the success and fail functions.
}

var ajaxFail = function (error) {
    console.log(error);
    ajaxFinally();    // <<< OMGWTFBBQ!!1! ajaxFinally is here, too.
}

doIt(ajaxSuccess, ajaxFail);

Here, we do get a little bit of a readability improvement, and certainly some DRYness.


Promise wrapping

That is, the Promise wrapper...

  1. Packages the Promise in a ripcord-ready state, but doesn't actually deploy the action, and...
  2. Magically combines all the chained functions wrapped in then, catch, and finally into resolve and reject (more precisely, "into the two parameters any Promise expects"), above.

Again, the Promise constructor just does some sugar-magic to wrap up all the chained functions that follow it once its async action completes, and route logic into one (resolve) or the other (reject) once the wrapped action is complete. Fwiw, all the chainable functions are described here, at MDN.

I think part of my block on Promises was that callbacks are so danged easy to understand. And if you have a true pyramid of callback doom, I've taken that as a code smell rather than an insurmountable issue inherent to javascript. What is this Promise doing that makes it simpler to follow than callbacks? In a sense, nothing. It's hiding how callbacks are chained together. Some folks find functions-as-objects difficult to grok, and I think the structure Promises gives helps folks that don't like function-freewheeling.

But, as thecodebarbarian.com explorer here, pyramids usually are signs of bad architecture, not a place where you're limited by code.

This case [of "callback hell" used as an example earlier] is a classic example where the single function is responsible for doing way too much, otherwise known as the God object anti-pattern. As written, this function does a lot of tangentially related tasks:

  • registers a job in a work queue
  • cleans up the job queue
  • reads and writes from S3 with hard-coded options
  • executes two shell commands
  • etc. etc.

The function does way too much and has way too many points of failure. Furthermore, it skips error checks for many of these. Promises and async have mechanisms to help you check for errors in a more concise way, but odds are, if you're the type of person who ignores errors in callbacks, you'll also ignore them even if you're using promise.catch() or async.waterfall(). Callback hell is the least of this function's problems. [emph mine, natch -mfn]


TL;DR

I'm belaboring the point, but bottom line is very simple:

Promises simply use chaining functions to organize your callbacks into two composite functions. One composite function gets called on success, one on failure.

here endeth the lesson

Labels: , ,


posted by ruffin at 5/28/2018 09:37:00 PM
0 comments
Thursday, May 24, 2018

From a recent episode of the Under the Radar podcast, discussing new Apple technologies that are announced at, eg, WWDC:

I think increasingly I've come to more of the conclusion that, if I don't get [the new technology Apple's releasing], if I don't think it's going to be ... if I don't really see the immediate utility of it or think it's really cool, then there's a good chance that I'm probably more typical than not. There's a chance that I'm missing the next big wave, but it's also just as likely that I'm not, and that they're actually not going to be this big thing. Because the nature of these new announcements is so often they are solutions going in search of problems, and they're often very cool and technically very capable. But it's difficult.  [emphasis mine --mfn]

This is exactly the current issue at Apple when comes to software (well, this and a fundamental mishandling of QA, though they're closely related): They dream up use cases, target very specifically for those use cases, and don't spend enough time dealing with edge cases, even useful ones.

The problem here is twofold:

  1. Often Apple's use case misses. Exhibit Ping, but even the Apple Watch to some point & the current MacBook Pro's crappy keyboard, and then the engineering time is wasted.
  2. People use the software in secondary ways Apple didn't intend, and d/w/on't support, meaning those secondary use cases don't work well.

For the second point, I'm reminded of this horrible experience Jason Snell wrote about, or, perhaps better, this case where I couldn't update a Google OAuth password in macOS preferences.

For Apple, there's the Happy Path or there's no path at all. That works a lot better when designing hardware than software.

Labels: ,


posted by ruffin at 5/24/2018 09:10:00 AM
0 comments

Support freedom
All posts can be accessed here:


Just the last year o' posts:

URLs I want to remember:
* Atari 2600 programming on your Mac
* joel on software (tip pt)
* Professional links: resume, github, paltry StackOverflow * Regular Expression Introduction (copy)
* The hex editor whose name I forget
* JSONLint to pretty-ify JSON
* Using CommonDialog in VB 6 * Free zip utils
* git repo mapped drive setup * Regex Tester
* Read the bits about the zone * Find column in sql server db by name
* Giant ASCII Textifier in Stick Figures (in Ivrit) * Quick intro to Javascript
* Don't [over-]sweat "micro-optimization" * Parsing str's in VB6
* .ToString("yyyy-MM-dd HH:mm:ss.fff", CultureInfo.InvariantCulture); (src) * Break on a Lenovo T430: Fn+Alt+B
email if ya gotta, RSS if ya wanna RSS, (?_?), ¢, & ? if you're keypadless


Powered by Blogger etree.org Curmudgeon Gamer badge
The postings on this site are [usually] my own and do not necessarily reflect the views of any employer, past or present, or other entity.