I Love This Idea – Seems Like A Great Reference

I Love This Idea – Seems Like A Great Reference

How to learn X

The things I have learned best, I have learned by working through the hard slog of either replicating someone else’s work/derivation or by using a technique/technology to the point that a meaningful understanding was required. For things that were worth learning, this has always been accompanied by (a) moment(s) of deep frustration to the point of nearly crying or crying out in frustration. I know what that moment feels like. It is very visceral and has taught me that the feeling is a good one, because I (usually) can get through it to the point of learning, and good things are on the other side.

This referenced repo has links and references to lots of potentially interesting things to learn. In some regards, such a list stresses me out, because off the top of my head, there are at least 8 subjects for which I would like to do a deep dive. But I don’t have time to do that. It makes me despair of even trying. But the upside to this view is that I have lots of worthwhile things which are currently taking my attention.

On Selecting Functions

Wow. I couldn’t think of a better title. The more specific version would have been “Simplifying The Selection Of Functions that Accept Identical Arguments”. And that was not… great…

Regardless of a terrible title, it is common situation to have a condition which requires the selection between two (or more) different functions to which the same arguments are being passed.

A typical example follows:

func firstFunc(with argument: Int) {
    print("first func \(argument)")

func secondFunc(with argument: Int) {
    print("second func \(argument*argument)")

let value = 43
if value > 30 {
    firstFunc(with: value)
} else {
    secondFunc(with: value)

This if-then construct always bothers me for two reasons.

First, I think it bugs me because it takes so much room vertically just to say “depending on the criteria, choose a function, then pass the value to it.” The value is the same in both cases, so it feels just a little bit duplicative to pass it along in two separate places in code. That is nominally a possible source of a bug.

Second, it also bothers me because all I am really doing is an assignment. I don’t want to do any other operations within the if-then blocks, but once it’s written, I can’t control what future programmers do and whether or not they see or respect my intent, possibly adding side effects. I would like an assignment structure that reflects that.

Lastly, I do realize that this is not the biggest issue in the world.

However, it did finally hit me that functions are 1st class citizens in swift and can be passed around just like any other parameter. Therefore we could choose a method like this based upon the criteria and then pass the argument to it directly:

let resolvedFunc = (value > 30) ? firstFunc(with:) : secondFunc(with:)

That’s pretty cool. We went from 5 LOC to 2 and we removed any side effects. We could even get a little fancier and do it in one line, by wrapping the value in a single item array, and applying the selected function to it:

[value].forEach(value > 30 ? firstFunc : secondFunc)

While I love that it is just a single LOC, it is not an obvious pattern, so I would probably prefer the 2 line version. But just for kicks, we could also do it this way in a single LOC, though syntactically it’s even more confusing:

(value > 30 ? firstFunc(with:) : secondFunc(with:))(value)

Unsurprisingly, this approach works with multi-parameter functions as well:

func firstFunc(with argument: Int, other: Int) {
    print("first tup func \(argument) -- \(other)")

func secondFunc(with argument: Int, other: Int) {
    print("second tup func \(argument) -- \(other)")

let valuesTuple = (22, 43)

let resolvedFunc = (valuesTuple.0 < 30) ? firstFunc : secondFunc
resolvedTupFunc(tupVal.0, tupVal.1)

That’s nice, but the forEach approach is even cooler, and is why I assigned the two values to a tuple was that by applying the function to a single item array of tuples, it is not necessary to call out the individual arguments. They are implicitly applied as $0 and $1 in order:

[valuesTuple].forEach(valuesTuple.0 < 30 ? firstFunc : secondFunc)

I’m not sure if I’ll use this in production code with other people because I’m afraid that it could be a little difficult to grok at first or second glance, but I am glad to have finally found a clean way to do this that avoids the possibility of side effects.

It would be nice if swift had a function that was equivalent to map or forEach that would allow the application of a function or closure to a value.

Weird Behavior With compactMap

The collection function compactMap is the replacement for a particular use of flatMap, and was introduced in Swift 4.1. My understanding of compactMap is that it applies a closure to the elements of a sequence and removes nil results, leaving only non-nil values in the sequence. This is how I read its type signature from the documentation:

func compactMap<ElementOfResult>(_ transform: (Base.Element.Element) throws -> ElementOfResult?) rethrows -> [ElementOfResult]

But based on some testing, things aren’t quite that simple or predictable.

My conclusion is that depending on the Swift type-checker, compactMap will return different results in ways that are not always obvious.

The first ambiguity is that the return value depends on the type of the receiver:

let array = [2, 3, nil]

let compactArray: [Int] = array.compactMap { $0 }
let optionalCompactArray: [Int?] = array.compactMap { $0 }

print(compactArray.count) // => 2
print(optionalCompactArray.count) // => 3
//printing without typed assignement
print(array.compactMap({ $0 }).count) // => 2

It appears that compactMap will return either Int or Int?, depending on type of assignment. I find this understandable, but less than ideal.

So, for the remaining examples, I will type the receiver as [Int?], so that this remains consistent:

The second ambiguity is that seemingly functionally identical code triggers different results with compactMap:

let optCompact1: [Int?] = array.compactMap {
    guard let num = $0 else { return nil }
    return num > 50 ? num : nil

let optCompact2: [Int?] = array.compactMap {
    guard let num = $0, num > 50 else { return nil }
    return num

print(optCompact1.count) // => 2
print(optCompact2.count) // => 3

The way that I understand this is that when the guard statement fails, then a true nil is returned, where as the compactMap behaves as though the second line causes it to return an unwrapped optional.

This seems bad and very difficult to predict. It seems to me that it should be treated as though it were returning an unwrapped optional, and the nil values were removed.

Likewise, if the code is put in a function and that function is called from the closure, then it treats it as an unwrapped optional:

func func1(_ n: Int?) -> Int? {
    guard let num = n else { return nil }
    return num > 50 ? num : nil
func func2(_ n: Int?) -> Int? {
    guard let num = n, num > 50 else { return nil }
    return num

let optCompact1b: [Int?] = array.compactMap { func1($0) }
let optCompact2b: [Int?] = array.compactMap { func2($0) }

print(optCompact1b.count) // => 3
print(optCompact2b.count) // => 3

Lastly, if we rewrite this as closures which are either called directly, or directly applied, then we get the following results, which are both inconsistent with each other, and inconsistent with the initial results:

let closure: ((Int?)->(Int?)) = {(n: Int?) in
    guard let num = n else { return nil }
    return num > 50 ? num : nil

let closure2: ((Int?)->(Int?)) = {(n: Int?) in
    guard let num = n, num > 50 else { return nil }
    return num

let optCompact1c: [Int?] = array.compactMap { closure($0) }
let optCompact2c: [Int?] = array.compactMap { closure2($0) }
let optCompact1d: [Int?] = array.compactMap(closure)
let optCompact2d: [Int?] = array.compactMap(closure2)

print(optCompact1c.count) // => 3
print(optCompact2c.count) // => 3

print(optCompact1d.count) // => 0
print(optCompact2d.count) // => 0

This seems really wrong to me, but perhaps I simply do not understand the differences between how the following cases above relate to wrapped and unwrapped optionals. Anyone want to weigh in?

Interesting Gotcha With UIButton Title

I was writing some code along these lines to set the background of a view based upon the value of the title of a button.1 Seems simple enough. The otherView.background should definitely be green at the end of this code. But it wasn’t. Can you figure out what I did wrong?

// starting condition - sender.titleLabel?.text == "invalidValue"

button.setTitle("validValue", for: .normal)

if sender.titleLabel?.text == "validValue" {
    otherView.backgroundColor = .green
} else {
    otherView.backgroundColor = .red

Turns out that setting the text of the titleLabel is an asynchronous, animated event. When we call setTitle(_, for:), we are setting the underlying title for a given state, and we also trigger an animated change to the visible titleLabel.text . So even if you call setNeedsDisplay or setNeedsLayout on the button prior to reading its value, titleLabel.text will not reflect the new value until the end of the animated change.

So, how do we get the correct value in an instant? Use button.title(for: .normal) 2 This reflects the “view model” value for the title in a given state, whereas the titleLabel.text value is what is actually displayed to the user at that instant.

  1. Actual example is more complex, but this gets to the heart of it. ↩︎
  2. replace .normal with a different state, if that is relevant ↩︎

4th spatial Dimension — Whoa!

NOTE: Not about coding today.

For no good reason, I started thinking about 4 dimensions. While I’ve seen a number of explanations of what a 4-dimensional object would look like from 3-dimensional space, I’ve never seen an explanation of what a 3-dimensional object would look like from 4-dimensional space. It occurred to me that in a universe with 4 spatial dimensions every point (inside and out) of a 3-dimensional object1 would visible to an observer. That is, every single point inside my 3D body would be perfectly visible viewed by an observer from 4 dimensional space.

Let me explain using an analogy to 2-dimensional space.2

First, look at the drawing below of a circle with dots on the inside, a triangle with lines inside, and the two-eyed observer. What can the observer see?

Since she’s two-eyed, she can perceive depth and therefore can see two distinct objects and can see that one is curved and can see that the other has one flat surface facing her, and that its surface is partially obscured by the object with the curved surface. But, she cannot see what is inside the objects, nor can she see behind the objects. But we, in 3-dimensional space can see the entire shape of each object, and we can also see literally every single point inside the entirety of each object, in a glance.

I understand this in the following way. In 2-dimensional space, observation is constrained by straight-lined trajectories within the x-y axes, but in 3-dimensional space, every point of a 2-dimensional object is viewable along an axis that is orthogonal to the x-y axes.

Now think of the analogous 3-dimensional objects. If I look at two people, one standing partially in front of the other, it’s just like the 2-d situation, I can see height, width, and depth of each object along a trajectory defined x-y-z axes, so I can’t the part of the person behind the other, and I can’t see the shape of the rear side of the people, and I definitely cannot see inside the people. But a 4 dimensional observer could see all of those things perfectly, in a glance along the orthogonal 4th axis. Whoa.

If it helps, you can make the further analogy of a 1-dimensional object being placed in 2-dimensional space: look at this picture, showing a straight line that merely varies in color along its one axis. The two dimensional observer can see the entire object, inside and out. There is no “behind” or “inside” for the 2-d observer.

I don’t have a “point” to this write-up, but I’ve always struggled to think of what higher dimensional spaces/shapes would look like, and this analysis doesn’t really answer that, but it does help me understand a little bit how our 3 dimensions might be perceived in 4-dimensional space.

  1. To be clear, in 4 dimensional space, it would actually be “a rendering of a 3 dimensional object”, just like a drawing on paper is a rendering of 2 dimensions, not actually 2 dimensions. ↩︎
  2. I’m just figuring this out on my own with inspiration from the idea of Flatland. I haven’t read it, and I am certain that others have fleshed these ideas out more fully and correctly elsewhere. But I haven’t personally come across this take before. ↩︎

UILabel + UISwitch Autolayout Bug?

I’m not sure that I have run into very many UIKit bugs in my career, but this sure seems like a bug to me. I have a UILabel and a UISwitch next to each other. Each is pinned with trailing & leading constraints as shown, but with no width constraints. The label has number of lines set to 0, and no other changes were made to compression priorities.

As you can see, no complaints from Interface Builder:

If I set the label’s text in ViewDidLoad to a string that is more than one line long, then I would expect that autolayout would see the switch and its required width, calculate the max width of the label based on that and the other horizontal constraints, and then set the label’s height accordingly. But the result that I get is as follows:

So the layout seems to be breaking the switch’s trailing constraint, however there are no error messages in the debugger.

If I switch the horizontal compression of the UISwitch to 751, then it lays out the views correctly:

The odd thing is that Autolayout is respecting the intrinsic size of the UISwitch, which can be verified by printing out the frame in the debugger after layout. So its not shrinking the horizontal size of the switch when it renders it. This means that it must be breaking the trailing constraint.

So the bug is two-fold. First, its calculating the width of the string based, perhaps, on a compressed UISwitch, but then laying out a UISwitch at full width, which necessarily breaking the trailing constraint of the UISwitch1. Second, it’s not providing any debugging information regarding the fact that it broke a constraint.

If I set the label’s text to the long version in Interface Builder, it recognizes the problem right away, as shown by the red lines, which further implies that there is a missing warning in the debugger:

  1. Right? Or maybe I’m missing something. ↩︎

That’s Weird!

While testing a particular part of a function in Swift, I added a return halfway through so that temporarily the rest of the code would not run. There were a number of things I was trying to avoid, but the first of them was a modal spinner dropping over the screen.

However, when I ran my code, the spinner was still dropping onto the screen. I was very confused and was searching all over the place for where else this modal may have been called from. Then I looked at the warning on the line invoking the modal:

I didn’t quite understand how the return could be given an argument in a Void function. I looked around for this error message and found this helpful blog post which reminded me that it is not the case that Void functions return nothing, but that they actually return a Void pointer/argument. And without a trailing semicolon at the end of the line containing the return, if the next thing after whitespace and newlines is of a Void type, then it will treat that as the argument to return.

Here is a quick demonstration of this:

  1. Warning with a Void function following return:
  1. Error with an Int returning function following return:
  1. Fix error with a semicolon after return:

That is logical, but crazy. At least the compiler will give you a warning (as long as your indentation is correct!). But I think that in a language that is trying to move away from pointers, it is pretty non-intuitive behavior, especially when it is so easy to treat a newline as though it were a semicolon.

I’ve added a comment to the effect to this issue at Swift.org