Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Mike Collins

Seven Languages In Seven Weeks: Clojure - Day 3

By Ben Nadel on
Tags: Clojure, Lisp

Day 3 of Clojure from Seven Languages in Seven Weeks took be about 4 hours to complete. And, to be completely honest, by the start of hour 3, I wanted to give up. This assignment only consisted of two problems: one that seemed straightforward, and one that was downright tortuous. Please keep in mind, as I present the following answers, that the solution for the second problem is basically an interpretation of someone else's solution.... an interpretation that took me 2 hours to write (and about 3 bouts of, "Just give up!" to overcome).

Todays learning was all about concurrency. And, like previous languages, much of the concurrency in Clojure appears to revolve around data points rather than blocks of code. Clojure, however, does have something called a "future," which is probably the closest thing to CFThread that I've seen in any of the other languages in the book. A future is basically a result that returns immediately, but doesn't necessarily contain the resultant value.

I think it might be good to think of a future like a Christmas present. Imagine that someone gave you a wrapped box a few days before Christmas and said, "Here's your present; there's nothing in it yet, but on Christmas, I'll put something inside of it." That box is like a future. You have the box immediately; but, that doesn't mean there's anything of value inside of it. And, if you want to open the box to get your present, you have to wait (ie. block the current thread) until the box is populated. However, if you don't want to look into the box, then you can just go about with your day while your friend does all of her Christmas shopping at the mall (ie. processes in a concurrent, parallel thread).

I have like a "mile" of learning left to do on the topic of concurrency and I feel like each concurrency-based section in this book gives me a "foot" of understanding. I'm not staying that there's anything wrong with the book; I'm just saying that concurrency continues to give me trouble. And, the fact that I'm trying to learn about it in the context of langues that don't really like "setting" variables - well, it adds another layer of complexity.

I say all this just to remind you that anything I say here should be taken with a grain of salt. I am in no way an authority on the topic of concurrency and am way more likely to mislead you than to enlighten you.

HW1: Use refs to create a vector of accounts in memory. Create debit and credit functions to change the balance of an account.

Clojure implements concurrency using a "Software Transactional Memory (STM)" approach; when it comes to ref values, it means that ref values can only be altered within the context of a transaction. The transaction takes care of locking access to the value in order to remove race conditions and deadlocks.

