The basics of configuring the Z shell (on a Mac)

On Macs, the default shell is the zsh, or Z shell. Here are some of the basics for setting things up. The shell has a bunch of files it executes at startup, some in the users home directory. Every time you open up a new terminal window, potentially all configuration files will be run.

  • .zprofile / .zlogin – Sets the environment for the login shells, just get loaded at different times.
  • .zshenv – Sets environment variables.
  • .zshrc – Sets the environment for interactive shells. (optional)
  • .zlogout – Cleaning up things when the shell exits. (optional)

What order are they loaded?

.zshenv → .zprofile → .zshrc → .zlogin → .zlogout

But do you need them all? Not really. The most commonly used file is .zshrc. It’s a place where you can set parameters like $PATH, $PROMPT, and aliases. So what to put in .zshrc? Well, you might have to do some research here, because shells like zsh generally provide a lot of options.

First thing, download a better terminal, such as ITerm2. Then change some of the basics.

Shell History

It’s handy to be able to remember previously executed commands. By default, zsh does not save its history when the shell exits. The history is lost when a terminal window is closed. To make zsh save its history to a file when it exits, a variable needs to be set in the shell:

HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history
HISTSIZE=4000
SAVEHIST=2000

Here a file, .zsh_history, is created in the users home directory. HISTSIZE indicates how many lines of history to keep in memory and SAVEHIST how many lines to keep in the history file. There are also a whole bunch of other options, for example setopt SHARE_HISTORY, which shares history between all sessions, and setopt APPEND_HISTORY which appends rather than overwrites.

Aliases

Aliases just help as text replacements for long commands. For example:
alias la='ls -la'
alias h='history'

Prompts

Everyone wants a different prompt, right? The default prompt is generally “username@hostname current-directory”. It may be more useful to have more information on where I am, because I know who I am. Here is a quick prompt which shows the last 2 elements of the current path (which you can change of course to any number you want).

PROMPT='%2~ %# '

PATHS

Adding new software often requires the location of the new software to be added to the path. This is easy done using export. For example, to add the path to homebrew:

export PATH=/opt/homebrew/bin:$PATH

This adds “/opt/homebrew/bin” to the path as well as prefixing the original path declared as $PATH.

Further reading:

You don’t know the power of the shell

There was a time when learning to program in a Unix shell was considered paramount. You learned the shell before you ever thought about learning C. You learned to program in some form of shell, and there were many, not that they differed a great deal though. Without the shell, Unix would not have been as powerful as it was (and is). Shell scripts allowed for the powerful Unix utilities to be bound in such a manner that they could be used to perform magical things. Is there a best Unix shell? Unlikely. Everyone has their preferences for one reason or another. It’s kind of like asking what the best programming language is.

The problem is that with shell scripts you really need to know what you’re doing. Like really. It’s easy to run some shell script that will do something nefarious if you don’t understand what you are doing. For example there are many ways to increase the load of a system. I remember as an undergrad, we had a department Unix server, a Pyramid 90, as I remember it. To put this into context, this machine had about 4MB of memory, and ran at 8MHz. There was always one faculty member who liked playing games on the system, Larn and Rogue. Problem is they would play games during time when they should have been teaching. So people would run up the load on the system to dump the instructor off. I don’t think the they ever clued in.

Many people don’t understand that using rm means that a file is gone forever. Like forever. I’m sure there are people who could recover it, but those people are super-super-super-users. I don’t even want to show some of the dangerous scripts because someone will try them. I you are interested, here’s a article that might be of interest, 8 Deadly Commands You Should Never Run on Linux. Just read it, don’t run any script in the article.

The thing is, as a computer scientist you have to know something about the shell… even if it is as simple as creating and manipulating the shell config file… which for zsh on a Mac is .zshrc. Understanding the shell provides insight to a very powerful world of processing at the system level. Too many people rely on fancy IDE’s and upper level programming, without ever looking at the power of the shell. For example, the shell is a great place to create a script to do batch processing of files. So go ahead, learn the power of the shell today.

Recursion versus iteration

Recursion is sometimes described by comparing it with its sibling iteration. However the two are fundamentally different. The basic difference lies in their process of evaluation.

Iteration performs an operation a series of times, either predetermined, or undetermined. It produces a stream of results, starting with the first iteration, and ending with the last. It is unidirectional, i.e. it does not have the ability to unwind itself. Recursion on the other hand is bi-directional. it proceeds through a series of recursive calls without generating a result until it reaches the base case. It then uses this result to trace back to the first call of the subprogram, evaluating results as it unwinds.

