The caveats of Julia for image processing (ii) – the image structure

Once Images and other image processing packages are loaded, inputing a colour image is relatively easy. What is somewhat perplexing is how the image data is returned and stored in Julia. From this perspective I am thinking of the novice doing image processing.

The image milano.png

Ideally, they would be able to access the “raw” data, stored in a simple multi-dimensional array. Look a grayscale image is just a 2D array, nothing fancy, easy to manipulate. The problem with img is that it isn’t a simple structure, or rather it is stored as a 2D array of pixels, where each pixel is a struct. Maybe that’s how it should be?

> using Images
> img = load("milano.png")
> summary(img)
"756×756 Array{RGB{N0f8},2} with eltype RGB{Normed{UInt8,8}}"

N0f8 is a special type that Julia uses to encode channel values in range 0–255 to range 0–1 (Normed(UINt8,8) is an 8-bit type representing 256 values from 0.0 to 1.0). This means that the RGB triplet of a pixel is (0.77,0.22,1.0). Each component of an RGB image is 8-bit, i.e. has values from 0-255. Making them real numbers is odd, and imprecise I would imagine. You can see what it does by running the following code for grayscale and colour images:

> reinterpret(N0f8, UInt8(197))
0.773N0f8
> reinterpret(N0f8, [UInt8(197), UInt8(56), UInt8(255)])
3-element reinterpret(N0f8, ::Array{UInt8,1}):
 0.773N0f8
 0.22N0f8
 1.0N0f8

Sorry, I don’t like using floating-point numbers to represent pixels. Call me old-fashioned, I know how imprecise non-integers are. I get it, for some image processing it’s impossible to avoid floating-point calculations. There is supposedly some logic behind the use of N0f8, it’s just not very intuitive, in fact it’s quite the opposite. This may be because things have been over-thought. The work-around? Images does provide functions for people to get the raw data: channelview() and colorview(). The first splits out the channels of an image, the second combines them back together. Here we split the image img, and then extract individual slices.

img2 = channelview(img)
imgR = img2[1,:,:];
imgG = img2[2,:,:];
imgB = img2[3,:,:];

This provides a 3×756×756 floating-point reinterpretation of the N0f8 representation (img2), and three slices representing the Red, Green and Blue components of the image. But they are still floating-point. To change it to 8-bit, you have to do a further bit of magic which will create an Int64 representation. Note, don’t be tempted to change it to Int8 (-128..217), or UInt8 (0..255) (which is annoyingly stored as hex).

img3 = trunc.(Int,img2.*255)

Converting to other colourspaces is not hard, but the process does return a N0f8 image by default. The second line code below converts the N0f8 image to floating-point.

imgG = Gray.(img)
imgGf = Float64.(imgG)

Other conversions seem less taxing. For example converting to YCbCr or HSV returns a 2D array of pixels, each of which is still a struct, but at least normal floating-points.

imgYCbCr = YCbCr.(img)
imgHSV = HSV.(img)

Look, I understand why it seemed like a good idea to store an RGB image as a 2D array of structs, but manipulating the individual R, G, and B components is easier if they are a 3D array. I mean, it seems like the storage is less, with a single element inning taking up 1 byte, not surprising if the underlying storage of UInt8 is 1 byte. It might be easier if Julia didn’t store unsigned integers in hexadecimal. An unsigned 8 bit number just goes from 0 to 255, why change that?

The caveats of Julia for image processing (i) – overview

I’ve been using Julia for about four years now. Early on I really liked it, now I’m not so sure, and it’s not because I think its any less a language than it was before. When I started out using it, it was the pre-V1.0 days. I thought that it would stabilized somewhat when it reached V1.0, at least for a small while. I recently installed it on my new Apple MacBook Pro with the M1 chip. It (version 1.5) installed easily, no qualms what-so-ever. Now I’m mostly interested in using it for image processing, and a couple of years ago I started work on an image processing library… then I stopped work because of the “unstable” environment with respect to new versions. The version hopping hasn’t really abated. I see V1.6 is in beta, and V1.7 is in development. Why, why oh why? Maybe stick for one version for a year, then release another version. With every new release I have to wonder if my code stills works, or if some small change will break it. If I code stuff in Fortran 95 at least I know what I’m getting.

