clj-net-pcap Version 1.7.1 Released

I actually uploaded clj-net-pcap version 1.7.1 quite some time ago. Unfortunately, I was very busy and couldn’t even write an announcement. In this post, I briefly announce the clj-net-pcap version 1.7.1 release.

I did the work on clj-net-pcap version 1.7.1 in preparation for the :clojureD conference. The most relevant changes for version 1.7.1 are:

  • Use latest cli4clj version.
  • Add stderr-forwarder-fn.

My motivation for these changes was to provide a better live demo of clj-net-pcap. With the latest cli4clj version, the interactive command line interface (CLI) offers more convenient functionality.

With the stderr-forwarder-fn, the output printed for each packet is printed to stderr instead of stdout. By redirecting stderr to a named pipe or file, it is now possible to use the interactive CLI without the CLI being flooded with the packet output. This, in my opinion, eases the use of clj-net-pcap for simple experiments or demonstrations a lot.

Below, a screenshot of a demo setup is shown. On the top left, the clj-net-pcap interactive CLI is shown. On the top right, another shell is shown that was used to generate traffic via the ping command. On the bottom, the output from the named pipe to which the stderr output was redirected is shown.

The following listing shows how the named pipe is created and the new stderr forwarder is used to redirect the packet capture output to it:

mkfifo stderr-pipe
java -jar clj-net-pcap-1.7.1-standalone.jar -F stderr-forwarder-fn 2> stderr-pipe

The remainder of the example as shown in the screenshot above is simple to print what is written to the pipe via “cat” and to generate the traffic via “ping”.

I hope that this will be as useful for you as it was for me. Comments etc. are, as usual, highly appreciated. Thanks.


Writing a Tiny FRP Library

To gain a better understanding of how FRP might be implemented, I wrote a simple FRP library.

(def clicks      (atom nil))
(def click-count (reduce* (fn [acc x] (inc acc)) 0 clicks))
(def evens       (filter* even? click-count))
(def as-string   (map* str click-count))

(sub* evens
  (fn [count]
    (prn "Count is even:" count)))

(sub* as-string
  (fn [count]
    (prn "Count as string:" (str count))))


Implementation leans heavily on Clojure’s atom watch functionality. To support multiple watches on a “stream”, sub* serializes it’s function and turns it into a keyword.

(defn fn->keyword [f]
  (keyword (str f)))

(defn trigger* [xs & [event]]
  (reset! xs event))

(defn sub* [xs f]
  (add-watch xs (fn->keyword f)
    (fn [_ _ _ x]
      (f x))))

The basic operators take an atom, create and return a new one, and subscribe to changes on the original. When a new “event” occurs, they update the new atom with a transformed version of the event. On the surface, this gives the illusion of a pure “derivation” of streams.