A good example of this is producing the 1/nth root of a number. For example, if we wish to calculate 2. This can be defined mathematically as:

2 = √√√2
2 = √√1.4143135
2 = √1.189107
2 = 1.090507

Iteration relies on a series of calculations, for example:

P = nsqrt(2) → 2½
P = nsqrt(P) → 2¼
P = nsqrt(P) → 2

Conversely, the recursive version would look like this:

P = nsqrt(nsqrt(nsqrt(2)))

The leftmost nsqrt cannot be computed because it requires the solution of the nsqrt to the right. This in turn waits for the solution of the next nsqrt to the right. This term can be calculated because it calls for the square root of 2. Once this is calculated, the previous nsqrt can be computed, and then finally the leftmost nsqrt.

This by no means implies that recursion is better than iteration, only that it exists as an alternate means of computing a solution. Below are both solutions implemented in Fortran.

function nsqrt(x,y) result(r)
   real, intent(in) :: x
   integer, intent(inout) :: y
   real :: r, temp

   temp = x
   do while (y >= 2)
      temp = sqrt(temp)
      y = y / 2
   end do
   r = temp
end function nsqrt
recursive function nsqrt(x,y) result(r)
   real, intent(in) :: x
   integer, intent(in) :: y
   real :: r
   if (y == 2) then
      r = sqrt(x)
   else
      r = nsqrt(sqrt(x),y/2)
   end if
end function nsqrt

The simple difference is that iteration involves loops, and recursion involves a function (or any other subprogram) calling itself.

Recursion in Python

I have said before that Python is horrible for recursion. But how does one perform recursion in Python? It’s really no different to other languages, you just have to make sure it doesn’t get out of control. Here is a simple Python program to calculate the sum of all the numbers from 1 up to and including the value n.

def sumn(n):
    if n <= 1:
        return n
    else:
        return n + sumn(n-1)

print(sumn(100))

It basically defines a function sum(), which contains nothing except a simple if-else statement. The first part of the if statement (line 2) determines if the value n is less than or equal to 1 – it effectively determines when the recursion ends. If the if statement is true, then it invokes the base case of recursion, i.e. the one that terminates the recursion. In this case it just returns the value n (line 3). For example if we tried to calculate sumn(1), there would be no recursion, because n<=1 would be true, and the value returned would be 1.

The else part of the if statement (lines 4,5), deals with the cases when n is greater than 1, i.e. everything else. In this case, it takes the value of n, and adds to it the value calculated when sumn(n-1) is called. This is the recursive call to sumn().

Let’s do a simple run of the program, say calculate sumn(5). The first time the function is called it is not yet a recursive call. The diagram below shows how the function calls work.

call 1: sumn(5) → 5 + sumn(4)
call 2: sumn(4) → 4 + sumn(3)
call 3: sumn(3) → 3 + sumn(2)
call 4: sumn(2) → 2 + sumn(1)
call 5: sumn(1) → 1

The first call, sumn(5), ends up on line 5 of the function, holding “5 +” and calling sumn(4). Call 1 to sumn() is held in suspension, waiting for sumn(4) to end. As you can see, this continues until sumn(1) is called, at which time, the recursion terminates. At this point there are 5 versions of sumn() in memory 4 of which are recursive. When this happens, the recursive functions can finished their work, and terminate. Given that we known sumn(1) = 1, we can complete the others.

call 5: sumn(1) → 1 (and terminates)
call 4: sumn(2) → 2 + 1 = 3 (and terminates)
call 3: sumn(3) → 3 + 3 = 6 (and terminates)
call 2: sumn(4) → 4 + 6 = 10 (and terminates)
call 1: sumn(5) → 5 + 10 = 15

So the solution to sumn(5) is 15. So what happens if n is a really large number? At n=999 we get the familiar Python "RecursionError: maximum recursion depth exceeded in comparison" error. This algorithms could have just as easily been implemented using an iterative algorithm, where the function is only called once. For example:

def sumn(n):
    s = 0
    for i in range(1,n+1):
        s = s + i
    return s

Now any realistic value of n can be used. Of course there is an even easier way, using the formula:

sum(n) = (n(n+1)) / 2

