Mark as deprecated methods generated by compiler

Is there a way to mark with @available(*, deprecated, message:) methods generated by compiler without manual reimplementation of them?

I have several large structs that conform to Hashable. This is needed for diffable algorithms and keeping them in Set<> or Dictionary. But it is not correct for programmers to directly compare two instances via == because of some reasons.
Possible solution is to mark such method as @available(*, deprecated, message: "some message that guide user"), but it requires to implement manually == function, which is error prone.

This doesn’t answer your question, but if you are manually implementing Hashable then you should also be manually implementing Equatable to match.

On the other hand, if your Hashable conformance is synthesized, I am having difficulty imagining why the synthesized Equatable conformance is problematic.

2 Likes

sniff sniff. what are the reasons?

aside: @available(*, deprecated) is probably not what you want here, this is for things that change between releases. but it sounds like ==(_:_:) was never valid to begin with.

1 Like

ok, lets see this one example:

public struct AmountValue: Hashable, CustomStringConvertible {
  private let underlyingValue: Double
  
  public init(uncheckedValue: Double) throws {
    guard uncheckedValue.isNormal || uncheckedValue.isZero, uncheckedValue.sign == .plus else {
      throw MappingError(code: .unexpectedValue)
    }
    underlyingValue = uncheckedValue
  }
  
  public func isAlmostZero() -> Bool { ...  }
  
  public func isAlmostEqual(to other: AmountValue) -> Bool { ... }
}

It is a typical task to use it as key in Dictionary<AmountValue, _>, but it is not legal to compare them via ==, instead isAlmostEqual(to:) method should be used.

There are mainly two reasons:

  1. these are structs backed with float point numbers, and it is not safe and correct to make all kind of operations. Like with Clock.Instant and Duration
  2. these are structs with a large numbers of fileds, which are made Hashable for using in diffable datasources. But it is not valid to compare them with ==. Typical mistake that users do is trying to find an element in collection by comparing instances with ==. Instead, they should compare instance's IDs.

So what I need is to keep structs Hashable, but prevent users from comparing via ==.

before going further, i have to ask: is AmountValue money?

(i don’t really run into many situations where i use a fractional type as a dictionary key that is not money)

assuming AmountValue is not money…

Duration is a fixed point type, so it should be perfectly fine to compare them for equality.

Clock.Instant changes depending on the clock type, but as far as i’m aware, ContinuousClock.Instant is safe to compare for equality.

why are you using the structs as dictionary keys instead of just using the IDs as dictionary keys?

No, it is for quantity, weight or packing step.

Yes, it is. I mean public Interface of Duration or Clock.Instant doesn't allow all math operations, only subset of them is possible. AmountValue is designed in a similar manner.

AmountValue has no ID. Image such example.

struct Product {
  let id: ID
  let prices: Dictionary<AmountValue, Price>
}

So product has different prices that depend on amount.

these things tend to be money-like in the sense that they have fixed increments, eg. 0.001 lbs. Double is for when you want to represent things that have wildly different magnitudes, like if you wanted to represent 1,000,000,000,000 lbs and 0.0000001 lbs in the same schema. so unless you are doing that, Double is probably the wrong tool for the job.

i’ve long wished swift had modern decimals. but sadly it does not. (this probably isn’t very helpful, i know.)

this is a different problem than the “large structs = slow equality” problem you mentioned earlier:

my non-actionable advice is to use a proper decimal type to model the product quantity. but this type doesn’t exist, we only have NSDecimal. arggghhh!!

1 Like

OK, but if it's not "legal" to compare them with ==, then it's also a programming error to put them in a dictionary, because Dictionary compares them with == internally.

7 Likes

Using a decimal type or not does nothing to change this situation; generally speaking if you should be comparing with a tolerance when using binary floating-point, you should also be comparing with a tolerance when using decimal floating-point or integers for the same computation.

You can construct trivial examples where one happens to work out "correctly" and the other does not, but these are not the norm, and code that assumes such behavior is buggy code.

2 Likes

Increment step is get from server, it is not a constant. And it is a float point number. Double suites well as a back-value of AmountValue .

I wish it too.

This is not the only problem. Another one is that comparing them via == is logically incorrect. The should be compared by InstanceID, like class are compared via === which is not the same as ==.

That's why we use Double for now. Decimal is more suitable but currently is inconvenient in use, and for our tasks there is not so much benefits of using it.

i don’t think this is true at all, if you are using decimals instead of binary floating point, it is probably because you care about exactness. e.g. an account balance either matches exactly, or it doesn’t match at all. interpreting $1.000001 as equivalent to $1.000000 is a very fast way to go bankrupt.

Right. Which is why you would not compare with a tolerance, whether you were using Double or a decimal type or UInt64 or anything else. If it's appropriate to use a tolerance with one, it is appropriate to use a tolerance with the other. If not, it is not appropriate in either case.

are you downloading binary protos? because if you are downloading JSON (or some other text-based serialization format), the JSON is probably encoding a decimal, not a binary float.

this is assuming OP isn’t doing any arithmetic on the AmountValues, no?

I know that Dictionary compares them with == internally, and it is totally correct. But this should not be done in app logic. We can get from server two almost equal values: "5.4999999999999991118215802999" and "5.5000000000000008881784197001". In app logic we should treat them as almost equal. Lats say it is a quantity of bananas user wants to buy. In user cart we have "5.4999999999999991118215802999", but in another response server returned us "5.5000000000000008881784197001". Comparing them with == is a programmer's error.

Finally, in app logic we should use special math and compare funcs, but for Hashability == should be used.

Yes, logically it is decimal. By float-point I meant that in one case Increment step is 0.02 and in another is 1.2

In this case do you want two Set elements / dictionary keys or just one? If just one – then you'd not want to use those values as is as set elements / dictionary keys.

Also not, that generally it's not safe to use Floating point numbers as Set elements / dictionary keys –because of NaN's that always compare false.

One possible workaround would be to use fixed point numbers in this case to sidestep the whole issue.

I can't answer this question directly.

As I wrote in example AmountValue can only be constructed if rawValue .isNormal || .isZero, .sign == .plus, so NaN is impossible.
So I agree generally it's not safe. But after all checks and validation it is.

Which one? Decimal is not suitable as I wrote.

To be focused: float point value that is used as underlying value is not a problem.
The real task is to make possible only special math and compare funcs and impossible to use < > == because it is a programmer error, no matter what underlying type is. For Hashability == should be used.