Even and Odd Integers

I know that some languages (not sure about swift), compile certain modulo expressions to the same assembly instructions as the binary logic and so there are no “performance optimizations” that could be made. Of course, this may not be the case for swift.

It is. As can be seen on line 39 in the link. test bl, 1 is emitted, but only with -O flag.

1 Like

So then I guess the binary version would be the one used in the potential design since we’d want the most optimized version no matter the compilation flags.

Wow! I'm so excited to see this thread get picked back up, and that someone reached a nearly identical solution to me (and independently, it sounds like). Also, thank you so much @rob for finding so many examples of how this would be beneficial. I would be incredibly happy to work with you (and anyone else who is interested and feels invested) on a proposal if people continue to support this proposal.

To be perfectly honest, I thought that this was a dead issue, so I'm glad to see that someone else cares about this. :smiley:

1 Like

If we're missing optimizations, we should fix that, not design around them.

1 Like

Good point. Is that something that should be filed as a bug?

Key paths make it easy to be point-free :wink:. We just need a get function, an equivalent prefix ^, or some first-class promotion, if anyone wants to help me with an implementation :smile:.

xs.filter(\.isEven)
2 Likes

It would be really cool if you map in a point free style using key paths too!

Instead of both isEven and isOdd being un-overridable properties, should at least one of them be a core member? (The other one would be a pure extension that calls the first and applies !.) That way a type that could compute the result faster than "% 2 == 0" can get the chance to actually do so.

For values besides 2 (possibly also powers of 2), can it actually be implemented faster than "% n == 0"?

This is mentioned in the proposal as a mistaken implementation, but it's my understanding that this would compile down to the same code going as far back as Swift 2, except perhaps in -Onone (where I doubt the extra function call setup would be noticeable given all of the other optimizations not taken in -Onone).

My concern wasn't for the default integer types, but for a user-defined type. "% 2 == 0" (or "& 1 == 0" or whatever is chosen) may not be the best choice for a particular implementation of a user-defined integer type, and making both isEven and isOdd pure-extension properties means that said user-defined types would be stuck.

Since BinaryInteger is a protocol, its members can be overridden by types that adopt it.

For example, the following compiles:

extension BinaryInteger {
    var isEven: Bool { return self % 2 == 0 }
    var isOdd: Bool { return self % 2 != 0}
}

extension Int8 {
    var isEven: Bool { return self % 2 != 0 }
}

var example: Int8 = 9
print(example.isEven)
//Prints true

Admittedly, this example is not useful in real life since there is no need to change the definition for Int8, but if you were to create a user-defined type adopting BinaryInteger, you would be able to create a definition that made sense.

Consider this code, where isOdd uses isEven in its implementation:

extension BinaryInteger {
    var isEven: Bool { return self % 2 == 0 }
    var isOdd: Bool { return !isEven }
}

extension Int8 {
    var isEven: Bool { return self % 2 != 0 }
}

var example: Int8 = 9
print(example.isEven)  // prints "true"
print(example.isOdd)   // also prints "true"

As you can see isEven can't really be overridden by adopting types. This is because isEven isn't a customization point – it's only defined in a protocol extension, not in the actual BinaryInteger protocol itself.

3 Likes

I believe, if my memory serves me correctly, that this is because isOdd, in your example, has not been updated alongside isEven and, due to a bug in Swift, will continue to call to the original BinaryInteger extension's definition of isEven. This issue and the performance impact issue are the reasons why isOdd and isEven should be defined separately and not rely upon one another for their definitions.

Additionally, because both definitions defined in the BinaryInteger extension do rely upon the definition of %, it will likely be rare that users ever need to define their own definitions for isOdd and isEven; the results, if not the implementations, of % will be valid for the definitions to return true or false correctly.

While you're right about the cause (isEven being a customization point), the root of the problem here is that the implementation of Int8.isEven is semantically different (or, to use a less fancy term, wrong). Customization points like this should really be reserved for optimizations, rather than different behaviors.

It's not a bug though, it's how the language works. To get dynamic dispatch in a generic context, the method needs to be declared on the protocol.

7 Likes

Totally, this only made it easier to show which version was called. :slight_smile:

Sorry, didn’t mean to imply you didn’t realize it. It’s a good example of why you need to stick to a “same, just better” rule for these kind of overloads.

SiliconUnicorn
July 16
Since BinaryInteger is a protocol, its members can be overridden by types that adopt it.

But a type-scoped override is applied only when you have a reference to that deepest type....

For example, the following compiles:

extension BinaryInteger {
var isEven: Bool { return self % 2 == 0 }
var isOdd: Bool { return self % 2 != 0}
}

extension Int8 {
var isEven: Bool { return self % 2 != 0 }
}

var example: Int8 = 9
print(example.isEven)
//Prints true
...So if you passed that Int8 as a reference to a BinaryInteger, then the type-scoped override will be ignored. In your example, it would return the original FALSE value.

Admittedly, this example is not useful in real life since there is no need to change the definition for Int8, but if you were to create a user-defined type adopting BinaryInteger, you would be able to create a definition that made sense.

But you can’t exploit that override in lots of code unless extensibility was added in the primary definition.

At the very least for powers of two, no?