One of the reasons I chose Julia over the likes of C to do image processing it the number of built-in functions to do things like statistics (yeah, Java and C++ have libraries too, but I dislike these languages). I tried Python for a while, but its tooooooo slow. I also like the ability to write plain code (e.g. two nested for loops), and not have to vectorize things just to make them work faster (here’s looking at you again Python). The ability to slice arrays is awesome as well. What I am not enjoying so much is the inherent complexity of the language. There is almost too much crammed into the language. Some of this complexity added by people who implement add-on packages. They usually add a bunch of OO stuff, and don’t always make things clear, and the documentation is often less than useful. Now packages are great, they help people get on with their work, and not have to write things from scratch. Julia also has a cool built-in package manager which makes installing them *awesomely* easy. The help functionality of Julia is also cool. But there are some things about packages which are annoying.

Take for example the JuliaImages package. I installed it, and started using it to rewrite a Retinex algorithm from Matlab to Julia. I got it working, but it took some effort. Here are some of my observations. Firstly there are a lot of packages under the umbrella of JuliaImages. Installing the basic package Images.jl just installs an overview, if you want additional functionality you have to add it. So in the end you can end up adding a whole lot of packages (thankfully Julia’s package manager makes them easy to manage). One real bugbear is I/O. It specifically says that an I/O backend is required to load and save images, because that package is not integral to JuliaImages. Images includes a front-end FileIO.jl which chooses the appropriate back-end, depending on the file-format. This allows support for an incredible diversity of file formats, but it seems kind of clunky.

The image automatically displayed using ImageInTerminal

Another issue is image display. There are a number of packages that support image display, one of the most interesting is ImageInTerminal, which supports image display in the terminal (the displayed images are downscaled to fit into the size of the current active terminal session). But image display requires another series of packages… it just seems like too much sometimes. The other issues have to do with comprehension. The documentation for Julia in general can sometimes be lacking in simplicity. For the novice Julia user, trying to wade through technical documentation can be challenging, as examples are often vague or non-existent. There is also the problem of the horrible error messages that can be generated when scripts are run. For the experienced programmer, they are not an issue, but that is because they understand how an error can be traced.

The problems with Julia.

I’ve been using Julia for about four years now. Early on I really liked it, now I’m not so sure, and it’s not because I think its any less a language than it was before. Below I’m going to try and summarize some of the “global” problems with Julia.

Version hopping

When I started coding in Julia, it was the pre-V1.0 days. I thought that it would stabilized somewhat when it reached V1.0, at least for a small while. I recently installed it on my new Apple MacBook Pro with the M1 chip. It (version 1.5) installed easily, no qualms what-so-ever. Now I’m mostly interested in using it for image processing, and a couple of years ago I started work on an image processing library… then I stopped work because of the “unstable” environment with respect to new versions. The version hopping hasn’t really abated. I see V1.6 is in beta, and V1.7 is in development. Why, why oh why? Maybe stick with one version for a year, then release another version? With every new release I have to wonder if my code stills works, or if some small change will break it. At least if I code stuff in Fortran 95 at least I know what I’m getting.

One of the reasons C and its brethren have been so successful over the last five decades it that they just don’t change much. C was designed as a somewhat simple language, and despite its flaws, has remained simple. Sure, it gets a new spec. every few years, with some subtle changes, but there is nothing major, not even fixing that stupid dangling-else problem. But Julia just has too much stuffed into it, and things keep changing. I don’t want to write code, and then have to rewrite parts of it because somebody changed their minds.

It’s getting bloated

Julia tries to be “everything to everyone”, but this seldom works well. Remember Algol68? It was an inherently bloated language. This was primarily because it was developed by committee, with far too many people wanting far too many things incorporated into the language. It is in some respects becoming as complex as C++, and everyone knows how that ended (Hint: Never use C++ to build a jet fighter).

A debugging nightmare

I have real issues with the usability of error message produced bye Julia. They are horrible, and they haven’t improved over the years. Programmers with some experience will be able to figure things out [eventually], but novice programmers will be lost trying to understand them. Here is a simple example of a piece of code I wrote (which worked in a pre-1.0 version). All it does is calculate interest earned on a principal amount at a certain interest rate.

println("Enter principal amount: ")
P = parse(chomp(readline()))
println("Enter interest rate (0-100): ")
r = parse(chomp(readline()))
r = r / 100.0
println("Enter period of time: ")
t = parse(chomp(readline()))
I = P * r * t
println("Interest = ", I)

Not much to it really. But when I try and “compile” this, I get the following:

Enter principal amount:
100
ERROR: LoadError: MethodError: no method matching parse(::SubString{String})
Closest candidates are:
  parse(::Type{T}, ::AbstractChar; base) where T<:Integer at parse.jl:40
  parse(::Type{T}, ::AbstractString; base) where T<:Integer at parse.jl:237
  parse(::Type{T}, ::AbstractString; kwargs...) where T<:Real at parse.jl:376
  ...
Stacktrace:
 [1] top-level scope at interest.jl:2
 [2] include(::Function, ::Module, ::String) at ./Base.jl:380
 [3] include(::Module, ::String) at ./Base.jl:368
 [4] exec_options(::Base.JLOptions) at ./client.jl:296
 [5] _start() at ./client.jl:506
in expression starting at interest.jl:2

This can be described in one word, blech. Now I know the error is the fact that the specs for parse() changed, and it should now be “P = parse(Int, chomp(readline()))”. But if I were a novice programmer, I would run away screaming. Again, an experienced programmer will realize there are options for function specifications provided, the novice won’t, or won’t understand them. Then there is the stack trace. Yuck. They might have been better off with more traditional error messages, or just a simple “Does not compute“.

“Does not compute”

Documentation blues

Julia provides a good amount of documentation, but let’s face it, it isn’t always exactly easy to find what you are looking for. Even when you do find it, it rarely really tells you what you wish to know. There aren’t many examples, and to be honest it just reads dry, boring, uninformative except for the technical details. Trying to find help online is almost as frustrating because there isn’t much out there (or it too is poorly written). If you want to learn some basics, head to GeeksforGeeks, LearnXinYMinutes, or JuliabyExample.

Libraries

There are a bunch of libraries out there, more than there use to be anyways. Libraries for image processing are available, and do an okay job. The problem lies sometimes with the code behind these libraries, and again their lack of documentation. There are many good people from many disciplines that write programs, and I have no problem with that, but they often have less of an understanding of the building software than people in the field. Things that should be simple can become complicated.

And so…

Don’t get me wrong, there are many things I like about the design of Julia. The fact that it terminates control structures with and end, is much nicer than C’s use of { }, although some might no doubt disagree. One-based indexing is great too, because I never liked 0-based indexing (which is useful in some situations, but again *very* challenging for novice programmers to understand). Those who like 0-based indexing generally have never programmed in anything but a C-based language. Julia’s strongpoint is numerical calculations, but it’s not a perfect language by far. If I didn’t want to build things from scratch I would likely just stick with Fortran. There was a time when I recommended it as a language to teaching programming in, but even for that, I’m not so sure anymore.

Julia has succeeded in part due to its speed. Were it slow it would likely never have made it out of the starting blocks. Don’t get me wrong it has some great features, but I fear there are too many cooks in the kitchen and the result will be a very bloated language. Its instability and innovation may contribute to its downfall if its developers aren’t careful.

What I really dislike about Julia? It’s scope.

There are things that just niggle at you all the time. Most of us haven’t had to worry about the likes of scope that closely in decades. You create a global variable, it’s global. You create a variable inside a subprogram, it’s local to that subroutine. Simple right? The problem comes then with dynamically typed languages where variables don’t need to be declared before they are used. They are “declared” at the time of use. So a variable used within a loop for the first time, should be accessible after the loop is finished. I mean that’s how it works in Python. But that’s not how it works in Julia. Consider a piece of Julia code like this:

for i in 1:10
   x = i + i
end
println(x)

Now the println() will fail because the scope of x is inside the for loop, and result in a “x not defined” message. If we try to make x “global” by setting it to zero before the loop (and changing the expression to sum the values of i):

x = 0
for i in 1:10
   x = x + i
end
println(x)

It fails again, leading to a message of the form:

Warning: Assignment to x in soft scope is ambiguous because a global variable by the same name exists: x will be treated as a new local. Disambiguate by using local x to suppress this warning or global x to assign to the existing global variable.

This is because although global variables exist in all scopes, from Julia V1.0 on they are read-only in local scopes. This means that in order for this to work, access to global variables must be made explicit (otherwise it is read only in the local scope of the loop).

x = 0
for i in 1:10
   global x = x + i
end
println(x)

This is extremely annoying to say the least. But, wait it gets even more stupid. There is an exception to the rule. If we encapsulate the second piece of code inside a function, it works.

function sum(n)
   x = 0
   for i in 1:n
      x = x + i
   end
   return x
end

println(sum(10))

So why two differing rules? I don’t quite get it, and it is likely even more confusing for the novice programmer. This is a good example of when language designers do something that just doesn’t make sense. Maybe it’s because there are so many people involved in the design of Julia. A global variable is by its very nature, global.

