Useful Functions cli-time Forgot

October 3rd, 2016

Clojure.jpgI'm a huge fan of clj-time - it's made so many of the time-related tasks so much simpler. But as is often the case, there are many applications for time that don't really fall into the highly-reusable category. So I don't blame them for not putting this into clj-time, but we needed to find the start of the year, start of the month, and start of the quarter.

The slight wrinkle is that the quarter could be on a few different starting cycles, so we had to be able to allow for that.

 (defn start-of-year
   "Gets the start of the year (midnight, 1 Jan) for the specified
   date-time."
   [dt]
   (date-time (year dt)))
 
 (defn start-of-month
   "Gets the start of the month (midnight) for the specified date-time."
   [dt]
   (date-time (year dt) (month dt)))
 
 (defn start-of-quarter
   "Gets the start of the quarter for the specified date time.
 
   This function assumes the standard quarterly cycle (Jan-Apr-Jul-Oct).
   A different cycle can be generated by providing the 1-based index
   (e.g., 1 => Jan) of the cycle start month."
   ([dt]
     (start-of-quarter dt 1))
   ([dt start-month]
     (let [offset (mod (- (month dt) start-month) 3)]
       (minus (start-of-month dt) (months offset)))))

None of this is Rocket Science, but it's nice to not have to mess with these conversions in the remainder of your code - which is kinda the point of the functional part of the language - no?

Getting the Name of a Clojure Function

September 13th, 2016

