Archive for the ‘Programming’ Category

On Selecting Clojure

Monday, March 8th, 2021

The Clojure and Scheme Compared comments about Peter Bex's Clojure from a Schemer's perspective article caught my attention. I know that discussion is about language features, but it got me thinking about the different criteria used for selecting a programming language like Clojure. E.g., it's interesting that Irreal considers the JVM a negative, while I consider it a positive. It just goes to show that every situation is unique, i.e. there is no right or wrong in these types of technology decisions.

I've only dabbled with Clojure over the last few years. See Exploring Clojure (and FP vs OOP). The real motivation was to explore the advantages of functional programming. Shifting your fundamental programming paradigm from OOP to FP has far-reaching impact. Language warts are not going to be a major factor in determining your success in doing this type of transformation.

The other major language/technology selection considerations involve organizational headwinds. For most large companies, there are three major challenges:

  1. Inertia. Convincing management that an esoteric language and techniques (FP) are worth diverting and training existing personnel is a difficult hill to climb. I also think there's a certain amount of organizational entrenchment going on here. For example, the C#/CLR vs. Java/JVM divide is really more cultural than technical. Because niche technologies like Clojure/FP are also generally viewed in this cultural context ("esoteric"), they don't stand a chance.
  2. Talent. Unless you're in Finland, it's difficult to find qualified people. This is also an on-going issue because programs developed today need to be maintained for the long-term (years). With all-remote employment now being more mainstream, maybe this will become less of an issue.
  3. Trends. As you can see from the 5-year trends below, Clojure has been on a slow decline. Also, Lisp languages are minor players. They are orders of magnitude smaller than the mainstream (Java, JavaScript, Python) and do not show up on the TIOBE top 20 lists (Lisp is #36).

Even if you're a small company or a startup, selecting Clojure is still a tough call. This would likely only happen if there were a critical mass of programmers (#2) that already had positive Clojure/FP development experiences.

There are many good reasons to choose Clojure as a front-end (JavaScript) and/or back-end (JVM) technology solution. I would love to see first-hand how well Clojure/FP performs on a large-scale project.  Unfortunately, there are also plenty of non-technical reasons that prevent organizations from choosing Clojure.

314 Digits of Pi (Python to Clojure)

Thursday, March 12th, 2020

Pi Day (3/14) is in a couple of days so I want to wish everyone a Happy Pi Day 2020! It's great to see that 55% of American's plan to celebrate and many will be eating pie or pi-themed food (whatever that is).  

My work colleague and basketball buddy Stan sells a nerd t-shirt here: 314 Digits of Pi.py. It has the Python code on the front and the results on the back. I "won" one of these at our annual White Elephant gift exchange in December. Even though the Amazon Best Sellers Rank is #12,306,667 in Clothing, Shoes & Jewelry, I really like it!

I've been staring at the code backward in the mirror for a number of months:

This got me wondering what this algorithm would look like in Clojure?

The first pass on the port was pretty straight forward, but I think it's worth noting some of the subtle differences. All of the code is here: pi-digits.

Here's the original Python code and result:

For the interested, here's an explanation of the calculation of Pi using fixed point math for speed improvements: Pi - Machin: The Machin formula, developed by John Machin in 1706 (!), is:

And here's a Clojure version that returns the identical result:

One problem with the arccot (arc-cotangent) implementation is that it just duplicates the Python logic and is not idiomatic Clojure. Instead of coding this in a non-functional style, i.e. using mutable state (atom), let's create a functional version:

We use loop/recur for a recursive implementation. This has the benefit of tail-call optimization (TCO). Here are the execution times (average of 10 runs) for calculating Pi with the three implementations:

MethodDigits
Time in seconds
10,00050,000100,000200,000
python0.1583.7414.960.3
clojure-while0.2605.1419.878.6
clojure-recur0.2525.1019.878.5

Python is certainly faster, but the purpose here was not to compare computation speed. It was to get a Clojure version of the t-shirt made! Who's the real nerd now? 🙂

Exploring Clojure (and FP vs OOP)

Sunday, January 27th, 2019

I've always viewed functional programming (FP) from afar, mostly because object-oriented programming (OOP) is the dominant development methodology I've been using (Java, Ruby, C#, etc.) for many years.  A majority of the articles I've read on FP have statements like this:

If you’ve never tried functional programming development, I assure you that this is one of the best time investments you can make. You will not only learn a new programming language, but also a completely new way of thinking. A completely different paradigm.

Switching from OOP to Functional Programming gives an overview of the differences between FP and OOP. It uses Scala and Haskell for the FP example code, but I think it still does a good job of getting the major concepts across:

I do not think that FP, or any single paradigm/language/framework/etc. for that matter, is a silver bullet. On the contrary, I'm a true believer in the "right tool for the job" philosophy.  This is particularly true in the software industry where there is such a wide variety of problems that need to be solved.

This view for programming paradigms is cute but is actually misleading:

As a developer, it's important to always to be learning new problem-solving approaches. I.e. adding new tools to your tool-belt. This will not only allow you to select the best solution(s) for the job, but you'll be better able to recognize the trade-offs and potential problem areas that might arise with any solution. I think understanding FP concepts will make you a better programmer, but not necessarily because you are using FP techniques and tools.

Functional Programming Is Not a Silver Bullet sums it up best:

Don’t be tricked into thinking that functional programming, or any other popular paradigm coming before or after it, will take care of thinking about good code design instead of us.

The purpose of this article is to present my own experiences in trying to use Clojure/FP as an alternative approach to traditional OOP. I do not believe there is anything here that has not already been covered by many others, but I hope another perspective will be helpful.

Lisp

I chose a Lisp dialect for this exploration for several reasons:

  1. I have some Lisp experience from previous projects and was always impressed with its simplicity and elegance. I really wanted to dig deeper into its capabilities, particularly macros (code as data - programs that write programs). See Lisp, Smalltalk, and the Power of Symmetry for a good discussion of both topics.
  2. I'm also a long-time Emacs user (mostly for org-mode) so I'm already comfortable with Lisp.
  3. This profound advice:

For all you non-Lisp programmers out there, try LISP at least once before you die. You will not regret it.

Lisp has a long and storied history (second oldest computer language behind Fortran). Here's a good Lisp historical read: How Lisp Became God's Own Programming Language.

I investigated a variety of Lisp dialects (Racket, Common Lisp, etc.) but decided on Clojure primarily because it has both JVM and Javascript (ClojureScript) support. This would allow me more opportunity to use it for real-world projects. This is also why I did not consider alternative FP languages like Haskell and Erlang.

Lastly, the obligatory XKCD cartoon that makes fun of Lisp parentheses (which I address below):

Why Clojure?

Why Clojure? I’ll tell you why… provides a good summary and references:

  1. Simplicity
  2. Java Interoperability
  3. REPL (Read Eval Print Loop)
  4. Macro
  5. Concurrency
  6. ClojureScript
  7. Community

Here are some more reasons from Clojure - the perfect language to expand your brain?

  1. Pragmatism (videos at Clojure TV)
  2. Data processing: Sequences and laziness
  3. Built-in support for concurrency and parallelism

There are also many good articles that describe the benefits of FP (immutable objects, pure functions, etc.).

Clojure (and FP) enthusiasts claim that their productivity is increased because of these attributes. I've seen this stated elsewhere, but from the article above:

Clojure completely changed my perspective on programming. I found myself as a more productive, faster and more motivated developer than I was before.

It also has high praise from high places:

Who doesn't want to hang out with the smart kids?

Clojure development notes

The following are some Clojure development observations based on both the JSON Processor (see below) and a few other small ClojureScript projects I've worked on.

The REPL

The read-evaluate-print-loop, and more specifically the networked REPL (nREPL) and its integration with Emacs clojure-mode/cider is a real game-changer. Java has nothing like it (well, except for the Java 9 JShell, which nobody knows about) and even Ruby's IRB ("interactive ruby") is no match.

Being able to directly evaluate expressions without having to compile and run a debugger is a significant development time-saver. This is a particularly effective tool when you are writing tests.

Parentheses

A lot a people (like XKCD above) make fun of the Lisp parentheses. I think there are two considerations here:

  1. Keeping parentheses matched while editing. In Clojure, this also includes {} and []. Using a good editor is key - see Top 5 IDEs and text editors for Clojure. For me, Emacs smartparens in strict mode (i.e. don't allow mismatches at all), plus some wrap/unwrap and slurp/barf keyboard bindings, all but solved this issue.
  2. When reading Lisp code, I think parentheses get a bad rap. IMO, the confusion has more to do with the difference in basic Lisp flow control syntax than stacked parentheses.  Here's a comparison of Ruby and Lisp  if  syntax:

Once you get used to the differences, code is code. More relevantly, bad code is bad code no matter what language it is. This is important. Here's a good read on the subject: Effective Mental Models for Code and Systems ("the best code is like a good piece of writing").

Project Management

Leiningen ("automating Clojure projects without setting your hair on fire") is essentially like Java's Maven, Ruby's Rake, or Python's pip. It provides dependency management, plug-in support, applications/test runners, customized tasks, etc.

Coming from a Maven background, lein configuration management and usage made perfect sense. The best thing I can say is that it always got the job done, and even more important, it never got in the way!

Getting Answers

I found the documentation (ClojureDocs) to be very good for two reasons:

  1. Every function page has multiple examples, and some are quite extensive.  You typically only need to see one or two good examples to understand how to use a function for your purposes. Having to read the actual function description is rarely needed.
  2. Related functions. The "SEE ALSO" section provides links to functions that can usually improve your code: if  → if-not, if-let, when,... This is very helpful when you're learning a new language.

I lurked around some of the community sites (below). The threads I read were respectful and members seemed eager to help.

Clojure Report Card

Language: A

On the whole, I was very pleased with the development experience. Solving problems with Clojure really didn't seem that much different from other languages. The extensive core language capabilities along with the robust ecosystem of libraries (The Clojure Toolbox) makes Clojure a pleasure to use.

I see a lot of potential for practical uses of Clojure technologies. For example, Clojurified Electron plus reagent-forms allowed me to build a cross-platform Electron desktop form application in just a couple of days.

I was only able to explore the tip of the Clojure iceberg. Based on this initial experience, I'm really looking forward to utilizing more of the language capabilities in the future.

FG vs OOP: B

My expectations for FP did not live up to what I was able to experience in this brief exploration. The lower grade reflects the fact that the Clojure projects I've worked on were not big enough to really take advantage of the benefits of FP described above.

This is a cautionary tale for selecting any technology to solve a problem. Even though you might choose a language/framework that advertises particular benefits (FP in this case), it doesn't necessarily mean that you'll be able to take advantage of those benefits.

This also highlights the silver bullet vs good design mindset mentioned earlier. To be honest, I somehow thought that Clojure/FP would magically solve problems for me. Of course, I was wrong!

I'm sure this grade will improve for future projects!

Macros: INC (incomplete)

I was also not able to fully exercise the use of macros like I wanted to. This was also related to the nature of the projects.  I normally do DSL work with Ruby, but next time I'll be sure to try Clojure instead.

TL;DR

The rest of this article digs a little deeper into the differences between the Ruby and Clojure implementations of the JSON Processor project described below.

At the end of the day, the project includes two implementations of close to identical functionality that can be used for comparison. Both have:

    1. Simple command line parsing and validation
    2. File input/output
    3. Content caching (memoization)
    4. JSON parser and printer
    5. Recursive object (hash/map) traversal

The Ruby version (~57 lines) is about half the size of Clojure (~110 lines). This is a small example, but it does point out that Ruby is a simpler language and that there is some overhead with the Clojure/FP programming style (see Pure Functions, below).

JSON Processor

The best way to learn a new language is to try to do something useful with it. I had a relatively simple Ruby script for processing JSON files. Reproducing its functionality in Clojure was my way of experiencing the Clojure/FP approach.

The project is here:   json-processor

The Ruby version is in the ./ruby, while the Clojure version is in ./src/json_processor. See the README.md file for command line usage.

The processor is designed to simply detect a JSON key that begins with "include" and replace the include key/value pair with the contents of a file's (./<current_path>/value.json) top-level object.  Having this include capability allows reuse of JSON objects and can improve management of large JSON files.

So, if two files exist:

and base.json contains:

And level1.json contains:

After running base.json through the processor, the contents of the level1 object in the level1.json file will replace "include":"level1", with the result being:

Also, included files can contain other include files, so the implementation is a good example of a recursive algorithm.

There are example files in the ./test/resources directory that are slightly more complex and are used for the testing.

Development Environment

Immutability

Here's the Ruby recursive method:

The passed-in object is purposely modified. The Ruby each function is used to iterate over each key/value pair and replace the included content as needed. It deletes the "include" key/value pair and adds the JSON file content in its place. Again, the returned object is a modified version of the object passed to the function. 

The immutability of Clojure objects and use of reduce-kv means that an all key/value pairs need to be added to the 'init' ( m) collection ( (assoc m k v) ). This was not necessary for the Ruby implementation.

A similar comparison, but with more complexity and detailed analysis, can be found here: FP vs. OO List Processing.

Pure Functions

You'll notice in the Ruby code that the class variable @dir_name, which is created in the constructor, is used to create the JSON file path:

get_json_content(File.join(@dir_name,v)).values.first

The Clojure code has no class variables, so base_dir must be passed to all methods, like in the process-json-file  macro:

`(process-json ~base-dir (get-json ~base-dir ~file-name))))

To an OOP developer, having base_dir as a parameter in every function definition may seem redundant and wasteful. The Functional point-of-view is that:

  1. Having mutable data (@dir_name) can be the source of unintended behaviors and bugs.
  2. Pure functions will always produce the same result and have no side effects, no matter what the state of the application is.

These attributes improve reliability and allow more flexibility for future changes. This is one of the promises of FP.

Final Thought

I highly recommend giving Clojure a try!

Bad joke:

I have slurped the Clojure Kool-Aid and can now only spit good things about it.

Sorry about that. 🙂

UPDATE (22-Aug-19): More Clojure love from @unclebobmartin: Why Clojure?

Embedding Angular Components into a Legacy Web App

Wednesday, July 25th, 2018

 In a perfect world, you'd be able to create a greenfield Angular SPA from scratch. In the real world, that's usually not the case. That legacy web application has way too much baggage to realistically convert it to an SPA in a single shot. This is particularly true if you're currently using server-side rendering with (e.g.) JSP or Rails technology.

The only real solution is to incrementally move/upgrade pieces of UI logic and data access patterns (i.e. converting to REST interfaces). If you are planning a move to Angular*, a good starting point is to first embed small pieces of Angular-implemented logic into your existing application. This approach also allows the new Angular components to share CSS styles for seamless visual integration.

NgInterop is a simple TypeScript class that allows a legacy Web application to have two-way communications (via pub/sub) with embedded Angular components. The underlying MessagingSerivce class is an implementation of Message Bus pattern in Angular 2 and TypeScript.

Source code for the demo project is here: embedded-angular

Highlights:

  • 6: Side note on the new Angular 6 providedIn syntax. This saves from having to add every service to the app.module.ts @NgModule providers list. Very handy!
  • 19: This saves the native JavaScript initialization callback function (see index.html below). This example only has one callback function, but it would be easy to extend this functionality to support multiple initialization callbacks.
  • 20: Add the NgInterop instance into the window object so that external JavaScript can simply call methods on window.ngInterop (again, see index.html below).
  • 32 and 38: Wrap the MessagingService subscribe/publish in a NgZone.run() call. This allows the external JavaScript to execute these functions in the Angular zone.

Other notes:

  • The typeClassMap object maps a BaseEvent class name (string) to a real class. The public static *_EVENT names provide safer access to the NgInterop functions from the Angular code.
  • There's no way to do type or parameter checking on the native JavaScript side, but it is still good practice to strongly type the BaseEvent derived classes. This provides good documentation and catches problems early on the TypeScript side.

Here is the stripped down index.html that shows how the external JavaScript code interacts with NgInterop.

Highlights:

  • 4: After subscribeToEvents() is called by the NgInterop constructor, this function subscribes to AngularEvent messages. AngularEvent messages are published when the Angular 'Toggle Remove Button' is clicked in the AppComponent class.
  • 10: On an HTML click event an HtmlEvent message is published. The subscriber to the HtmlEvent messages is also in the AppComponent class.
  • 13: The callback function is added to the window object. This is executed prior to Angular being started up.
  • All logging is done by publishing LogEvent messages. These are displayed by the LogComponent class.

The example app has two Angular components that interact with the native JavaScript as well as with each other with NgInterop. The rest of the code should be self-explanatory.

Screenshot of the example app:


This project uses the following:

  • Angular CLI -- Of course.
  • RxJS  -- Used by the MessagingService.
  • Bootstrap 4 -- For the pretty buttons and "card" layout.
  • Moment.js  -- To more easily format the log timestamp.
  • Protractor  -- For running the Angular e2e tests.

Enjoy!
*There are probably similar integration approaches for React and Vue. I just don't know what they are.

UPDATE (7/27/18):

Here's a React approach: Creating & Managing components outside React.

React JS Dynamic DOM Generation

Wednesday, September 13th, 2017

I had implemented an Angular 4 dynamic DOM prototype using the Angular Dynamic Component Loader and wanted to do the same thing with React JS. After doing some research I found that it was not very obvious how to accomplish this.

By the time I was done there ended up being two functional components worth sharing:

  1. Dynamic component creation using JSX.
  2. JSON driven dynamic DOM generation.

Source code for the demo project is here: reactjs-dynamic-dom-generation

Try demo app live! (with CodeSandbox)

The project was created using create-react-app. The only other package added was axios for making the AJAX call to retrieve the JSON content.

Dynamic Component Creation

With JSX, dynamic content generation turned out to be pretty simple. The core piece of code is in DynamicComponent.js:

In the demo application, all available components register themselves via the ComponentService, which is just a singleton that maintains a simple hash map. For example:

As highlighted on lines 17-18, the desired React Component is first fetched from the ComponentService and then passed to JSX via <this.component ... />.

The JSX preprocessor converted this embedded HTML into Javascript with the 'React Element' type set to the passed Component along with the additional attributes. I.e. if the UI type was 'switch', the hard-coded HTML would have been <SwitchComponent ... /> which is a perfectly acceptable JSX template.

Voilà, we have created a dynamic DOM element!

Note that Vue.js applications using JSX can use the same technique except they pass a Vue Component instead.

JSON Driven Dynamic DOM Generation

In order to demonstrate dynamic DOM generation I have defined a simple UI JSON structure. The demo uses Bootstrap panels for the group and table elements and only implements a few components.

The UI JSON is loaded from the server when the application is started and drives the DOM generation. A DynamicComponent is passed a context (i.e. its associated JSON object) along with a path (see below). Each UI element has the following attributes:

  • name: A unique name within the current control context. It is used to form the namespace-like path that allows this component to be globally identified.
  • ui: The type of UI element (e.g. "output", "switch", etc.). This is mapped by the ComponentService to its corresponding React Component. If the UI type is not registered, the DefaultComponent is used.
  • label: Label used on the UI.
  • controls: (optional) For container components ("group", "table"), this is an array of contained controls.
  • value: (optional) For value-based components.
  • range: (optional) Specifies the min/max/step for the range component.

This structure can easily be extended to meet custom needs.

There are a number of implementation details that I'm not covering in this post. I think the demo application is simple enough that just examining and playing with the code should answer any questions. If not, please ask.

The example UI JSON file is here: example-ui.json:

The resulting output, including console logging from the switch and range components, looks like this.


This is, of course, a very minimal implementation that was designed to just demonstrate dynamic DOM generation. In particular, there is no UI event handling, data binding, or other interactive functionality that would be required to make a useful application.

Enjoy!

Publishing an Angular 2 Component NPM Package

Friday, December 9th, 2016

It was suggested on the Angular 2 Password Strength Bar post that I publish the component as an NPM package. This sounded like a good sharing idea and it was something I've never done before. So, here it is.

You should go to the Github repository and inspect the code directly. I'm just going to note some non-obvious details here.

Application Notes:

  • Added in-line CSS with the @Component styles metadata property.
  • In addition to passwordToCheck, added client configurable barLabel parameter.

Project Notes:

  • src: This is where the PasswordStrengthBar component (passwordStrengthBar.component.ts) is. The CSS and HTML are embedded directly in the @Component metadata. Also, note that the tsconfig.json compiles the Typescript to ../lib which is what is distributed to NPM (app and src are excluded in .npmignore).
  • The index.d.ts and index.js in the root directory reference ./lib to allow importing the component without having to specify a Typescript file.  See the How TypeScript resolves modules section in TypeScript Module Resolution.  I.e. after the npm installation is complete you just need this:

Development Notes:

Overall (and briefly), I find the Typescript/Javascript tooling very frustrating. I'm not alone, e.g.: The Controversial State of JavaScript Tooling. The JSON configuration files (npm (package.json), Typescript, Karma, Webpack, etc.) are complex and the documentation is awful.

The worst part (IMO) is how fragile everything is. It seems like the tools and libraries change rapidly and there's no consideration for backward compatibility or external dependencies. Updating versions invariably breaks the build. On-line fixes many times take you down a rabbit hole of unrelated issues. If you're lucky, the solution is just to continue to use an older version. Use npm-check-updates at your own risk!

Feedback:

If you have questions or problems, find a bug, or have suggested improvements please open an issue. Even better, fork the project, make the desired changes and submit a pull request.

Enjoy!

Angular 2 Password Strength Bar

Wednesday, September 28th, 2016

I spent a little time updating AngularJS Directive to test the strength of a password to be a pure Angular 2 component and thought I'd share.

A working demo and all of the code can be found here: Angular 2 Password Strength Bar.

Notes:

  • Upgraded to Typescript and used the OnChanges interface.
  • Incorporation of the bar is now component-based:

<password-strength-bar [passwordToCheck]="account.password"></password-strength-bar>

  • Removed direct DOM modification and replaced with Angular 2 dynamic in-line styles.
  • Removed JQuery dependence.

Enjoy!

Old Nerds

Wednesday, April 27th, 2016

Nobody is immune from aging.

In the tech industry, this can be a problem as described in Is Ageism In Tech An Under-The-Radar Diversity Issue?.  Programmer age distribution from the Stack Overflow Developer Survey 2016 Results clearly shows this:

2016-Stack Overflow Developer Survey 2016 Results

Worth noting:

  • 77.2% are younger than 35.
  • Twice as many are < 20 then are over 50.

Getting old may suck, but if problem-solving and building solutions are your passion being an old nerd (yes, I'm way over 35) really can look like this:
nerd-age-satisfaction
There's a lot of reasonable advice in Being A Developer After 40, but I think this sums it up best:

As long as your heart tells you to keep on coding and building new things, you will be young, forever.

I sure hope so! 🙂

UPDATE 13-Oct-16: Too Old for IT

Deep Learning

Sunday, December 6th, 2015

deepLearningAI500I recently attended a Deep Learning (DL) meetup hosted by Nervana Systems. Deep learning is essentially a technique that allows machines to interpret sensory data. DL attempts to classify unstructured data (e.g. images or speech) by mimicking the way the brain does so with the use of artificial neural networks (ANN).

A more formal definition of deep learning is:

DL is a branch of machine learning based on a set of algorithms that attempt to model high-level abstractions in data by using multiple processing layers with complex structures,

I like the description from Watson Adds Deep Learning to Its Repertoire:

Deep learning involves training a computer to recognize often complex and abstract patterns by feeding large amounts of data through successive networks of artificial neurons, and refining the way those networks respond to the input.

This article also presents some of the DL challenges and the importance of its integration with other AI technologies.

From a programming perspective constructing, training, and testing DL systems starts with assembling ANN layers.

For example, categorization of images is typically done with Convolution Neural Networks (CNNs, see Introduction to Convolution Neural Networks). The general approach is shown here:

Construction of a similar network using the neon framework looks something like this:

Properly training an ANN involves processing very large quantities of data. Because of this, most frameworks (see below) utilize GPU hardware acceleration. Most use the NVIDIA CUDA Toolkit.

Each application of DL (e.g. image classification, speech recognition, video parsing, big data, etc.) have their own idiosyncrasies that are the subject of extensive research at many universities. And of course large companies are leveraging machine intelligence for commercial purposes (Siri, Cortana, self-driving cars).

Popular DL/ANN frameworks include:

Many good DL resources are available at: Deep Learning.

Here's a good introduction: Deep Learning: An MIT Press book in preparation

Creating a Minimally Sized Docker Image

Wednesday, September 23rd, 2015

dockerThis is a follow up to the Publishing a Static AngularJS Application with Docker post.

Relative to the size of a standard Ubuntu Docker image I thought the 250MB CoreOS image was "lean". Earlier this month I went to a Docker talk by Brian DeHamer and learned that there are much smaller Linux base images available on DockerHub. In particular, he mentioned Alpine which is only 5MB and includes a package manager.

Here are the instructions for building the same Apache server image from the previous post with Alpine.

The Dockerfile has significant changes:

Explanation of differences:

line 2: The base image is alpine:latest.

lines 4-5: Unlike the CoreOS image, the base Apline image does not include Apache. These lines use the apk package manager to install Apache2 and clean up after.

lines 6-7: Runs the exec form of the Dockerfile ENTRYPOINT command. This will run httpd in the background when the image is started.

line 8: The static web content is copied to a different directory.

Building and pushing the image to DockerHub is the same as before:

Because of the exec additions to the Dockerfile, the command line for starting the Docker image is simpler:

The resulting Docker image is only 10MB as compared to 290MB for the same content and functionality. Nice!

UPDATE (12-Jun-17): Here's an even smaller image: Stuffing Angular into a Tiny Docker Container (< 2 MB)