Julia is bad at recursion

If you are thinking about using Julia to implement recursive algorithms, forget it. It just won’t work properly, well not unless you have some idea what the depth of recursion will be… but that’s hardly the point of recursion. So you could likely do Fibonacci(40) with few problems. Anything with indeterminate recursive depth, or a lot of internal variables though will suffer at the hands of Julia.

The hard limit a the stack size (kbytes) on a system can be found using ulimit, but its no panacea:

% ulimit -Hs
65520

Which is seemingly still a lot, at 65 megabytes (on OSX 11.1). The problem with Julia is weird, because Python has similar constraints, yet is able to run Slowsort(). Julia produces the following error when faced with sorting a mere 25 numbers:

ERROR: LoadError: StackOverflowError:
Stacktrace:
[1] recur(::String) at /recur.jl:2 (repeats 79984 times)
in expression starting at /recur.jl:5

This is different to Python, which also has issues with recursion, but there is it based on the value of the “recursion limit”, or depth of the recursion. In Python this can be fixed by setting new depth:

sys.setrecursionlimit(100000)

Julia on the other hand does not have an internal limit to the stack size, but uses the limits of the operating system. Now this doesn’t mean recursive depth. How many recursive calls will fit on the stack then depends upon both your system and the complexity of the function itself. Now let’s do an experiment. With the system stack set at max, we will run the following simple recursive function, with very little in the way of resource overhead.

function recur(var::String)
   recur("Ai")
end

recur("ai")

The outcome is still the same, a stack overflow. But the value 79984 is repeated. So what is going on here? To dig deeper, we calculate the depth of recursion before failure (DORBF). All we really do is count the number of times the function is called.

x = 0

function recur(var::String)
   global x = x + 1
   print(x," ")
   recur("A")
end

recur("a")

In this case the depth of recursion is 209,181, which is reasonably deep. If we apply the same principle to the Slowsort() function, we get a depth of 130,738.

The moral of the story is don’t use Julia for recursion, I mean it really was never designed for that.

Why is Julia not more popular?

I truly like Julia, both from the perspective of how the language is designed, and the speed at which is undertakes tasks like processing images. I like Python too, its just too slow, and I don’t want to have to vectorize code. Why then is Julia not more popular? Python is ranked in the top 5 most popular languages, this despite the fact that it is often as slow as molasses flowing. Julia is lightning fast. It processes so fast, Python is still thinking about starting to read the data in.

Why are people not flocking to Julia?

  • Stability. Since it was launched in 2012, it has had numerous releases. Version 1.0 was finally released in August 2018, and the most recent release in August 2020 is V1.5. Too many small changes and tweaking. It makes one nauseous just thinking about small things in your older code that potentially won’t work. Too many releases. V1.6 is due to be released in on Sept.30, 2020. Crazy.
  • Contributors. Methods of open source language development such as Julia are a neat idea. It has supposedly had contributions from over 870 developers worldwide. Ever tried to cook with more than 1-2 people in the kitchen? Just say-in.
  • General-purpose. The term general-purpose is tricky. A general-purpose language is essentially one that does everything. Bad move. That’s like saying duct tape is a general-purpose fix-it? Although originally designed for numerical type programming, it can apparently now do low-level systems programming, and web programming. Stop-the-madness.
  • Multi-paradigm. It’s procedural, it’s functional, it’s a bit of everything.
  • Too large. Although languages like C can be challenging for novice programmers, their core benefit is brevity. Julia is anything but small. Gargantuan is more likely the word. The core of the programming structures is simple and easy to learn, but there is more and more baggage with every version – this is what happens when features keep getting added. Remember what happened to C++.
  • No executable. Despite all its abilities, Julia does not generate an executable, which is a bummer.
  • Immature packages. Add-ons (yes, there is still a need for these), are not sufficiently mature, or even well maintained. This is related to the lack of users in the field. People aren’t willing to commit time to a library that will hardly be used.
  • Error message are still horrible. I thought we might progress here, but for the novice programmer, dealing with the error messages is horrendous. It’s enough to send you running towards Python.
  • The name. Why call the language Julia? (there is no specific reason) Why not something more meaningful, like a tribute to one of the great women in computing – Hopper? Easley? Coombs?

Look, I really like Julia. I really love all the embedded math functions, makes things easier than building them from scratch, but are things getting somewhat out of hand? Does a language need to do soooooo much? If I want a low-level systems language, I have C. Maybe the problem is that I’m getting old, I mean I still enjoy coding in Fortran.