Clojure.jpgWe wanted to be able to log, to the database, the name of the function that was called and there was no obvious way to do that. There were some half-baked ideas, but we really needed to have a little more robust scheme. So we made this:

  (defn fn-name
   "Function to get the name of the function passed in from the basic
   information of the function - or in the case of a hooked function, the
   metadata placed on that function by the hook."
   [f]
   (-> (or (:robert.hooke/original (meta f)) f)
       (class)
       (str)
       (cs/replace #"^class " "")
       (cs/replace #"\$" "/")
       (cs/replace #"_" "-")))

where:

  [clojure.string :as cs]

is in the namespace's require function.

This will take the normal function - or one that Robert Hooke has captured for additional magic, and return it to the caller.

Very handy thing to have for logging... very.

Adding CORS to Ring Middleware

August 16th, 2016

Clojure.jpgBret and I finally got the CORS support for ring middleware working in our applications. It required that we get the starting point for the CORS support from another project, and then augment it to make sure it was working for all the cases we needed.

Basically, you need to start with:

  [rwilson/ring-cors "0.1.9"]

in your project.clj, and then in the server.clj for the library, we reference it:

  [ring.middleware.cors :as cors]

and then create the middleware function:

  (def wrap-cors
    "This is a simple convenience definition for enabling the CORS support in
    middleware for a service. The use of this will be far simpler than including
    all this in every service."
    #(cors/wrap-cors % :access-control-allow-origin #".+"
                       :access-control-allow-headers ["Origin" "Content-Type"
                                                      "Accept" "Authorization"
                                                      "Last-Modified" "Credentials"
                                                      "X-Request-Id" "X-Session-Id"]
                       :access-control-allow-methods [:get :put :post :delete]))

And with this, you can then simply put this into your middleware stack and it takes care of all the work:

  (def app
    "The actual ring handler that is run -- this is the routes above
     wrapped in various middlewares."
    (-> app-routes
        wrap-json-with-padding
        (wrap-ssl-redirect { :ssl-port (cfg/jetty :https-redirect) })
        handler/site
        wrap-cors
        wrap-params
        wrap-cookies
        wrap-logging
        wrap-proxy-loggly
        wrap-gzip))

Interesting Rounding Issue in Clojure

August 12th, 2016

Clojure.jpgWe have a shared library that has a lot of really useful functions in it for all the apps we build, and one of the really useful things is to round to a fixed number of decimal places. Now, this isn't Rocket Science, but it is nice to have written well once, and then not have to mess with ever again.

The trick is that you need it to be fast for all Clojure datatypes as well. And several of the more common implementations aren't very fast due to boxing and the overhead associated with that. So we made some pretty simple little functions:

  (defn to-2dp
    "Function to simply resolve a number to a decimal (double), and then round
    it to 2 DP and return it. Pretty simple, but very useful."
    [x]
    (/ (math/round (* 100.0 (parse-double x))) 100.0))
 
  (defn to-3dp
    "Function to simply resolve a number to a decimal (double), and then round
    it to 3 DP and return it. Pretty simple, but very useful."
    [x]
    (/ (math/round (* 1000.0 (parse-double x))) 1000.0))

but we had a problem. Some of the inputs gave us a lot of grief because of some nasty rounding. And it wasn't all values - it was just those values that had a hard time being expressed in the floating point format.

The solution was to accept some of the inefficiency of rat eJVM BigDecimal and the advanced representation it had, and use that:

  (defn to-2dp
    "Function to simply resolve a number to a decimal (double), and then round
    it to 2 DP and return it. Pretty simple, but very useful."
    [x]
    (/ (math/round (* 100.0 (bigdec (parse-double x)))) 100.0))
 
  (defn to-3dp
    "Function to simply resolve a number to a decimal (double), and then round
    it to 3 DP and return it. Pretty simple, but very useful."
    [x]
    (/ (math/round (* 1000.0 (bigdec (parse-double x)))) 1000.0))

and then things settled down.

Upgraded My Volvo

August 8th, 2016

Volvo

Today I finally upgraded my 2002 Volvo V70 XC to a 2016 Volvo XC70, and wow! is it a nice car. I've been looking for a few months because my old V70 was over 193,000 mi, and it was making some noises that were going to be several thousand dollars to fix. There was nothing wrong with the car - other than old age, and a lot of miles. It has served me very well these three years, but it was costing about $400 to $500 a month in repairs, and there were plenty of signs that a long trip to Indy was not a really advisable journey.

So I looked at the same car - just newer - and there were some really good deals on year-end clearance at the dealerships around me. So I decided that today was as good a day as any, and went and drive the new model.

What a dream! Now, to be honest, the 2002 V70 was a great car to drive. Even with the age, it was smooth, easy to drive, and very comfortable. The newer model is on a different chassis, and with a different power-plant and lots of interior goodies, it is just an amazing car to drive. But then it's a Volvo - and to me, that's amazing engineering.

Plus, with the new frame, I've got about 15% more storage in the back which means I can really sleep in the back with a few blankets. And it's got all the goodies. Very nice.

Fun Visit with a Friend

July 16th, 2016

Path

Today a friend came to Naperville to walk around and see the town. It was fun because we got to go to Eggs, Inc. for a really nice breakfast, and their omelettes are really quite good. Then we just walked around, talking about the town and a lot of nothing. We stopped at Einstein Bros. Bagels and sat in the outdoor area - it was nice. Chatted about college, decisions, things.

Walked into a new outfitting shop next to Barnes and Noble, first time for me, and it was a lot like REI. Interesting. Fun to see things. We walked around more... down on the Riverwalk, and then up through the park. It was a nice time.

After that, we went to Home Depot and picked out some flowers and things for their patio. It was fun talking about planting, how many, what colors. It was just plain fun.

I had forgotten what fun with another person felt like. Bittersweet in many ways.

Upgrading Home Wireless Networking

July 8th, 2016

Air Port Extreme

I've finally decided that I've had enough with the sad state of my wireless networking. To be honest, the wired networking is going pretty well. I've got the best DOCSIS-based Comcast service (75Mbps) and I'm on the list for the 1Gbps. I've thought about the 2Gbps fibre, but that seems like a steep $300/mo for the service. I'd really like to see what 1Gbps works like, so we should be able to see later this year.

Anyway, the service is not all bad. It comes into their modem/router/hotspot, and then out the back of that I had an older WiFi Router that just kept giving me issues. Sure, it was 3 yrs old, but it shouldn't have had that hard a life - but it's just not that reliable. And the coverage in my bedroom is really pretty disappointing.

So it was time to upgrade. But to what?

I looked at a lot of the "Best of" lists, and they were all nice, and then I read another review saying why folks might want to buy Apple's AirPort Extreme networking - configuration simplicity. And he was right.

Yes, their network equipment is not the best price/performance of all the routers out there. But face it - it's Apple - so it's going to be easy to configure... easy to monitor... easy to upgrade... all the things that I've not done a lot of with my old router because it's just not that simple. Sure... I know how to do it, but it's not as clean and easy as Apple's work.

So I went to the local Apple Store, and got a 3TB Time Capsule, and an AirPort Extreme for the bedroom, and set down to setting them up. What an amazing experience!

The Time Capsule properly detected that the Comcast router did NAT, so it didn't set that up. It also picked everything up for nice defaults. All told - it was a few minutes on my laptop, and it was very easy. The AirPort Extreme was just as easy. It detected the Time Capsule and suggested a bridge, and it worked perfectly! Just as simple as could be.

This is why I got the Apple equipment. I can look at things from my iPhone, monitor things, update code... it's simple. It's powerful. And it works like a champ.

Fantastic.

I Love the Sound of the Train

June 6th, 2016

Metra Engine

I was just sitting in my office at home and heard the sound of a train rolling by my house. I can't be 50 yards from the tracks, and I just love it. When I was a little kid, I'd visit my grandparents in a little town in upstate Indiana, and from the room we'd see in, we could hear the trains go by. They were a lot further away than the trains that run by my house, but I loved the sound then, and maybe I love the sound now because of what it meant to me then.

Memories are powerful things. I'd like to hold onto the good ones, and let go of the bad, but life isn't like that. You have to accept the bad ones, and enjoy the good. That's what life is about.

Sometimes Life is Unpopular

May 3rd, 2016

Bad Idea

This morning I was dealing with yet another change to some clojure functions that are in a library that we have created at The Shop to make it easy to make the services that we make, and easy for new clojure developers to wrap their heads around. It's not perfect, or ideal, but it does strike a nice balance between purely functional code and re-use and state in the library.

Face it - connection pooling is state. It needs to exist, but it's not what you'd like to have if you could avoid it. So you bury it in a library, so that the new clojure developers can use this library with the connection pools clearly hidden off screen doing their thing.

It's a compromise. But one that favors the new clojure developer. And most days, that's the folks I have to work with. They typically know Java, or C#, but it's a rare individual that comes into the shop with a lot of clojure experience, and usually they "get it" about the decisions, and it's something they pretty easily adapt to.

But then there are others.

Today I've been dealing with Steve. Steve is a C# developer that hates his job. He's hated it for a long time. He hates the language he's using (C#). He hates the tools he is having to learn to do his job. He pretty much hates everything about his job.

But he wants to work in clojure.

Yet he's not really interested in learning the landscape. He wants to change it to be how he thinks is should be. I don't blame him - he questions a lot because he trusts no one. Having asked him this direct question, and receiving the affirmative response, I know beyond a shadow of a doubt, this is exactly who he is. But to not learn what is before suggesting what should be is a little short-sighted.

That he's annoying about it is just "icing on the cake" for me.

So this morning I decided that emotion has no place in this conversation. Neither does his opinions. I have listened, I have tried to explain, I have listened more, and tried to show why this is the path we have taken. All of this is immaterial to him, and so in the end, I have to make a decision, list the reasons for this decision, and then move on.

I don't relish being a jerk. Or a dictator. But at this point, more conversation is wasting time. It's time to accept that this is the paradigm we are working under, and that's it.

I do not expect him to like it.

Another Month in the Books

April 29th, 2016

Path

I don't claim to know what the future will bring. I'm doing my best to just make it day-to-day most of the time. I'm lucky in that I have a decent job, and it affords me a nice distraction - most of the time. I have a support system that seems to work pretty well, and on the whole, I am making it day-by-day.

I was reading about the Five Stages of Grief and one of the things that really amazed me was that when talking about the final step - Acceptance - "...a gift not afforded to everyone.", it is marked by a withdrawal and calm.

I don't know why I was surprised to read this - I mean, I'm going through a loss, but I expected to "find myself" in one of the previous stages. And to be honest, I think I find moments of anger, or bargaining, from time to time - but I always end it with the resignation that it would never work, and for better or worse, this is my path.

Finding out that not everyone reaches Acceptance was also a bit of a shock - because as they say your life is never the same. Never. It's just a new "normal", and you make new routines, and new paths, and you get on with living.

Anyway... it's another month in the books, and I'm still here.