Remember, recursion is a means of implementing solutions to specific problems, not a panacea for everything.

Recursion and problem solving

Is there such a thing as recursive problem solving? It is a challenging question to answer. Donald Knuth defines recursive problem solving where “the solution to each problem depends on the solutions of smaller instances of the same problem”. But humans don’t actively think about recursion, we may actively use recursion to solve problems, but likely don’t realize it. The recursive approach to problem solving substitutes for the given problem another one of the same form, but in such a manner that the new problem is simpler than the original. By repeating this process we eventually arrive at a problem which is so simple, it can be solved directly.

Another good example of thinking recursively can be found in the Russian nesting dolls, or Matryoshka dolls. How do determine how many dolls are inside the doll? The best approach is to continuously open the outer doll, add it to the count, and then solve the problem of how many dolls remain nested? At each opening, the problem becomes smaller, because there are fewer dolls. At some point the problem must be solved, because no more dolls can be nested (ignore the differences in scale of the dolls).

Recursive problem solving

Is this recursive problem solving? In some respects yes, it is. Each time the outer doll is separated, the problem now becomes smaller, both figuratively and literally. When we reach the doll which can’t be separated, the problem is solved. Here is how is could be described algorithmically (in a round-about sort of way):

doll-count is 0

count-NestingDolls(doll)
begin
   if (doll can be separated)
      smaller-doll = separate(doll)
      increase doll-count by 1
      perform count-NestingDolls(smaller-doll)
   otherwise
      increase doll-count by 1
      done
end

Notice that the “function” count-NestingDolls() is performed again on the smaller-doll if the doll can be separated. The issue is that the same problem can also be solved in an iterative manner, and in all likelihood that’s how most people would do it. Here is an iterative version of the same problem:

doll-count is 1

while (doll can be separated) do
begin
   increase doll-count by 1
   smaller-doll = separate(doll)
   doll = smaller-doll
end

So it is hard to think recursively, in a natural manner. It is a little bit different if we are solving computer problems, because most programming languages provide a means of performing a task in a recursive manner.

Further reading

  1. Graham, R.L., Knuth, D.E., Patashnik, O., Concrete Mathematics, Addison-Wesley (1989)

Toy programming languages are like atomic playsets of the 1950s

In the 1950s, all manner of science toys appeared. Many were less than harmful. Early chemistry sets were completely crazy on the danger scale. They often contained chemicals like potassium nitrate (used in gunpowder and fireworks), nitric acid (rocket fuel), sulphuric acid, sodium ferrocyanide and calcium hypochlorite (could be used to make chlorine gas). Maybe worse was the “Atomic Energy Lab”, released in 1951 (and thankfully production ended in 1952). It contained three “very low-level” radiation sources (alpha, beta, and gamma particles), a U-239 Geiger counter, a Wilson cloud chamber, a spinthariscope, four jars of uranium-bearing ore samples (autunite, torbernite, uraninite, and carnotite), and an electroscope to measure radioactivity.

Atomic energy and explosive chemistry sets.

As we move into the third decade of the 21st century, we have similar toys in the digital realm – toy languages. These are meant to be used by children to learn programming, and while they obviously do not possess the same physical dangers, they do pose a danger to the ability to learn proper programming.

Now toy languages are considered to be any language not suitable or capable of being used to build general purpose and high-end software. These languages are often created to teach kids to “code”. The problem with these languages is that there is often very little thought put into the complete process of programming. Find a problem, solve the problem, design and algorithm, select an appropriate programming language, implement the algorithm in the language. Instead toy languages make things “fun”, usually at the expense of understanding what is actually happening. They also have the tendency to spiral into the same issue introductory programming classes have in higher education – too much emphasis on a particular programming language’s syntax.

Many of these “programming languages for kids” are block-based, or drag-and-drop, i.e. they use a series of colourful shapes that “snap” together on screen, comparable perhaps with LEGO. Each block contains some piece of code, that helps create a program. The problem here is that eventually kids grow out of block-based languages, because it’s not “real coding”, and it does tend to stifle creativity. While potentially useful in the early years of coding (not something that I advocate for anyways), they have severe limitations. They don’t teach any of the other skills associated with programming: problem solving, commenting, code styling, testing, etc. It’s a bit like disregarding cursive writing in favour of digital “writing”, and then not actually bothering to teach typing skills.

