C. Keith Ray

C. Keith Ray writes about and develops software in multiple platforms and languages, including iOS® and Macintosh®.
Keith's Résumé (pdf)

Friday, October 20, 2017

How can we create an operator to append a value to an array?

This is a sample from my book in progress at LeanPub: What Every Programmer Needs to Know About Swift.

Sometimes you want to be succinct and repetitive — as in appending values to an array. We could create an operator function that modifies the array, but that doesn’t let us use the operator multiple times in the same expression. This is because the temporary values you get in a complex expression are immutable.

precedencegroup AppendPrecedence {
    associativity: left
    lowerThan: TernaryPrecedence
}

infix operator «: AppendPrecedence

func « <T>(lhs: Array<T>, rhs: T) -> Array<T>
{
    var result = lhs
    result.append(rhs)
    return result
}

let x = [1,2,3,4]
let z1 = (x « 5) « 6
print(z1) // prints [1, 2, 3, 4, 5, 6]

let z2 = x « 5 « 6
print(z2) // prints [1, 2, 3, 4, 5, 6]

let z3 = x « 4 + 5 * 9 « 12
print(z3) // prints "[1, 2, 3, 4, 49, 12]"

let z4 = x « 4 < 5 ? 22 : 33 « 12
print(z4) // prints "[1, 2, 3, 4, 22, 12]"

By making the precedence of this operator less than one of the lowest precedences (TernaryPrecedence), we can mix other expressions and still get the results we expect. Though you should use parentheses to make complicated expressions more clear.

Tuesday, October 10, 2017

Playing With Compare And Closures

// playing with compare() and closures

import Foundation

print("actOn start")
let one = "1"
let two = "2"

func actOn(_ result: ComparisonResult
        ifLess: ()->Void
        ifEqual: ()->Void
        ifGreater: ()->Void ) {
    switch result {
        case .orderedAscending:
            ifLess()
        case .orderedSame:
            ifEqual()
        case .orderedDescending:
            ifGreater()
    }
}

actOn(one.compare(two), ifLess: {print("    string 1 < 2")},
        ifEqual: {print("    string 1 == 2")},
        ifGreater: {print("    string 1 > 2")})

actOn(two.compare(two), ifLess: {print("    string 2 < 2")},
        ifEqual: {print("    string 2 == 2")},
        ifGreater: {print("    string 2 > 2")})

actOn(two.compare(one), ifLess: {print("    string 2 < 1")},
        ifEqual: {print("    string 2 == 1")},
        ifGreater: {print("    string 2 > 1")})

print("actOn end\n")

print("switch start")

switch one.compare(two) {
    case .orderedAscending:   print("    switch 1 < 2")
    case .orderedSame:        print("    switch 1 == 2")
    case .orderedDescendingprint("    switch 1 > 2")
}

switch two.compare(two) {
    case .orderedAscending:
        print("    switch 2 < 2")
    case .orderedSame:
        print("    switch 2 == 2")
    case .orderedDescending:
        print("    switch 2 > 2")
}

switch two.compare(one) {
    case .orderedAscending:
        print("    switch 2 < 1")
    case .orderedSame:
        print("    switch 2 == 1")
    case .orderedDescending:
        print("    switch 2 > 1")
}

print("switch end\n")

print("compare start")

func compareComparable
>(_ expr1: T, _ expr2: T, ifLess: ()->Void, ifEqual: ()->Void, ifGreater: ()->Void ) {
    if expr1 < expr2 {
        ifLess()
    }
    else if expr1 == expr2 {
        ifEqual()
    }
    else if expr1 > expr2 {
        ifGreater()
    }
    else {
       assertionFailure("\(expr1) isn't < or == or > \(expr2)")
    }
}

compare(1, 2,  ifLess:     {print("    1 < 2")},
               ifEqual:    {print("    1 == 2")},
               ifGreater:  {print("    1 > 2")})

compare(2, 2, ifLess: {print("    2 < 2")},
    ifEqual: {print("    2 == 2")},
    ifGreater: {print("    2 > 2")})

compare(2, 1, ifLess: {print("    2 < 1")},
    ifEqual: {print("    2 == 1")},
    ifGreater: {print("    2 > 1")})

// trigger assertion (as designed)
compare(Double.nan, Double.leastNonzeroMagnitude, ifLess: {print("NaN < n")},
    ifEqual: {print("    NaN == n")},
    ifGreater: {print("    NaN > n")})

print("compare end\n")



----------- output -------------------------------------------------------

actOn start
    string 1 < 2
    string 2 == 2
    string 2 > 1
actOn end

switch start
    switch 1 < 2
    switch 2 == 2
    switch 2 > 1
switch end

compare start
    1 < 2
    2 == 2
    2 > 1
fatal error: nan isn't < or == or > 4.94065645841247e-324: file forloop.playground, line 76


Saturday, October 7, 2017

How Do I Use "Switch" to Initialize a Variable or Constant?

This is a sample from my book in progress at LeanPub: What Every Programmer Needs to Know About Swift.

You might want to be able to use 'switch' to initialize a variable or constant, given 3 or more possible conditions (if it's just two, you can use the trinary operator ":?")
enum  Color {  // In this example, assume we can't associate 
    case red   // values with these cases _in_ this enum, perhaps
    case green // because we're not allowed to modify this code.
    case blue 
    case purple
}
let c = Color.green

// a one-line "switch expression" which doesn't exist in Swift.
let menuNumber = switch c { case .red: 1, case .green: 2, 
    case .blue: 3, default: 0 } // ILLEGAL SYNTAX
You can use a switch statement or if/else statements to initialize the value; the compiler knows you are initializing a constant within that logic. As long as in every code path before the constant is used, you initialize the constant once and only once.
let menuNumber : Int
switch c {
    case .red: menuNumber = 1
    case .green: menuNumber = 2
    case .blue: menuNumber = 3
    default: menuNumber = 0
}
print(menuNumber) // prints 2
You could also create a dictionary and subscript it in the same line of code:
// moral equivalent of a one-line "switch expression":
let menuNumber = [Color.red : 1, Color.green : 2, Color.blue : 3][c] ?? 0
print(menuNumber) // prints 2
We need "?? 0" because subscripting a dictionary returns an optional, which will be nil if the subscript value isn't found in the dictionary. The "??" operator returns the unwrapped value of the optional, if the optional isn't nil, and it returns the right-hand value, 0, if optional value is nil.
This is actually less code than "switch expression", but some people may find harder to read than the explicit logic of switch/if/else/etc. Use caution.