Episode 006: All Wrapped Up in Twitter

Christoph tries to get a handle on his #clojure tweet-stream habit.

  • NOT tic-tac-toe
  • Follow #clojure tweet stream and see it print out in the terminal
  • “We like reinventing things.”
  • “The terminal is the best UI out there.”
  • Does Twitter have an API?
  • Websocket? Nope! Requires a big “E” plan: “enterprise”.
  • PubSub? Nope! Not from Twitter.
  • Alas, we must poll the /search/tweets.json endpoint
  • Problem: we’re going to keep getting results we’ve already seen
  • Avoid duplicates? Let’s use core cache.
  • Once again, we use loop and recur for our main loop
  • Time for an API wrapper, but what does the wrapper do?
  • HTTP POST form-encoded parameters
  • Ack! 401 Access Denied
  • “An important step in any API exploration is your first 401 response.”
  • “OAuth?” “Oh….auth…”
  • Meet OAuth, the API bouncer.
  • Make an auth function to call the OAuth endpoint and get an auth token
  • Have auth return a “handle” with the auth token. Other wrapper functions will need handle.
  • Need to keep handle around. Put that in the app state too.
  • Let the exceptions fly!
  • “Exceptions are an exceptionally accepted way of handling exceptional circumstances.”
  • “I caught what you meant.”
  • Make a fetch function that does the I/O work.
  • Create a search function that takes handle and query
  • Look for pure logic and move it into its own function, then it’s easy to test.
  • Transform args to search into a “request description” and have fetch operate on that.
  • “Twitch, I mean Twitter. You know, that Internet thing that starts with t-w-i-t.”
  • Different layers of the wrapper:
    • Top-level functions used by the application. Sole job: sequence internal functions
    • Pure transforms from args to “request description”
    • A fetch function that follows the “orders” of the “request description”
  • “The point of testing this code is not to test if Aleph works or Twitter works. The logic is in the translation function, so that’s why we test it.”
  • “Our hero, the Twitch wrapper, is poised and ready to fetch data on our behalf. What will happen next?”

Episode 005: Tracking, for the Win

Nate tries to figure out who actually won this never-ending game of tic-tac-toe.

  • Tic-tac-toe is “just boring enough, just interesting enough.”
  • How do we know who won the game? Inspect the board.
  • If you can track progress toward the win, you check for the win quickly
  • “Tic-tac-toe at scale!”
  • Tracer bullet: go simple, just examine the 8 options for winning
  • “In that case, nil has won…which is nobody.”
  • Keep detection logic out of the high-level winner function–should read like a process description of steps.
  • Make new “verbs” like row-winner and column-winner and use those.
  • “You’re just adding new verbs to raise Clojure up to the level of your problem. You can speak about your problem using those verbs.”
  • Let’s make it faster! Need incremental detection to be efficient.
  • Tracking structure with win condition totals
    • keys like: [player case index]
    • value is a counter for that case
    • eg. { [:x :row 0] 1, [:y :row 0] 0, [:x :diag] 2, ...}
  • The win tracker is a “nested model” of the game state
  • Put the tracker it its own namespace app.game.tracker
  • Use [:x 1 0] as the play
  • Nested updates: (update game-state :tracking tracker/update [:x 1 0])
  • How do we handle diagonals? Not all moves will increment those totals.
  • Make helpers for diag? and rdiag? to use in cond-> (see code below)
  • High-level functions describe the process. Low-level functions describe the steps.
  • “You can see the animal, not the intestines.”
  • “If you see a word that’s a higher level concept, it allows you to stay at that higher level and be able to view the algorithm instead of viewing the implementation. That’s the point of lifting up all these little functions.”
  • Bonus: the tracker tells us all the ways a player won.

Episode 004: Atomic Curls

Christoph tries to make tic-tac-toe work over the Internet and discovers the power of the atom.

  • Let’s get a web framework, build a UI, hook that up to an HTTP API, throw in some websockets for notifications!
  • “We’d end up with our first 12 hour podcast episode.”
  • A “tracer bullet”: get something functioning to see the important parts of the solution.
  • Can choose when to replace important parts by something more “production worthy”.
  • “Let’s just ditch all the complexity of having a UI and just use curl.”
  • curl will print the text response. We have a terminal UI!
  • “We’re extending the command line out to the web!”
  • “I just keep curling every day or every other day until it’s my turn.”
  • “It’s your morning curls!”
  • How do we handle web requests? What is a route?
  • Routes: /new, /show, /play?row=0&col=1
  • “Super simple URL API. Who needs REST?”
  • Let’s run this on port 1337 to hide it from the Internet.
  • One shared game state stored in an atom. It’s the only game in town!
  • Use an atom instead of a database to cut complexity while problem solving.
  • The ! (“bang”) in swap! and reset! indicates you’re causing a side effect.
  • Handler’s sole job: take web requests and alter the game state using the game model.
  • Function called by swap! should be pure. Don’t throw exceptions!
  • Dilemma: How much do you put inside the function called by “swap!”?
  • For errors, the transaction function can:
    • Return unchanged reference. Use “triage” function to diagnose.
    • Have an “error” attribute in the state and set an “outcome” code like :success,:invalid-coordinate, etc.
  • Tracer bullet shows lots of ways to complex-ify this into “Tic-Tac-Toe, Enterprise Edition”

Episode 003: Tic-Tac-REPL

Nate tries to turn the tic-tac-toe “game engine” into a real application he can play with a friend.

  • Let’s play the game!
  • How do you keep track of the game state as it changes?
  • Bucket brigade the reference to the future loop using recur
  • The game loop: read input, evaluate, print out new board, loop.
  • “It has occurred to me that we are basically writing a REPL.”
  • “We have the tic-tac-REPL”
  • How do you get input from the user? How to you make sure it’s right?
  • “It keeps harassing the non-compliment user until they type the right thing in”
  • Input loop: read, validate, loop on error, return on success
  • Keep the logic pure! Separate out the parsing and validation functions.
  • “Much better to tuck it away in a function!”
  • Sequence the pure parts with a minimalist function that does the I/O.
  • “I don’t like having ovens present because they’re hard to put in my test cases.”
  • Unit test those pure parts. (No one likes to be mocked.)
  • I/O is a side effect!
  • “Every time I redefine one of those things I feel like I’m reaching down into the bowels of Clojure and doing something moderately illegal.”
  • Using keywords as error codes is nifty
  • “You don’t have any different kinds of nil. You just have one. It’s the nuh-uh.”
  • Can use a tuple with the first element always being a keyword and the second being data for the “details”

Episode 002: Tic-Tac-Toe, State in a Row

Christoph tries to make tic-tac-toe and gets stuck on immutability.

  • The REPL calculator
  • Let’s make a game!
  • How do you keep track of the game board?
  • How do you update the game board if you can’t change anything?
  • OO told me the “right” way to encapsulate, now what do I do?
  • “Nine lines of let block, and one line of actual function.”
  • “The reference bucket brigade”
  • reductions wants to blow your mind
  • Multiple universes of tic-tac-toe, and they’re all in a row!
  • Time travel, for free!

Episode 001: Why, Oh Why?

Nate and Christoph try to figure out how to make a podcast.

  • Who are we?
  • What are we doing?
  • What will we talk about?
  • Lots of (with-meta podcast ...)