Take for example this example in Blockly. Blockly suggests it is “an intuitive, visual way to build code. From a developer’s perspective, Blockly is a ready-made UI for creating a visual language that emits syntactically correct user-generated code.”. The blocks seem confusing, just from a visual perspective. That’s because there are a lot of extraneous words, that don’t end up in the actual code. Blockly can export to popular languages like Javascript, and Lua. Lua? Don’t get me wrong, I really like Lua, but it’s only really popular in the game development community.

An example using Blockly.

The problem is that this method of programming really clutters up the visual space. Take the sequence: “set Count to Count + 1“. This phrase does make sense, but in most real languages this is simply: Count = Count + 1. The problem with using more English-like languages comes later, when the student views the “user-generated code”, wondering how the two relate. It’s almost like a two-step process that only needs one. (Of course we all know that programming languages blundered on that aspect years ago, it should really be Count⟵Count+1 (🤪). It’s almost like a two-step process that only needs one.)

Sure, I bet some block based languages are great for kids to interpret, but the reality is that courses teaching programming set a pattern for expectations. It’s no difference to teaching only C in undergrad… when some students are then faced with a non-C-based language, they baulk. The problem is that programming is not about making fun games – it’s hard. Really hard. Most kids that are good at programming, and enjoy it, want to code in real languages. The rest? They will do what they do in other classes, they will learn enough to get through the class, and then expunge their brains of it. It has about the same relevance as Shakespeare to most kids.

The real problem is that there have been few if any long-term studies on how to best approach programming for children. Since the 1980s, there has been little work done in this area. What age should we really start teaching programming, and what language should we use? There are just too many unanswered questions. I imagine many of these toy languages are created by people who: (i) have never taught; (ii) have never taught elementary students; (iii) don’t have kids themselves, and (iv) don’t have any knowledge of pedagogy. It’s like highly educated people creating a math curriculum for a Grade 4 class, never having stepped foot in a Grade 4 class.

A better idea would be to use a language like Processing. It’s not block-based, and it’s not C++, but it does allow kids to build small programs with visual output. It provides a sketchbook, and is designed for people to code in the context of the visual arts. Yes, it still requires some knowledge of syntax, but if introduced at an appropriate level (Grade 7/8), it would be more than appropriate.

What Fortran does better than C-like languages

C-like languages (C, C++, Java) can do many things, but over the decades nothing much has changed with the inadequacies of some of their control structures. Fortran on the other hand, has evolved. Here are some things that just make implementing some algorithms easier. (Yes there are work arounds in C, but they are not as elegant).

Exiting from a nested loop

In C-like languages exiting from a deeply nested loop isn’t exactly trivial (without the use of goto, so don’t even go there). A break statement in C will only exits the loop the break resides in. If the break appears inside a nested loop, the break only leaves the loop it is in. There is no single break statement in C that will break out of more than one level of nesting. It is possible to add an extra condition, for example in C:

exitloop = 0
for (i=1; i<=10 && !exitloop; i=i+1)
   for (j=1; j<=10 && !exitloop; j=j+1)
      for (k=1; k<=10 && !exitloop; k=k+1)
         if (x[i][j][k] == 0) 
            exitloop = 1;

Fortran however allows loops to be named, so exiting nested loops is as simple as naming the loop to be exited.

loop1</strong>: do i = 1, 10
   loop2: do j = 1, 10
      loop3: do k = 1, 10
         if (x(i,j,k) == 0) exit loop1
      end do loop3
   end do loop2
end do loop1

Ranges in a case statement

C doesn’t do ranges in switch… okay so some compilers, like gnu C offer it as an extension, but it’s not part of the spec. Fortran allows it as part of the standard.

integer :: temp_c

! Fujita Scale for wind velocity (mph)
select case (windS)
case (40:72) 
   write (*,*) 'F0 : light-weak'
case (73:112)
   write (*,*) 'F1 : moderate-weak'
case (113:157)
   write (*,*) 'F2 : significant-strong'
case (158:206)
   write (*,*) 'F3 : severe-strong'
case (207:260)
   write (*,*) 'F4 : devastating-violent'
end select

Array slicing

If you deal with arrays, you know how important it is to have array slicing in a language – because it makes manipulating arrays super easy. Why write loops when you don’t have to? Here are two array declarations in Fortran.

integer, dimension(100) :: vec
integer, dimension(1:6,1:6) :: arr2d

Here are some things you can do:

