A Bit of Functional Programming By Example
· Jan 29, 07:07 PMGiven a gzip-compressed file, the shell function gzip -l returns various information about it, including the name of the uncompressed file. I needed to read that name from the output of gzip -l.
The output looks like this.
compressed uncompressed ratio uncompressed_name
115815 136227 15.0% Foo.jpg.gz
The lines are separated by return characters, and the formatting of columns is done using multiple spaces. Splitting the output into lines is easy. of course.
dim outputLines() as String = Split(theShell.Result, Encodings.UTF8.Chr(13))
The string I want is on the second line. As Ken Thompson famously stated, “when in doubt, use brute force”. I apply his advice here.
dim secondLine() as String = Split(outputLines(1), " ")
This array contains a bunch of empty strings corresponding to the extra spaces. So here is a function to remove them.
Function RemoveBlanks(s() as String) As String()
dim outputList() as String
for i as Integer = 0 to UBound(s)
if s(i) <> "" then
outputList.Append s(i)
end if
next
return outputList
End Function
Before I do that, I would like to trim the elements in the array to also eliminate any other all-whitespace lines.
Function Trim(s() as String) As String()
dim outputList() as String
for i as Integer = 0 to UBound(s)
outputList.Append Trim(s(i))
next
return outputList
End Function
Using these functions, my code now looks like
dim secondLine() as String = RemoveBlanks(Trim(Split(outputLines(1), " ")))
Now I can reasonably expect to find the uncompressed file name as the last element of the array secondLine.
Parsing the output of gzip is made quite simple by the use of the two auxiliary functions Trim and RemoveBlanks. Each of these functions is of course more generally useful. And we can make them yet more general.
Let’s take a look at Trim first. What this function does is to apply the built-in function Trim to every element of a string array. Replace Trim by an arbtrary function of the same signature, and we have something quite general.
Delegate Function StringMapDelegate(s as String) As String
Function Map(s() as String, f as StringMapDelegate) as String()
dim outputList() as String
for i as Integer = 0 to UBound(s)
outputList.Append f.Invoke(s(i))
next
return outputList
End Function
We can similarly decompose RemoveBlanks.
Delegate Function StringFilterDelegate(s as String) As Boolean
Function Filter(s() as String, f as StringFilterDelegate) As String()
dim outputList() as String
for i as Integer = 0 to UBound(s)
if f.Invoke(s(i)) then
outputList.Append s(i)
end if
next
return outputList
End Function
Using these functions Map and Filter, I could write my code as follows.
Function IsNotEmpty(s as String) as Boolean
return s <> ""
End Function
dim secondLine() as String = Filter(Map(Split(outputLines(1), " "), AddressOf Trim), AddressOf IsNotEmpty)
Not long after you decide that Filter is a very useful addition to your toolkit, you will ask questions like “how do I pass two conditions to Filter?” or “how do I pass the negation of a condition?”. It turns out this requires a bit more work to do in a clean way. I will post some answers in the future.
Comment
Commenting is closed for this article.
Finally I understand the meaning and use of Delegates. No more need for Interfaces all the time.
Thanks for doing another teaching by example!
— Thomas Tempelmann · Jan 30, 03:20 AM · #