In the following solution, I have defined my accounts vector as a reference (ref). This requires a transaction in order to update it. I have chosen not to put the transaction construct inside of my (credit) and (debit) functions; in this way, I can run multiple updates (credit and debits) on my accounts within a single transaction. Plus, I'm beginning to think that transactions are better handled by the "controller" rather than the "model"... but this might just be ignorance speaking.

  • ;-- Use refs to create a vector of accounts in memory. Create debit
  • ;-- and credit functions to change the balance of an account.
  •  
  •  
  • ;-- Define the accounts vector. Each element in this vector will be
  • ;-- a map with the keys:
  • ;--
  • ;-- :account The account number.
  • ;-- :balance The amount in the account.
  • ;--
  • ;-- NOTE: Since this is being defined as a ref value, it can only be
  • ;-- changed within the context of a transaction.
  • (def accounts (ref []))
  •  
  •  
  • ;-- --------------------------------------------------------- --
  • ;-- --------------------------------------------------------- --
  •  
  •  
  • ;-- I credit the account with the given number by the given amount.
  • ;-- I return the updated accounts collection.
  • (defn
  • credit
  • [accounts account-number credit-ammount]
  •  
  • ;-- Since this is a vector, we don't have direct access to the
  • ;-- account with the given number. As such, we'll have to map
  • ;-- the current accounts collection onto a new collection,
  • ;-- looking at each account as we go.
  • (map
  • (fn
  • [account]
  •  
  • ;-- Check to see if the current account has the number
  • ;-- we are looking for.
  • (if
  • (= account-number (:account account))
  •  
  • ;-- This is our target account; as such, return a new
  • ;-- map with an augmented balance.
  • (assoc
  • account
  • :balance
  • (+ (:balance account) credit-ammount)
  • )
  •  
  • ;-- This is NOT the account; as such, simply return
  • ;-- the unaltered account so that it can be mapped
  • ;-- back onto the result collection.
  • account
  • )
  • )
  • accounts
  • )
  • )
  •  
  •  
  • ;-- I debit the account with the given number by the given ammount.
  • ;-- I return the updated accounts collection.
  • ;--
  • ;-- NOTE: I simply use the credit function, but with a negative
  • ;-- credit ammount.
  • (defn
  • debit
  • [accounts account-number debit-ammount]
  •  
  • ;-- Pass this operation off to the credit but negate ammount.
  • (credit
  • accounts
  • account-number
  • (- debit-ammount)
  • )
  • )
  •  
  •  
  • ;-- --------------------------------------------------------- --
  • ;-- --------------------------------------------------------- --
  •  
  •  
  • ;-- First, we need to push some accounts onto the accounts queue.
  • ;-- Since this is atlering the collection, we need perform this
  • ;-- within a transaction.
  • (dosync
  •  
  • ;-- Use the (conj) function as our mutation method. We can
  • ;-- then conjoin the given account maps onto the collection.
  • (alter
  • accounts
  • conj
  • {
  • :account "123"
  • :balance 50
  • }
  • {
  • :account "456"
  • :balance 50
  • }
  • {
  • :account "789"
  • :balance 50
  • }
  • )
  •  
  • )
  •  
  •  
  • ;-- Now that we populated our accounts collection, we can update
  • ;-- some of the accounts in the collection. Again, since this is
  • ;-- a ref value, we need to alter the collection from within a
  • ;-- transaction.
  • (dosync
  •  
  • ;-- Credit $50 to the account "456".
  • (alter
  • accounts
  • credit
  • "456"
  • 50
  • )
  •  
  • ;-- Debit $25 from the account "123".
  • (alter
  • accounts
  • debit
  • "123"
  • 25
  • )
  •  
  • )
  •  
  •  
  • ;-- --------------------------------------------------------- --
  • ;-- --------------------------------------------------------- --
  •  
  •  
  • ;-- Print out updated accounts collection.
  • (println @accounts)

As you can see, the (credit) function maps the incoming accounts vector onto an outgoing accounts vector. As part of this mapping, the credit function is scanning the accounts collection, looking for the account with the given number. If it doesn't find the given account, it simply passes the incoming account to the outgoing account. If it does find the given account, however, it passes back an account structure with an updated balance.

When we run the above code, we get the following console output:

(
{:account 123, :balance 25}
{:account 456, :balance 100}
{:account 789, :balance 50}
)

As you can see, the first account was debited $25 and the second account was credited $50.

HW2: In this section, I'm going to outline a single problem called "sleeping barber." It was created by Edsger Dijkstra in 1965. It has these characteristics:

  • A barber shop takes customers.
  • Customers arrive at random intervals, from 10 to 30 milliseconds.
  • The barber shop has three chairs in the waiting room.
  • The barber shop has one barber and one barber chair.
  • When the barber's chair is empty, a customer sits in the chair, wakes up the barber, and gets a haircut.
  • If the chairs are occupied, all new customers will turn away.
  • Haircuts take 20 milliseconds.
  • After a customer receives a haircut, he gets up and leaves.

Write a multithreaded program to determine how many haircuts a barber can give in 10 seconds.

