The languages people use guide how they think in both literature, science and programming. French and Welsh may good for poetry but my interest has always been in programming languages; how they influence the problem solving process and get the programming job done. From a career start in the old school languages of COBOL, Fortran and Basic it has always been obvious that the language used to describe the solution to a problem has a big influence on the accuracy and ease of the problem outcome.
A career in software technical support has provided a deep familiarity with broken software and solutions built on technical and scripting languages. Having a bit more time to research I thought it would be good to see what a really modern programming environment has to offer. Looking with a focus on improving the reliability of solutions built using modern software techniques. Swift the Apple supported language behind iPhone apps was chosen being young, well supported, comprehensive and summed up in the tagline :
"Swift makes it easy to write software that is incredibly fast and safe by design."
Just a few months of familiarity with Swift have confirmed very positive initial impressions. The features listed combine in the language to deliver a rich and effective programming environment.
- Data types and structures are enforced but in a good way being flexible including the let/var choice and generic functions and extensibility.
- Parameter madness is tamed using named typed parameters and distinct result typing.
- Eco system provides easy integration with the OS and support tools. Xcode or just command line or including a playground.
- Documentation and understanding, hints and tips are built in. Books, online docs, error messages and tool tips.
- Memory non-management - threads unravelled and access to external features.
- The language and environment has rapidly evolved to maturity.
Example Code
The following simple example demonstrates just a very few of the language features of Swift. See below for the explanation.
The program demonstrates two methods of rotating the contents of an array of mixed items. The Array 'start' is set up with a mix of strings, numbers, boolean and an array. 'start' is then passed to funcs that rotate the contents by a specified number of positions then printed. Two rotation func techniques are used.
// Sun 22 Dec 2019 16:39:47 GMT
// Programmining pearls 2.1 b
// Rotate an array of mixed elements
import Foundation
func RotateTradional (_ arr:[T], by: Int) -> [T] {
var rev:[T] = []
var rby = by % arr.count // only rotate once
if (rby == 0 || rby == arr.count)
{ return arr } // not moving or exact wraparround
if (by < 0 ) { rby = arr.count + by } // handle -ve rotation in a positive way
assert( rby > 0 && rby < arr.count, "Bad rotation \(rby) on \(arr.count)" )
for z in (rby ..< arr.count+rby)
{ rev.append( arr[ z % arr.count ] ) }
return rev
}
func Reverse (_ brr:[T], _ lft:Int, _ rgt:Int) -> [T] {
var rev:[T] = []
// reverse the array elements from lft to rgt-1
// print ("#reverseIN \(brr) \(lft) \(rgt)")
if ( lft == rgt-1 ) // only 1 element to reverse
{ rev.append( brr[lft] ) }
else {
for z in stride(from:rgt, to:lft, by: -1 )
{ rev.append( brr[z-1] ) }
}
print ("#reverseOut \(brr) \(lft) \(rgt) is \(rev)")
return rev
}
func Rotate (_ arr:[T], by: Int) -> [T] {
// using a two partial reversal technique
var rby = by % arr.count // only rotate once
if (rby == 0 || rby == arr.count)
{ return arr } // not moving or exact wraparround
if (by < 0 ) { rby = arr.count + by } // handle -ve rotation in a positive way
assert( rby > 0 && rby < arr.count, "Bad rotation \(rby) on \(arr.count)" )
return Reverse( Reverse(arr,0,rby) + Reverse(arr,rby,arr.count), 0,arr.count)
}
//=================== Start Here ===========================
// any old stuff in the array including a literal array
let start:[Any] = [ "alpha","b","c",5,"e",[99,3.2],"g",true,"I","omega" ]
print ("Start with \(start)")
print ("Reversed start \(Reverse(start,0,start.count))")
print ("Rotated by Tradional method");
for i in ( -2 ... start.count + 2 ) {
let temp = RotateTradional(start, by: i)
print("\(String(format:"%2d", i)) = \(temp) has \(temp.count)")
}
print ("Rotated by partial reverse method");
for i in ( -2 ... start.count + 2 ) {
let temp = Rotate(start, by: i)
print("\(String(format:"%2d", i)) = \(temp) has \(temp.count)")
}SampleOutput
Output from the above program with the debug output lines (starting with #) removed.
$ swift RotateString.swift | grep -v '#'
Start with ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"]
Reversed start ["omega", "I", true, "g", [99.0, 3.2], "e", 5.0, "c", "b", "alpha"]
Rotated by Tradional method
-2 = ["I", "omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true] has 10
-1 = ["omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I"] has 10
0 = ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"] has 10
1 = ["b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha"] has 10
2 = ["c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha", "b"] has 10
3 = [5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha", "b", "c"] has 10
4 = ["e", [99.0, 3.2], "g", true, "I", "omega", "alpha", "b", "c", 5.0] has 10
5 = [[99.0, 3.2], "g", true, "I", "omega", "alpha", "b", "c", 5.0, "e"] has 10
6 = ["g", true, "I", "omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2]] has 10
7 = [true, "I", "omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g"] has 10
8 = ["I", "omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true] has 10
9 = ["omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I"] has 10
10 = ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"] has 10
11 = ["b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha"] has 10
12 = ["c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha", "b"] has 10
Rotated by partial reverse method
-2 = ["I", "omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true] has 10
-1 = ["omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I"] has 10
0 = ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"] has 10
1 = ["b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha"] has 10
2 = ["c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha", "b"] has 10
3 = [5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha", "b", "c"] has 10
4 = ["e", [99.0, 3.2], "g", true, "I", "omega", "alpha", "b", "c", 5.0] has 10
5 = [[99.0, 3.2], "g", true, "I", "omega", "alpha", "b", "c", 5.0, "e"] has 10
6 = ["g", true, "I", "omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2]] has 10
7 = [true, "I", "omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g"] has 10
8 = ["I", "omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true] has 10
9 = ["omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I"] has 10
10 = ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"] has 10
11 = ["b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha"] has 10
12 = ["c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha", "b"] has 10
Sample output showing Debug lines starting with # with line gaps added for clarity
Rotated by partial reverse method
#reverseOut ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"] 0 8 is [true, "g", [99.0, 3.2], "e", 5.0, "c", "b", "alpha"]
#reverseOut ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"] 8 10 is ["omega", "I"]
#reverseOut [true, "g", [99.0, 3.2], "e", 5.0, "c", "b", "alpha", "omega", "I"] 0 10 is ["I", "omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true]
-2 = ["I", "omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true] has 10
#reverseOut ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"] 0 9 is ["I", true, "g", [99.0, 3.2], "e", 5.0, "c", "b", "alpha"]
#reverseOut ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"] 9 10 is ["omega"]
#reverseOut ["I", true, "g", [99.0, 3.2], "e", 5.0, "c", "b", "alpha", "omega"] 0 10 is ["omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I"]
-1 = ["omega", "alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I"] has 10
0 = ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"] has 10
#reverseOut ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"] 0 1 is ["alpha"]
#reverseOut ["alpha", "b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega"] 1 10 is ["omega", "I", true, "g", [99.0, 3.2], "e", 5.0, "c", "b"]
#reverseOut ["alpha", "omega", "I", true, "g", [99.0, 3.2], "e", 5.0, "c", "b"] 0 10 is ["b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha"]
1 = ["b", "c", 5.0, "e", [99.0, 3.2], "g", true, "I", "omega", "alpha"] has 10
Lest have a look at some of the feature that make Swift such a great language. Starting out with a few language features followed by the environmental factors that help to make the rich Swift ecosystem.
1a) Data types are enforced but in a good way.
The ability to use bits and bytes to represent multiple forms of data has been a blessing and source of errors throughout out the history of programming. Knowing what the contents of a specific memory location represent and enforcing the correct usage has driven improvements in both system security and program integrity. Any memory location can be a program instruction, integer, floating point number, string of characters, set of bits, reference to another thing, or even the internal representation of the pixels displayed on the screen. Improvements in detecting when a location is being used in the wrong way has been a fundamental part of software engineering over the last few decades.
Borrowing the concept of constants and variables from Pascal, Swift makes a good impression right from the outset by using "let" and "var" to declare fixed and changeable storage locations.
The types follow the variables around. When a new variable is declared and assigned a value from another with a known type the new variable becomes that type.
In addition the strict typing of variables functions and classes can be written to handle any type presented to them. We can even have arrays where each element is a different type.
One advantage of having a rich set of data structures is that the language can be framed to include data structure handling tools such as iterators.
"f" could be declared as a set, array or dictionary or enumeration but this loop will print each element contained in "f".
func RotateTradional(_ arr:[T], by: Int) -> [T] {
This function is passed a generic array "arr", an integer called "by" and will eventually return an array of the same type as arr whatever that happened to be.
The Swift parameter design avoids the madness that arrises from passing pointers to items as parameters to functions then having the function change the pointer or altering dereferenced content.
Borrowing the concept of constants and variables from Pascal, Swift makes a good impression right from the outset by using "let" and "var" to declare fixed and changeable storage locations.
The types follow the variables around. When a new variable is declared and assigned a value from another with a known type the new variable becomes that type.
In addition the strict typing of variables functions and classes can be written to handle any type presented to them. We can even have arrays where each element is a different type.
let start:[Any] = [ "alpha","b","c",5,"e",[99,3.2],"g",true,"I","omega" ]
1b) Rich interchangeable data structures
Swift has a rich set of flexible built in data structures that can be combined and extended. Enumerations, arrays, dictionary, sets and tuples. Structures and classes with functions provide the basic tool of object oriented programming. Extensions to classes using modern techniques of protocols and closures is also supported.One advantage of having a rich set of data structures is that the language can be framed to include data structure handling tools such as iterators.
for i in f {
print ( "\(i)" )
}
"f" could be declared as a set, array or dictionary or enumeration but this loop will print each element contained in "f".
1c) Uncertainty is allowed
yes/no true/false 1/0 are great as far as they go but sometimes two possible answers to a question are not enough. The option of "Thank you for that great question but that whole thing didn't work out for me.' is sometimes required. At the statement level a try { "something" } catch { "error handled here" } is provided but a real innovation is the provisional of optional values. For example converting a String to an Int works well when the string is made up of digits but what number is expected when the string contains "three" or "soixante-trois" ? Swift, as strongly typed language, cleverly uses an optional types Int? as the return data type from such functions. The options can be unwound by forcing an error if the conversion was not successful or carried on to a point in the program where the conversion failure can be handled.
2) Subroutine Parameter madness is tamed
Start as declared above is an array of type "Any" and has been initiated with a mix of strings character, integers, an array and a boolean. The Rotate functions are written as generic functions that can operate on array of any data type. This can be done as the function only handle array elements and not the contents of the array elements.func RotateTradional
This function is passed a generic array "arr", an integer called "by" and will eventually return an array of the same type as arr whatever that happened to be.
By using named parameters and distinct result typing interface confusion is avoided. Parameters are normally treated as read only inside a function but where "mutating" parameters are needed these have to be annotated with "inout:".
The Swift parameter design avoids the madness that arrises from passing pointers to items as parameters to functions then having the function change the pointer or altering dereferenced content.
No comments:
Post a Comment