print *, size(vec)   ! print the size of the array
print *, vec(40:50)  ! print elements 40 to 50
print *, vec(40:50:2)! print elements 40 to 50, step size 2, ie. 40,42,...
add2d = 1            ! set all elements of the array to the value 1
arr2d(3:4,3:4) = 2   ! set elements in row 3 and 4, col 3 and 4 to value 2
write (*,*) arr2d    ! print out the whole array (a bit messy)

Arrays in 2D can be messy when printed using the single statement above. It is possible to use a single loop, and slicing to make things look nicer. The loop controls the row, and the columns are all included using the “:” symbol.

do i = 1,size(arr2d,1)
   write(*,'(6i4)') arr2d(i,:)
end do

There is no array slicing mechanism in C.

Arrays with any indices you want

Fortran by default indexes arrays at 1. But, you are not restricted to that, you can specify the index range of any array. Some examples are shown below:

integer, dimension(100) :: v      ! indices-> 1..100
integer, dimension(0..99) :: w    ! indices-> 0..99
real, dimension(-5:5) :: z        ! indices-> -5,-4,...0,...4,5
real, dimension(3,4) :: a         ! indices-> x: 1..3 and y: 1..4
real, dimension(-1:1,4) :: a      ! indices-> x: -1,0,1 and y: 1..4

Makes it much easier to bend a language to an algorithm, rather than the other way around.

No dangling else

Regardless of what people say, dangling else does cause problems in languages like C. The fact that all Fortran control structures are terminated puts a stop to that. There is also no need for { and } or similar to signify the begin and end of a control block.

Few if any dangerous things

Fortran is cool because it is easy to learn, hard to make catastrophic errors with, and for numerical computation there is likely no faster language. Sure, it may lack some of the low-level features of C, but not every language has to be so close to the system.

Coding Ada: get vs. get_line (ii) – some caveats

When used separately, get and get_line rarely cause issues. When used together, there can be some issues. Consider the following code:

a : integer;
s : string(1..4);

get(a);
s := get_line;
put(a);
put_line(s);

Here is the dialog obtained when running the program containing this code:

4

raised CONSTRAINT_ERROR : testgetm.adb:14 length check failed

It allows the user to input the number 4, but then balks and pulls an exception. What went wrong? To see what happened we have to consider exactly what these input functions do. As we have seen, calls to get for numeric and string input skip over blanks and line terminators that come before the number. That’s why it is possible to press the <return> key several times between numbers being entered. However these get functions do not skip over any blanks and line terminators that come after the number. After the integer variable a is read, the input pointer is set to the line terminator after 4.

Because get_line stops reading when it encounters a line terminator, it stops and sets the length of the string to zero to indicate a null string. As the size of the (fixed) string is expected to be 4 characters in length, an exception is actioned. One way to circumvent this is to type the string on the same line as the number 4. For example:

6tool
          6tool

This illustrates another feature of numeric input. The get function for numeric data stops reading when a non-numeric character is encountered. The non-numeric character “t” is not read and is still available to be read by the next call to an input function.

This solution is not really optimal. A better idea is to use the function skip_line. When skip_line is called, it moves the input pointer to the next line. It is most useful when using a get_line function after getting a numeric value. Here is an example of its use:

a : integer;
s : string(1..4);

get(a);
skip_line;
s := get_line;
put(a);
put_line(s);

Note that if we use a unbound_string instead of the fixed string, then the program will not convulse, but it also will skip the string input… but it will print out a string. Why? Well the unbound_string will actually store the line terminator in the string… so it does read something, just not what it wanted.

To make things more consistent, it is also possible to read all input in as strings using get_line, and convert the string to numbers. Below is an example of the code. The first value is input to the unbound_string buf, which is then converted to a string, and then an integer, and assigned to the integer variable a.

a : integer;
buf : unbounded_string;

buf := get_line;
a := integer'value(to_string(buf));
buf := get_line;
put(a);
put_line(buf);

Note that weird things can also occur when using consecutive get_line() statements. For example consider the code:

s,t : string(1..4);
get_line(s,len);
put(s); put(len);
get_line(t,len);
put(t); put(len);

Here is an example of it running:

tool
tool          4�#           0

The first string input “tool”. works, the second string reads in the line terminator. The same does not occur using the alternate form of get_line. For example the code below works as it should.

s,t : string(1..4);
s := get_line;
put(s);
t := get_line;
put(t);