(defn map* [f xs]
  (let [ys (atom (f @xs))]
    (sub* xs #(reset! ys (f %)))

(defn reduce* [f init xs]
  (let [acc (atom init)]
    (sub* xs #(swap! acc f %))

(defn filter* [f xs]
  (let [ys (atom nil)]
    (sub* xs
      #(when (f %)
         (reset! ys %)))

(defn union* [xs ys]
  (let [zs (atom nil)]
    (sub* xs #(reset! zs %))
    (sub* ys #(reset! zs %))


Of course, nothing prevents you from calling reset! on a derived stream or computation. For a less leaky, more idiomatic Clojure solution, check out core.async’s support for transducers.


ClojureScript Ejecta

Ejecta is a fast browserless implementation of HTML canvas for iOS and tvOS.

It's very easy to set things up so that you can drive Ejecta using ClojureScript.

First, download Ejecta, which ships as an Xcode project. Also, ensure that you have CocoaPods installed.

We'll make a few minor modifications to the Ejecta project which will allow us to establish a ClojureScript REPL into iOS and drive Ejecta:

Create a Podfile file in the top of the Ejecta project tree:

platform :ios, ’10.0’

target 'Ejecta' do
  pod 'Ambly', '~> 0.7.0'

With this in place, run these commands

$ pod install
$ cp /dev/null App/index.js

Then open Ejecta.xcworkspace find AppDelegate.m and make a couple of mods to hook in Ambly:

Before @implementation AppDelegate, add these lines

#import "ABYContextManager.h"
#import "ABYServer.h"

@interface AppDelegate ()
@property (strong, nonatomic) ABYContextManager* contextManager;
@property (strong, nonatomic) ABYServer* replServer;

and add these lines prior to the return YES; at the end of application:didFinishLaunchingWithOptions:

NSURL* out = [[[NSFileManager defaultManager] URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] lastObject];
self.contextManager = [[ABYContextManager alloc] initWithContext:((EJJavaScriptView*)window.rootViewController.view).jsGlobalContext compilerOutputDirectory:out];
[self.contextManager setUpAmblyImportScript];
self.replServer = [[ABYServer alloc] initWithContext:self.contextManager.context compilerOutputDirectory:out];
[self.replServer startListening];

With these revisions, run the modified Ejecta app in the simulator or on a device.

In another directory set up a small ClojureScript project. Do lein new ejecta and edit the resulting project.clj to specify [org.clojure/clojurescript "1.9.521"] and [org.omcljs/ambly "0.7.0"] as :dependencies.

Then lein repl and connect by evaluating these two forms and then choosing your device from the list presented:

(require '[cljs.repl :as repl] '[ambly.core :as ambly])
(repl/repl (ambly/repl-env))

You can now draw on the Ejecta canvas element by evaluating ClojureScript like this:

(let [ctx (-> js/document
              (.getElementById "canvas")
              (.getContext "2d"
                           #js {:antialias        true
                                :antialiasSamples 4}))]
  (set! (.-fillStyle ctx) "#FFFFFF")
  (.beginPath ctx)
  (.arc ctx 300 150 100 0 (* 2 Math/PI))
  (.fill ctx)
  (set! (.-lineWidth ctx) 12)
  (set! (.-strokeStyle ctx) "#96CA4B")
  (.beginPath ctx)
  (.arc ctx 300 150 86 1.57 4.71)
  (.stroke ctx)
  (set! (.-strokeStyle ctx) "#5F7FBF")
  (.beginPath ctx)
  (.arc ctx 300 150 86 4.71 1.57)
  (.stroke ctx)
  (.fillRect ctx 294 55 12 190))

With any luck, you should see:

Of course, there is much more you can do, such a loading namespaces containing drawing code using require, or bundling all your code using :advanced and including that in the app, but above minimal example should get you started.

Have fun!


New GPU game engine

This post is about a new GPU game engine, billboards, forest and kovasb/gamma.

The source code is here.

I decided to scratch the idea of using a web worker to run the engine. Instead I want to run the engine on the GPU, using WebGL shaders. This allows to scale to a huge number of units (target 64k). But at this scale I can’t render expensive geometries anymore, so I will render billboards. This page explains how to do it. I am using solution #2 from that page. In addition, I need to discard transparent fragments in the fragment shader, as described here. Otherwise transparent fragments write to the depth buffer and block other fragments without setting a color, and that looks very glitchy. I am also using instancing to make it fast. Today I implemented only rendering a 2D sprite on the billboards, but I will switch it up later by rendering one instance of a 3D model to a texture at current camera angle, and then displaying that on all the billboards. I think it will be a decent approximation.

At large scale, multiplayer will also be a lot harder, and would have to be some kind of lock-step or turn based solution, so I think I will drop it, or maybe have 2 modes, swarm mode for single player and few units mode for multiplayer.

Also, a lot of my current work, like explosions, magic stars and attack vectors will need a rethink and reimplementation with the new GPU engine.

Here is a screenshot of a forest using trees on the new engine unit billboards. It runs 64k trees at 60 FPS. screenshot of forest

I decided to start using the ClojureScript library kovasb/gamma for writing GLSL shaders functionally in a ClojureScript DSL rather than directly. Note that hendekagon/gamma on Clojars is a more recent packaging, and provides necessary fixes to for example aget for vector and matrix indexing. Gamma buys me composability. It’s a bit hard to get started, since documentation is not perfect, but you can piece it together from the Wiki, tutorial, the test code, examples and the source code itself. Using imperative hacks like the “discard” statement is hard though, because gamma is purely functional and only supports assignments at the top level. So since I needed “discard” I resorted to putting a “magic” vector vec4(0.0, 1.0, 2.0, 3.0) and doing a string search/replace of that line with discard. I opened an issue for gamma.

Next up is collision detection. At first I was thinking I needed a bounding volume hierarchy, asking a question on StackOverflow, but after asking on the EFNet IRC channel I am reconsidering a simpler grid approach. The problem is that WebGL does not have atomic compare-and-exchange, so storing multiple objects per cell, for example in a linked list, is hard. I will write about my workaround if and when I find a solution.


Data wrangling with transducers for a machine learning problem

Data wrangling with transducers for a machine learning problem

The transducers from the net.cgrand.xforms library are great to transform and analyze data with in Clojure. This blogpost shows how the xforms transducers can be used to do data analysis for a machine learning problem from Kaggle, which is a data science competition platform.

One of the competitions on Kaggle is the Titanic competition. For this competition you are given a dataset about passengers aboard the Titanic, with data such as their age and how much they paid for their ticket. In the training data you are also told if the passenger survived. The goal of the competition is to predict if a passenger survived or not for a test set of data.

Read more


The REPL returns

The REPL returns

The REPL returns with a spec split.
View this email in your browser



Long time between drinks for The REPL. There's been family illness + work stuff that delayed me writing, but it's back now.


Libraries & Books.

  • Matthew Jaoudi mixes up some baking-soda for combining Reagent and Reactstrap
  • spec-provider infers a spec based on sample data
  • restpect does integration tests for HTTP APIs
  • datofu provides common utilities for Datomic

People are worried about Types. ?



Recent Developments.

  • The big Clojure news this week was Clojure 1.9 split spec out into its own library to help Clojure 1.9 move towards production while keeping spec in alpha.
  • Much (all?) of Clojure's infrastructure is now served over HTTPS
  • Var serialisation serialises identity, not value


  • Techniques for Efficiently Learning Programming Languages
  • Making software testing easier with Clojure
  • On the judicious use of core.async
Copyright © 2017 Daniel Compton, All rights reserved.

Want to change how you receive these emails?
You can update your preferences or unsubscribe from this list

Email Marketing Powered by MailChimp


Abstracting the Clojure def-macro pattern

Wrapping a function in a ‘def-macro’ is a common Clojure pattern. This practice serves two purposes: providing a nicer syntax for definitions, and controlling evaluation order. Although simple to understand, this pattern is somewhat repetitive. Introducing: a higher-order macro that relieves this pain.

(defmacro defserver [name & args]
 `(def ~name ~(apply run-server args)))

The example above uses httpkit’s run-server function to define a defserver macro. With defmacro<-, it can be written as follows.

(defmacro<- defserver run-server)

; Usage
(defserver server routes {:port 8080}) 

Implementation was a little tricky, because I had to keep track of two different compile-time execution contexts: defining a macro, and using that macro to define something.

(defmacro defmacro<-
   (make-def-macro (def-sym f) f false))
  ([name f & [eval-after]]
   (make-def-macro name f eval-after)))

(defn def-sym [f-sym]
  (symbol (str "def" f-sym)))

Both normal and macro (eval-after) evaluation are supported, to accomodate for the two most common usecases.

(defn make-def-macro [name f-sym eval-after?]
  `(defmacro ~name [name# & args#]
     (apply->intern ~f-sym name# args# ~eval-after?)))

(defn apply->intern [f name args eval-after?]
  (intern *ns* name
    (if eval-after?
      (eval (apply f args))
      (apply f (map eval args)))))


This macro solved a common annoyance for me. I hope you find it as useful as I do, and encourage you to replace some boilerplate with it.


XT16 - Looking beyond Clojure

At JUXT we are a huge fans of Clojure, but we like to always keep an open mind. Therefore we asked our resident 'agent provocateur' Martin Trojer to take us through the Clojure alternatives, although as Martin puts it, we'll pretty soon discover that:

All roads lead to Haskell.

Martin will try to convince us that the 'time spent typing types' pays immense dividends. Enjoy.


Machine Learning With Clojure

Machine Learning with Clojure

From the past 3 blog posts on Artificial Intelligence (AI), Machine Learning (ML) and Deep Learning (DL), you should by now, have an idea of the basics. I hope I have whet your appetite by the potential for ML, but the some of apprehensions that surround it too.

The good news is that you are still interested! So the next natural step is to ask, "How can I start Machine Learning myself, and build an AI that'll do all my coding for me?" (Or ... is just my dream?) Luckily, there are many open sourced resources such as TensorFlow, by Google, Amazon Lex, Polly and Rekognition, by Amazon and Microsoft's Cortana (are you getting the idea?), to name but a few.


TensorFlow, released in November 2015, is an open sourced library by the Google Brain Team. Originally called DisBelief, Google uses TensorFlow across a range of it's products and services. From the obvious, deep neural networks for search rankings, to the subtle, automatic generation of email responses and real time translations.

The main language for Machine Learning, for much of it's history, is in Python, as it is built upon C/C++. Python also has a very well developed and powerful scientific computing library NumPy, allowing users to do very efficient and complex neural networks with relative ease. Due to the longevity of Machine Learning's relationship with Python, the community is strong and libraries are rich.

But, "what if I don't want to use the bad concurrency models in Python and use the sane concurrency model in Clojure?" I hear you cry! Luckily, TensorFlow has just released it's Java API. Although it is still new and comes with a warning that it is experimental, you have the first in-roads to TensorFlow and Clojure. Along with the majority of Java libraries, TensorFlow has it's own Maven Repository so getting started with TensorFlow in Clojure is as easy as adding

[org.tensorflow/tensorflow "1.1.0-rc1"]
to your project and then importing it into your namespace.

(ns tensor-clj.core
  (:import org.tensorflow.TensorFlow))

(defn myfirsttensor []
  (println (str "I'm using TensorFlow version: " (TensorFlow/version))))

Comparing the TensorFlow Python API, to the TensorFlow Java API, you will immediately see a massive disparity in the libraries available, making operations that are relatively straightforward in Python, difficult in Java and hence Clojure.

However, for computing matrices and other statistical operations, Java has a wealth of options such as ND4J. If you'd rather use a Clojure Library, there is Incanter, Core.Matrix or Clatrix that can have similar capabilities.

An alternative option is to do your TensorFlow operations (or Machine Learning Operations) in Python and simply call you results in Clojure. The beauty of programming is that you are not penned into one language, you have the ability to choose based on your personal preferences.

For an interesting article that goes into more depth about Clojure, Deep Learning and TensorFlow, follow here.

Amazon Services

Amazon have recently released 3 different Deep Learning sources, Lex, Polly and Rekognition.

Amazon Lex

Amazon Lex is a service used for conversations by either voice or text. It comes with a ready-made environment allowing you to quicky make a natural language "chatbot". Amazon Lex comes with slack integration so you can build a chatbot in any case you desire (it can respond to any pesky chat messages on your behalf). The technology you'd be using with Lex is the same that is used by Alexa. This means the text and voice chats are tried and tested, being used in a commercially viable product that can be bought by you and me.

With Lex, the cost is $0.004 per voice request and $0.00075 per text request, so you are able to a large number of interactions with you chatbot for a low cost.

Amazon Polly

Amazon Polly is a service that turns text into speech. Polly uses deep learning technology to turn text (words and numbers) into speech. It sounds like the human voice and can speak 24 languages. You are able to cache and save the speech audio to be able to be replayed it when you are offline, or if you want distribute it.

For Polly, you pay per character converted from text to speech. As you have the ability to save and replay speech, for testing purposes the cost is low.

Amazon Rekognition

Amazon Rekognition is used for image analysis. The "Hello World" equivalent for deep learning is MNIST, classifying images of dags and cats. Rekognition gives you the ability to do just that, with the added benefit of visual search. The technology is actively used by Amazon to analyse billions of images each day for Prime Photos. A deep neural network is used to detect and label the details in your image, with more labels and facial recognition features continually being added.

Testing with Rekogntion allows you to analyse 5000 images per month and store 1000 face data each month for 12 months.

Coming from Amazon, Lex, Polly and Rekognition come ready with integration into your existing Amazon infrastructure, and are able to coexist with other Amazon WebServices such as S3 and Elastic BeanStalk.

All 3 Amazon products are now available for you to test. All you need is an Amazon account and you are up and running.

The products are out-of-the-box, meaning you can easily get them up and running, having a simple working product in a short amount of time. However, due to this ease of starting up, it means the learning gradient of customisation is steeper.


Microsoft's Cortana, released in 2014 as a competitor to Google Now and Siri is a digital personal assistant for Windows 10, Windows 10 Mobile, Windows Phone 8.1 Microsoft Band, Xbox One, iOS and Android. Cortana provides many different services:

  • Reminders and Calander Updates
  • Track Packages, Flights
  • Send Emails and Texts
  • Access the internet

Cortana uses Microsoft's Cognitive Toolkit (CNTK), a Python based deep learning framework which allows a machine to recognize photos and videos or understanding human speech by mimicking the structure and functions of the human brain. Using CNTK, you have access to ready-made components, the ability to express your own neural networks. You are also able to train and host your models on Microsoft's alternative to Amazon Web Services, Azure.


A Clojure library for neural networks is Cortex. An excellent article on the capabilities of cortex can be found here

As it is written in Clojure, Cortex has the obvious benefit of being easily integrable into your current application. However, it doesn't have the same backing of major companies nor the longevity of other frameworks. An advantage is that Cortex is being actively developed with improvements being made consistently. If you want a more tried and tested framework to begin your deep learning journey, Cortex may not be the right framework for you, rather something for you to come back to once you have a better understanding.

Other Machine Learning Systems

There are many more ML and DL frameworks such as Caffe, Theano, and Torch. Caffe is targeted at mass markets, for people who want to use deep learning for applications. Torch and Theano are more tailored towards people who want to use it for research on DL itself.


Caffe is a deep learning framework, created by Berkeley AI Research. It is Python based that gives Expressive Architecture, Extensible Code, Speed and already has an established community.


  1. High Performance without GPUs
  2. Easy to use - Many features come "Out-of-the-Box"
  3. Fast


  1. Outside Caffe's Comfort Zone, difficultly increases massively
  2. Not well documented
  3. Uses 3rd party packages so may lead to versions skewing


Theano is also a Python Library. Although not strictly a Deep Learning Framework, it allow you to efficiently evaluate mathematical expressions with multi-dimensional arrays. Theano, works along the same lines as TensorFlow, using multi-dimensional arrays as tensors. Theono uses automatic differentiation on a neural network, which is the bases of backpropagation.


  1. Lazy Evaluation
  2. Graph Transformation
  3. Supports Tensor and Linear Algebra Operations
  4. Gives a Good Long term understanding
  5. Developed and helpful community
  6. Automatic Differentiation (When you look into the Mathematics behind Neural Networks, you begin to realise how much timeand effort this saves!)


  1. Can get Stuck in the Nitty Gritty Details
  2. Graph Optimizations increase the compilation time
  3. Long Overheads due to Optimizations


Torch is a Machine Learning Library, released in 2002, based on Lua. Torch also provides an N-Dimensional Array (Tensor) that has the basic operations for indexing, slicing, transposing, type-casting, resizing, sharing storage and cloning. Torch is maintained and used by a variety of companies, including Facebook AI Research, Google's Deep Mind and Twitter.


  1. Flexibility and Speed
  2. Torch Read Eval Print Loop (TREPL) for debugging. If you are coming from aprogramming language that already utilises the power of the REPL, then you can see the immediate benefits.
  3. Provides ports to MAC, Linux, Android and iOS
  4. Written in Luajit which is a JITlanguage, so there is almost zero compilation overhead for the computational graph. It is extremely easy to integrate Lua with an existing C/C++/Fortran code.
  5. Very easy to create a (deep) neural nets right out of the box without additional libraries
  6. Actively developed


  1. Written in Lua so not accessible to many people
  2. Doesn't have complete automatic differentiation

For a more comprehensive comparison across multiple deep learning software's, go here.

To Conclude ....

Although it's been around for a while, Machine Learning and Deep Learning have only come to the fore-front of the programming world in the past 5 years. People were just starting to test the waters at the beginning of the century and so much has happened in a small amount of time.

Now we come to Clojure. Clojure is even younger, so the community doing both Clojure and Machine Learning is very small indeed. Luckily Clojure has Java and all it's resources to fall back on. As Machine Learning and Clojure separately become more popular the community using both technologies will grow alongside.

The Deep Learning Libraries and Frameworks I have provided in this article are by no means an exhaustive list. They have their own subtle differences, enough for you to make your own personal choice. However, it is important to say that there are many other examples out there, but I feel these are some great choices to get you going.


Why Functional Programming?

Why Functional Programming?

11 Reasons to Learn Functional Programming

1. Functional Programming is Fun

I was this close to quitting software engineering. I was tired of jobs writing PHP or Java or JavaScript. I wanted a challenge and something interesting. What drew me back as I was about to leave? Functional Programming.

2. Functional Programming is Growing

I started doing functional programming back in 2001. Back then, if you talked to people online, even those who had been programming for a while, there was no hope for getting a job in Lisp. It was like an urban legend. People knew people who told stories about people who had jobs. But mostly they remembered how easy functional life was in the 1980s.

But it's different now. People are getting paid to work in Clojure, Haskell, Elixir, Erlang, Scala, and more. They're talking about them at conferences. They're blogging and tweeting FP. FP is a viable career path.

Check out this graph of survey results:

Future Languages (Intent to learn)

Scala, Swift, Clojure, Haskell, Erlang, Elixir, and F# are all in the top 15.

Source: O'Reilly 2016 European Software Development Salary Survey

3. Two Ways are Better than One

Some people are going to tell you that Functional Programming is better than the other paradigms. Some people will say Object Oriented is better. They might be right. They might be wrong. But what is clear is that a person knowing Object Oriented and Functional is better off than someone only knowing Object Oriented. It's like having a hammer and a saw. Two different tools let you do more.

4. Functional Programming Makes You Look Smart

When I moved back to New Orleans in 2012, I told someone that I was working in Haskell. He literally dropped down and did this:

We're not worthy!

It was half joking and totally unjustified. But only half joking.

You don't have to be a genius to learn functional programming, though that is the stereotype. It's unfortunate. Functional programming is very accessible. But, of course, you can keep that a secret if you want to impress people 😉

Joking aside, knowing how to program in a different paradigm will help you fill niches others can't. You'll solve problems that were made for FP and it will look like magic.

5. You Will Learn Monads

Monads are not that big of a deal. But learning them is a challenge. There is a law of the universe that says that once you "get" monads, you have an incredible desire to share them with the world but lose all ability to communicate how they work.

The best way to learn monads is to approach them gradually. You can use Haskell very productively without learning monads. Eventually, just by exposure, you'll get them. That's what happened to me.

6. Functional Code is Shorter

You may have heard it before, but I'll say it again: Functional code tends to be shorter than equivalent code in non-functional languages. See this paper about a project at Erlang and their estimates of how much shorter it is than an equivalent C++ program. Spoiler: C++ is between 4 and 10 times longer than Erlang. Here is a quote:

Some concrete comparisons of source code volume have been made, as applications written in C++ have been rewritten in Erlang, resulting in a ten-fold reduction in the number of lines of uncommented source code. Other comparisons have indicated a fourfold reduction. A reasonable conclusion is that productivity increases by the same factor, given the same line/hour programmer productivity.

When measuring product quality in number of reported errors per 1000 lines of source code, the same relationship seems to exist: similar error density, but given the difference in code volume, roughly 4-10 times fewer errors in Erlang-based products.

7. Functional Code is Simple

In the Clojure world, the concept of "simplicity" has an objective meaning. We have taken to agree with the research done by Rich Hickey and his powers of presentation. In Simple Made Easy, Rich Hickey presents a definition of simplicity that deals with how intertwined things are. It's not about "ease".

That definition is what I mean. But the concept applies to the broader functional programming world as well.

8. Functional Programming will Improve All of Your Programming

Programmers who understand more than one paradigm are better programmers. They have more tools in their toolbox. So even if you're writing Object Oriented code at work, knowing functional programming concepts will help you with that code. In the end, the principles of good code are the same. OO and FP simply approach the same problem from different angles. Seeing in multiple angles will let you solve your problem in a better way.

9. Functional Programming has a Long and Storied History

Lisp was invented in 1958 and has been copied and reimplemented countless times since then. Each time, important lessons were learned. Those lessons are felt when you're coding with it. Functional programming is one of the first paradigms (invented around the time of procedural) and has fed the other paradigms ideas and features since its inception, including the if statement. You will learn a lot just by working in a language with so much history.

10. Functional Programming Makes Concurrency Easier

Immutability and pure functions were made to work on multiple CPUs.

11. Functional Programming Embodies Good Software Practices

In college my professors gave me lots of advice about best practices in software. But the code for the languages we used didn't follow those practices. It was hard to accept their advice. I would have to implement the advice by discipline and hard work--with no help from the language. Then when I got into functional programming, I realized that functional programming did all of those good practices by default.

Want to transition your career to Functional Programming? Sign up for my 10-part email course called The Functional Programming Career Guide.

Get on the mailing list!


Copyright © 2009, Planet Clojure. No rights reserved.
Planet Clojure is maintained by Baishamapayan Ghose.
Clojure and the Clojure logo are Copyright © 2008-2009, Rich Hickey.
Theme by Brajeshwar.