After a few false starts on this problem, I realized that I had no idea what I was doing. As such, I wound up basically writing an interpretation of a Producer-Consumer Queue solution by Michal Marczyk on Stack Overflow. However, even with a related solution in front of me, it still took me about 2 hours to write my version.

  • ;-- The "sleeping barber" problem.
  •  
  •  
  • ;-- Define a flag that determines if the barber shop is open
  • ;-- for business (ie. we are still testing the amount of hair
  • ;-- that the barber can cut). This will be used to terminate our
  • ;-- parallel processes (as they will all cease at the COB - close
  • ;-- of business).
  • (def open-for-business? (atom true))
  •  
  • ;-- Keep track of the number of haircuts that the barber has given
  • ;-- during the day of business.
  • (def haircut-count (atom 0))
  •  
  • ;-- Define a queue to keep track of the number of customers waiting
  • ;-- to see the barber. This queue will start off empty. Since
  • ;-- multiple threads will be updating this queue (the barber and the
  • ;-- "waiting room," we'll be using a (ref) for better locking.
  • (def waiting-room (ref []))
  •  
  • ;-- Define the number of chairs in the waiting room.
  • (def waiting-room-size 3)
  •  
  •  
  • ;-- --------------------------------------------------------- --
  • ;-- --------------------------------------------------------- --
  •  
  •  
  • ;-- I open the barbershop for the day (ie. a given amount of time
  • ;-- in milliseconds for which we want to run the test).
  • (defn
  • open-shop
  •  
  • ;-- Define arguments.
  • [duration]
  •  
  • ;-- Sleep the current thread to indicate that the business is
  • ;-- running.
  • (Thread/sleep duration)
  •  
  • ;-- Now that the business day has finished, toggle the flag
  • ;-- that indicates that the business is no longer open.
  • (swap! open-for-business? not)
  • )
  •  
  •  
  • ;-- --------------------------------------------------------- --
  • ;-- --------------------------------------------------------- --
  •  
  •  
  • ;-- I bring customers into the barber shop at random intervals.
  • ;-- I launch a parallel thread (future) that occassionally checks to
  • ;-- see if new customers can be added to the waiting room.
  • (defn
  • add-customers
  •  
  • ;-- Define arguments.
  • []
  •  
  • ;-- We want this function to run asynchronously to the primary
  • ;-- thread. As such, let it run in a future where we don't have
  • ;-- to wait for a response.
  • (future
  •  
  • ;-- While we are open for business, keep trying to add a
  • ;-- new customer to the waiting room.
  • (while
  • @open-for-business?
  •  
  • ;-- Ouptut the current state of the waiting room.
  • (println "Waiting Room:" @waiting-room)
  •  
  • ;-- Lock access to the waiting room queue inside of a
  • ;-- transaction to make sure we don't have race
  • ;-- conditions with our other threads when mutating.
  • (dosync
  •  
  • ;-- Check to see if the waiting room is not full.
  • ;-- When doing this, we call (ensure) on the waiting-
  • ;-- room queue in order to allow the value to be
  • ;-- received without blocking????????
  • (if
  • (<
  • (count (ensure waiting-room))
  • waiting-room-size
  • )
  •  
  • ;-- The waiting room is not full, so add a
  • ;-- new customer to one of the empty chairs.
  • (alter waiting-room conj :customer)
  • )
  •  
  • )
  •  
  • ;-- Wait for between 10 and 30 ms before trying to add
  • ;-- another customer to the waiting room.
  • (Thread/sleep
  • (+ 10 (rand-int 21))
  • )
  •  
  • )
  •  
  • )
  •  
  • )
  •  
  •  
  • ;-- --------------------------------------------------------- --
  • ;-- --------------------------------------------------------- --
  •  
  •  
  • ;-- I cut the hair of the next person available. I launch a parallel
  • ;-- thread (future) that checks for available customers in the
  • ;-- waiting room. When a customer becomes available, I take them out
  • ;-- of the waiting room and given them a haircut.
  • (defn
  • cut-hair
  •  
  • ;-- Define arguments.
  • []
  •  
  • ;-- We want this function to run asynchronously since the barber
  • ;-- can cut hair independently of when people are coming into the
  • ;-- the shop. As such, let it run in a future so we don't have to
  • ;-- block the control flow.
  • (future
  •  
  • ;-- While we are open for business, keep checking to see if
  • ;-- there are any customers that can be serviced.... ha ha,
  • ;-- I just said "serviced".
  • (while
  • @open-for-business?
  •  
  • ;-- Check to see if we can get a customer out of the
  • ;-- waiting room. IF WE CAN, then we will cut their hair.
  • (when-let
  • [
  • next-customer
  •  
  • ;-- Lock access to the waiting room queue since
  • ;-- we are going to be popping a value off of it.
  • (dosync
  •  
  • ;-- Try to capture the "next customer."
  • (let
  • [
  • next-customer
  • (first (ensure waiting-room))
  • ]
  •  
  • ;-- We found the next customer, so update
  • ;-- the queue and return the customer to
  • ;-- the barber.
  • (when
  • next-customer
  •  
  • ;-- Pop the customer from the queue.
  • (alter waiting-room rest)
  •  
  • ;-- Return the next customer to the
  • ;-- when-let. If we return a non-nil
  • ;-- value, then the following body
  • ;-- (hair cutting) will execute.
  • next-customer
  • )
  • )
  •  
  • )
  •  
  • ]
  •  
  • ;-- If we made it this far (in our when-let
  • ;-- statement), then we DO have a next next customer.
  •  
  • ;-- (println "Cutting hair...")
  •  
  • ;-- Sleep the thread for 20ms as that is how long
  • ;-- a hair cut will take to finish.
  • (Thread/sleep 20)
  •  
  • ;-- Increment the hair cut count.
  • (swap! haircut-count inc)
  •  
  • )
  •  
  • )
  •  
  • )
  •  
  • )
  •  
  •  
  • ;-- --------------------------------------------------------- --
  • ;-- --------------------------------------------------------- --
  •  
  •  
  • ;-- Start cutting hair.
  • (cut-hair)
  •  
  • ;-- Start adding customers to the waiting room.
  • (add-customers)
  •  
  • ;-- Open the business for the day (ie. 10 seconds).
  • (open-shop (* 10 1000))
  •  
  • ;-- Ouptut the number of given haircuts.
  • (println "Number of cuts:" @haircut-count)

Since this solution is basically a rip off of someone else's solution, I won't try to explain it further. I did my best to comment the code as thoroughly as possible; but beyond that, I'm not gonna offer up too much information.

This solution makes use of the Clojure function (ensure). I am not entirely sure what this does. It has something to do with getting the value of the reference within a transaction. Apparently, it allows the current value to be gotten without causing a deadlock or something. All I know is that the solution doesn't work at all without it.

When we run the above code, we get the following console output:

Waiting Room: []
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer :customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: (:customer)
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Waiting Room: ()
Number of cuts: 481

As you can see, the barber was able to cut the hair of 481 customers in the 10 second business day. Depending on how frequently the customers came into the shop, the waiting room was either full or empty.

I really wanted to give up on this homework; but, I am glad that I didn't. True, I didn't quite come up with the final solution; but, I am at least a little proud that I was able to adapt someone else's queue onto my problem. In the end, I am just excited that I was able to put down something for every assignment in the Clojure chapter. Like I said before, Lisp was a language that gave me a lot of trouble in college. Today, I feel like I finally have the upper hand.

Well, one more language left in the book: Haskell!




Reader Comments

As someone doing some concurrent programming stuff in Groovy right now Clojure is of great interest to me. Unfortunately I haven't had time to delve into it and see how it would fit my project. I'd be interested in hearing if you have any additional exploits in this arena, and I'm gonna have to take a look at this book real soon!

Cheers!

I just worked through the barbershop problem this week and had quite a different solution. I'm not sure if you've had additional success understanding the concurrency capabilities of Clojure but there's a simpler solution for this problem using agents. An agent is assigned a thread pool and works through the functions tossed into that pool in sequential order. Using that knowledge, it is easy to simply make the barber an agent and toss him a 'cut-hair' function with a chair as a parameter that delays for 20ms and then returns the chair to the waiting room.

Additionally, I'd recommend an atom for the chair queue since there's only one queue. It's my understanding that a reference is really more for encapsulating multiple changes into a single dosync. Using an atom gets rid of a bit more code.

Cheers and happy lisping!

-John