Life @ NCP

not everyone needs to go outside to have fun

The Lion and The Gazelle

This is one of my favourite quotes.

Every morning in Africa, a gazelle wakes up. It knows it must run faster than the fastest lion or it will be killed. Every morning a lion wakes up. It knows it must outrun the slowest gazelle or it will starve to death. It doesn’t matter whether you are a lion or a gazelle: when the sun comes up, you’d better be running. ~ a quote from The Economist 1985.

I get complacent, a lot. I despise the idea of being complacent, but I enjoy it when I’m in a state of one. This is nothing unique to me though, it’s just what naturally happen when one becomes better at things. It’s comforting to be good at something. When someone tells me that I did a good job, it polishes my ego, verifying that I don’t need to do anything more. It’s a good enough job, and I can stop.

But the truth is, I’m not even running. Let’s use an analogy in this post.

Once every year, there are two races that are held in different locations: one is a marathon in a desert and another is a 10 km run in the forest. The marathon has 1000 runners competing in it. All of them are used to the harsh heat, lack of water, and long distance torture. The price for finishing the race is large, but only very few can make it even to the last phase of the marathon. The forest short race however, has only 10 runners competing. It’s far from a marathon, and more like a walk in the park, where everyone will be fed fruits along the way, so that they can enjoy the run. If you win the forest race, you get nice lucrative prizes, which are, on average larger than the desert race. But it varies, in some rare years, the prize for the desert race is astronomically larger than the forest’s. No one knows when this happens though.

Every year, I walk fast in the forest, and I walk faster than other nine among the trees. I can’t see how fast the desert runners have blitzed passed me in terms of actual speed. Fortunately they can’t see me either, and I get to win because only I, and people slower than me race in the forest. Oh, I forgot to mention, it’s a VIP entry only, so the same people do the race with me every year. But here’s the scary bit, once every decade, at some random time, the entry is open to the winner of the desert marathon who doesn’t get much in the desert marathon. A kind of consolation prize if you like.

I don’t know when this marathon winner is going to join the forest. But I know one thing, when it happens, I will lose and it will be hard for me to give away the prizes I’m used to receiving every year.

I better start running.

Author image Min'an

PAF #5 – LifeRecorder

There have been numerous occasions where I have been in a casual conversation with someone and gone “damn that was a good line” or “I am so going to record this for your wedding day”. A particularly infamous example I tell people about comes to mind. During a discussion about future marital prospects:

us: _what kind of girl would you like then?
_ him: any one with a hole is fine

Would have made for an interesting wedding day speech recording 🙂

Apart from blackmail material, how many times have you thought “that’s a good idea, I should write it down later” only for it to disappear in the pit of ideas I thought of but forgot and someone eventually implemented?, particularly during a conversation with someone else where pen and paper weren’t accessible? _
_

For the longest time I’ve wanted something which could easily capture moments like this; an always on recorder that you could say “save the last 60 seconds of what you heard”. Except it fell into the too crazy/difficult _to do realistically _bit till I saw Kapture and thought I wasn’t going to wear some wristband just to get it done. So here’s PAF #5 in a not so pretty picture:

liferecorder

Assuming I figure out how to do the technical bits, there’s a key difference between this PAF and the previous ones. By hook or crook, this one is definitely going on the play store.

 

Author image Min'an

PAF #4 GeoParty Show and Tell

Go try it out at http://geoparty.nimblecouchpotatoes.com ! It should work just fine if you login on your box with one name and your phone with another.

Before:

After:

It’s mostly functional 😉 It’s done using plain old setInterval ajax calls as opposed to WebSockets and since all of the command sending and message retrieval is done via REST endpoints, you could write your own external client for it.

Unfortunately I had to break some rules to get to this point (this took two fortnights), but I put it down to our trip to Iceland and London, which is another story altogether. There was already a usable frontend and backend in the first fortnight, the rest of the time was figuring out what other features to add, mostly frontendy work.

I remember talking to Scott H about the project early on and he mentioned that it sounded like there would be a significant amount of frontend work. It turned out that frontend work consumed the vast majority of the work involved, coming second only to time wasted flip flopping between ideas. Writing the backend using Go turned out to be relatively easy; it’s a simple language, you can get your head around its use of channels pretty quickly and the documentation is quite good.