Programming made easy – if statements

It’s not hard to make programming concepts appear simple. In fact you can do it with very little effort.

Humans make decisions on a daily basis, so it goes without saying that systems must also make decisions, albeit in the guise of algorithms. Decision, selection, or conditional statements, as they are often referred to, are the basic logical structures of programming. A conditional statement resembles a fork in the road; there are at least two paths that may be taken and one must be chosen. Consider the following description of an if statement:

The if statement allows a program to choose between two alternative paths by testing the value of an expression.

Programs “flow” from start to end, activating each instruction as they proceed. For example, consider the following statements:

1  radius = 3.784
2  height = 12.3
3  areaCir = pi * radius^2
4  volCyl = areaCir * height

The statements are activated in sequence from line 1 to line 4. An if statement is a control structure that allows for a branch to occur in the flow of control in a program: if a certain condition exists, then perform one action, else perform another action. Here is what a basic if statement looks with only one option:

The code in the program moves linearly from statement A, until it encounters an “if” statement. At the if statement, some condition is tested. If the condition is true, then the program activates an addition piece of code (made up of one or more statements). If the condition is false, nothing happens, and the program bypasses the if statement and continues on to statement B. You will notice that there is no else portion of the decision, as it is usually optional – you can just have the if do something if the condition is true, otherwise it does nothing. If there is an “else” part to the decision, activated when the condition is false, the diagram is just modified, so that the if statement activates the piece of code in the else portion. Here is what the if-else looks like visually:

The if statement is pretty ubiquitous in programming languages, and the only thing that really changes is its syntax (how it is constructed). Some languages use the word then, others assume that the statements after the condition are associated with the “true” part of the branch – as is the case with Julia. 

In the Julia example below, a branch is made based on whether a person worked <= 40 hours, or more than 40 hours. Based on the value of hours, a different value of pay is calculated. This value may then be used to calculate taxes owed, or some other things.

if (hours <= 40.0)
   pay = rate * hours
else
   pay = rate * (40.0 + (hours-40.0)*1.5)
end

Here is the same example written in Fortran, the only difference is that Fortran uses the keyword then, and terminates using “end if” instead of just the end of Julia.:

if (hours <= 40.0) then
   pay = rate * hours
else
   pay = rate * (40.0 + (hours-40.0)*1.5)
end if

Python is a little different again, because it uses indenting instead of terminators like “end“.

if hours <= 40.0:
    pay = rate * hours
else
    pay = rate * (40.0 + (hours-40.0)*1.5)

Finally here is C, which in its simplest form looks like this:

if (hours <= 40.0)
   pay = rate * hour;
else
   pay = rate * (40.0 + (hours-40.0)*1.5);

Note that while the syntax changes, the fundamental concept of how an if statement works is the same. Make a decision, branch in a program.

Calculating π with Gregory-Leibniz (iv) – Julia just for fun

Just for fun, let’s also calculate π using Julia. It actually did quite well. Calculating  5 billion terms took just over 7 seconds, and the result was:

3.141592653788201

Here’s the Julia code:

function pi_leibniz(n)
   x = 1.0
   numer = -1.0
   denom = 3.0

   for i = 1:n
      frac = numer / denom
      x = x + frac
      numer = -numer
      denom = denom + 2.0
   end
   x = x * 4.0

   return x
end

@time pi = pi_leibniz(5000000000)
println(pi)

The nice thing about Julia (and I am becoming somewhat biased) is that it automatically assigns x the type Float64 (and the code is much cleaner).

A simple Julia example – The Yorkshire Estates

To illustrate how easy it is to make programming somewhat fun, consider the  the following puzzle, “The Yorkshire Estates“, described by Henry Ernest Dudeney in his book on puzzles “The Canterbury Puzzles”.

I was on a visit to one of the large towns of Yorkshire. While walking to the railway station on the day of my departure a man thrust a hand-bill upon me, and I took this into the railway carriage and read it at my leisure. It informed me that three Yorkshire neighbouring estates were to be offered for sale. Each estate was square in shape, and they joined one another at their corners, just as shown in the diagram. Estate A contains exactly 370 acres, B contains 116 acres, and C 74 acres.

Now, the little triangular bit of land enclosed by the three square estates was not offered for sale, and, for no reason in particular, I became curious as to the area of that piece. How many acres did it contain?

Yorkshire Estates puzzle diagram