Writing the frontend was a different matter altogether. You can spend hours just getting the bits and pieces aligned and working the way you want it to, but all too easily decide that some bit doesn’t quite work the way you want it to and just spend another few hours massaging it so the bits get aligned again. Doing web-based frontend work gives you a really quick feedback loop, but is consequently both a blessing and a curse; it’s easy to get lost “tidying up” or “improving” how a frontend looks when you are given free reign over how it should look. Looking at the final outcome, it would probably have taken alot less time to build if I knew what I was coming up with.

In terms of directions and ideas this could go, there’s a number of things I’d like to try if I worked on this in the future. The page itself is very general – it does room, private chat and sending of files. But it could be streamlined for specific use cases; I could imagine a “geoshare” site using the same backend that focused on sending files between people in the same location. Or richer interactions with external services; there is an unexposed API for registering webhooks that trigger on specific regular expressions, which is why you can say things like “weather in here” or “weather in sydney”. It could be hooked up to external services to provide IRC-style bot games like hangman or geo-aware wikipedia queries.

What do you think? Could this be turned into something bigger? Would love to hear your thoughts 🙂

Author image Min'an

Go Language Tour #71 – Web Crawler

Go is doing my head in. This took me a long long time to get out:

package main

import (  
    "fmt"
)

type Fetcher interface {  
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {  
    type Url struct {
        url string
        depth int
    }
    type HistoryReq struct {
        url string
        resultChan chan bool
    }

    historian := func() chan HistoryReq {
        history := map[string]bool{ url: true }
        historyReqChan := make(chan HistoryReq)
        go func() {
            for request := range historyReqChan {
                _, visited := history[request.url]
                if !visited {
                    history[request.url] = true
                } 
                request.resultChan <- visited
            }
        }()
        return historyReqChan
    }

    produce := func(urlChannel chan Url, urlRequest Url, historyReqChan chan HistoryReq, doneCounterChan chan bool) {
        getDepth, urlString := urlRequest.depth, urlRequest.url
        if (getDepth <= 0) {
            doneCounterChan <- false
            return
        }

        body, urls, err := fetcher.Fetch(urlString)
        if err != nil {
            fmt.Println(err)
        } else {
            fmt.Printf("found: %s %qn", urlString, body)
            for _, u := range urls {
                historyResultChan := make(chan bool)
                historyReq := HistoryReq{u, historyResultChan}
                historyReqChan <- historyReq
                if !<- historyResultChan {
                    urlChannel <- Url { u, getDepth - 1 }
                }                                                   
          }
        }
        doneCounterChan <- false
    }
    startWork := func(done chan bool, historyReqChan chan HistoryReq) chan Url {
        counter := 0
        doneCounterChan := make(chan bool)
        urlChannel := make(chan Url)
        go func() {
            for {
                select {
                case task := <- urlChannel:
                    counter++
                    go produce(urlChannel, task, historyReqChan, doneCounterChan)
                case <- doneCounterChan:
                    counter--
                    if counter == 0 {
                        done <- true
                    }
                }
            }
        }()
        return urlChannel
    }
    done := make(chan bool)    
    historyReqChan := historian()
    startWork(done, historyReqChan) <- Url{url, depth}

    <- done
    return
}

func main() {  
    Crawl("http://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {  
    body string
    urls []string
}

func (f *fakeFetcher) Fetch(url string) (string, []string, error) {  
    if res, ok := (*f)[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = &fakeFetcher{  
    "http://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "http://golang.org/pkg/",
            "http://golang.org/cmd/",
        },
    },
    "http://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "http://golang.org/",
            "http://golang.org/cmd/",
            "http://golang.org/pkg/fmt/",
            "http://golang.org/pkg/os/",
        },
    },
    "http://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
    "http://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
}

Go’s use of channels for synchronisation takes some getting used to :/

Here’s an earlier, less parallel version:

package main

import (  
    "fmt"
)

type Fetcher interface {  
    // Fetch returns the body of URL and
    // a slice of URLs found on that page.
    Fetch(url string) (body string, urls []string, err error)
}

// Crawl uses fetcher to recursively crawl
// pages starting with url, to a maximum of depth.
func Crawl(url string, depth int, fetcher Fetcher) {  
    type Url struct {
        url string
        depth int
    }
    type History struct {
        urls map[string]bool
        lock chan bool
    }

    var produce func(urlChannel chan Url, done chan bool, history *History)
    produce = func(urlChannel chan Url, done chan bool, history *History) { 
        urlToRetrieve := <- urlChannel
        urlString, getDepth := urlToRetrieve.url, urlToRetrieve.depth
        if getDepth <= 0 {
            return
        }

        body, urls, err := fetcher.Fetch(urlString)
        if err != nil {
            fmt.Println(err)
        } else {
            fmt.Printf("found: %s %qn", urlString, body)
            <- history.lock 
            for _, u := range urls {
                if _, visited := history.urls[u]; !visited { 
                    history.urls[u] = true
                    go produce(urlChannel, done, history)
                    urlChannel <- Url { u, getDepth - 1 }
                }                                                   
            }
            select {
            case history.lock <- true:
            default:                      
                done <- true;
            }
        }
    }

    history := &History{ map[string]bool{ url: true }, make(chan bool) }
    urlChannel := make(chan Url)
    done := make(chan bool)

    go produce(urlChannel, done, history)
    urlChannel <- Url{url, depth}
    history.lock <- true

    <- done
    return
}

func main() {  
    Crawl("http://golang.org/", 4, fetcher)
}

// fakeFetcher is Fetcher that returns canned results.
type fakeFetcher map[string]*fakeResult

type fakeResult struct {  
    body string
    urls []string
}

func (f *fakeFetcher) Fetch(url string) (string, []string, error) {  
    if res, ok := (*f)[url]; ok {
        return res.body, res.urls, nil
    }
    return "", nil, fmt.Errorf("not found: %s", url)
}

// fetcher is a populated fakeFetcher.
var fetcher = &fakeFetcher{  
    "http://golang.org/": &fakeResult{
        "The Go Programming Language",
        []string{
            "http://golang.org/pkg/",
            "http://golang.org/cmd/",
        },
    },
    "http://golang.org/pkg/": &fakeResult{
        "Packages",
        []string{
            "http://golang.org/",
            "http://golang.org/cmd/",
            "http://golang.org/pkg/fmt/",
            "http://golang.org/pkg/os/",
        },
    },
    "http://golang.org/pkg/fmt/": &fakeResult{
        "Package fmt",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
    "http://golang.org/pkg/os/": &fakeResult{
        "Package os",
        []string{
            "http://golang.org/",
            "http://golang.org/pkg/",
        },
    },
}

 

Author image Min'an

PAF #4 Geo Party – Location Based Rooms

Working from home can be a lonely experience. There isn’t anyone to have coffee with or bounce ideas off. Things you usually take for granted, like being able to catch up become alot harder when everyone else is working or in a different timezone. That’s not to say there aren’t any avenues for human interaction, going out and working in public areas is always an option.

Image courtesy of FreeDigitalPhotos.net


So close yet so far
Image courtesy of FreeDigitalPhotos.net

Even when working in public areas, you’re close to folks, but not quite either. Walking up to someone random and starting a conversation is a tricky proposition for most. Interactions with strangers is not a new idea though; mediums like IRC have been around for ages.

IRC chatrooms had some nifty features for their time. Though you had to connect to a server, you could initiate a direct chat or transfer using something called Direct-Client-to-Client. In fact, many clients supported scripts that allowed you to run bots like fileservers and trivia games.

When MSN Messenger came along, you could even do things like initiate games between clients (Solitaire showdown anyone?) which was still alot easier than what you’d probably have to do if you wanted to do a two player game on your phone today.

So here’s what I hope PAF #4 will look like.

  • Multiple folks jump online to a site, based on their location a chatroom is created or joined
  • They can chat with each other (multi party chat has been done to the death, which should help :P)
  • They can send files to each other
  • They can see in relative terms how far they are to each other (e.g. 25m away)
  • They can enter an email address or login with FB/G+ which will pull down their info/gravatar
  • They can join another chat room if they know its secret

Initially I thought of using Go, but it turns out there isn’t any free online hosting available for it. Too efficient resource utilisation maybe? So I might try to get a micro EC2 instance or switch to something else.

Author image Min'an