The task is to derive a way of calculating the area of the triangular piece of land. I won’t go through the whole description of the problem, and how to create the algorithm, because you can find that here. Basically you can use the fact that each estate is square in shape, along with its area to determine the lengths of the three sides of the triangle. The area of the triangle can then be calculated using Heron’s formula. Let’s just look at the Julia program.

println("What is the area of each estate?")
println("Estate 1: ")
area1 = parse(Float64, chomp(readline()))
println("Estate 2: ")
area2 = parse(Float64, chomp(readline()))
println("Estate 3: ")
area3 = parse(Float64, chomp(readline()))
side1 = sqrt(area1)
side2 = sqrt(area2)
side3 = sqrt(area3)
semiP = (side1 + side2 + side3)/2.0
areaTri = sqrt(semiP * (semiP-side1) * (semiP-side2) * (semiP-side3))
println("The area of the triangle of land is ", areaTri, " acres.")

This teaches the basics of problem solving, keyboard input, output, variables, and calculations. Nothing more is needed for an introductory problem.

 

You could think like a computer scientist, but do you want to?

There are many books on programming in the bibliosphere. Some are good, some mediocre, and some are very technical, targeted towards a particular audience. Think Julia (How to Think Like a Computer Scientist)  is a book relegated to the latter category. It’s title alludes to the fact that you could think like an computer scientist, but the question is do you want to? One of the reasons that people shy away from learning to program is the “tech” aspect of it. By proclaiming your book as CS centric you will automatically reduce your audience.

The book contains a good amount of information in its 276 pages – at least it is not a 1000 page behemoth. The authors do however slip into the same issue many technical writers do – a lack of thought for the abilities of their audience, assuming their audience are non programmers (which they seem to do, saying that  “No formal prior knowledge is required.”) . People from varied walks of life likely would like to learn how to program, and Julia is indeed a good language in which to learn. The problems include (i) the number of chapters dealing with higher-end language concepts and (ii) the  lack of cohesive and relatable examples. It’s not alone, there are few good programming books out there. Part of the problem is the complexity of programming languages. There are no books that just teach the basics of programming using a series of languages as examples. They use to exist in the 1970s and 80s. Now programming books all seem to be the size  of War in Peace, or are too esoteric to read.

I wanted to like this book. If it’s audience was anyone but novice programmers I would not have an issue. But the way the book reads, just doesn’t present an easy read for the novice, non-CS individual wanting to learn programming. First, maybe start with the concept of data – something that underpins all computing. Jensen and Wirth did it in their small book: Pascal: User Manual and Report. It doesn’t take a lot, and Julia is *all* about data. After data, and variables, I think books should cover control structures, because you can build programs without functions, but this book chooses the function route first. It’s not terrible, as long as only assignment statements are used to illustrate what’s inside a function (these functions don’t return values, that comes in a later chapter). The problem arises when one hits the “stack diagrams”, or “void functions” sections. Really? This is exactly why this is a book for computing-centric people, and not for novices. People who write programs to solve programs don’t want to know what’s under the hood.

Describe a programming concept, illustrate the concept using data people understand. Leave out things that aren’t relevant. Recursion is a good example. Many CS students don’t understand it, so don’t beguile the novice programmer with it. It will seem like Harry Potter magic. The fact is books on programming could be simpler than they are. Take the simple concept of making decisions. Strangely enough in this book recursion is covered in a chapter entitled “Conditionals and Recursion” – why I do not know – would it not be better associated with functions? (or not included at all). The same chapter covers “Keyboard Input”. Choose one topic, stick to it.  In Ch.7 we finally hit iteration – arguably more important than recursion. Start the chapter off with variable reassignment, and end with algorithms, a topic that would have been better dealt with at the start of the book, and not in a chapter on iteration. From there on the book deals with higher level topics for the remaining 176 pages – strings, arrays, dictionaries, tuples, files etc.

Summing up, I think this book is perfect for someone who knows the basics of programming, but not for someone who has never programmed before. Julia is about data, and as such the examples used to illustrate concepts should reflect this. The examples used throughout the book are typically CS centric as well –  Factorial, Fibonacci and Ackermann to illustrate recursion (which pops up as a topic twice) – not exactly practical examples of how recursion is useful for solving problems. There are case studies, for example one which looks at word puzzles, but there seem to be more exercises than any sort of methodology for solving a particular word puzzle. Maybe look at some financial or environmental data, or even image data, which isn’t that complex to do with the tools available.