Ray Rahke
05/06/2024, 8:09 AMtypealias Alias<T> = T
Type alias expands to T, which is not a class, an interface, or an object
I need a way to get this working.Joffrey
05/06/2024, 8:10 AMJoffrey
05/06/2024, 8:11 AMT is not class, interface, nor object? T is a type. Do you need to constrain T to some types?Ray Rahke
05/06/2024, 8:12 AMtypealias Fahrenheit<T> = T
class Context (
val temperature: Fahrenheit<Float>
) {
fun fn() {
return temperature * 2f
}
}Ray Rahke
05/06/2024, 8:12 AMRay Rahke
05/06/2024, 8:12 AMRay Rahke
05/06/2024, 8:13 AMtypealias Fahrenheit = Number
class Context (
val temperature: Fahrenheit
) {
fun fn() {
return temperature * 2f
// now this breaks!
/*
Unresolved reference. None of the following candidates is applicable because of receiver type mismatch:
public inline operator fun BigDecimal.times(other: BigDecimal): BigDecimal defined in kotlin
public inline operator fun BigInteger.times(other: BigInteger): BigInteger defined in kotlin
*/
}
}Ray Rahke
05/06/2024, 8:13 AMJoffrey
05/06/2024, 8:14 AMNumber where everyone told you you shouldn't 😊Ray Rahke
05/06/2024, 8:15 AMRay Rahke
05/06/2024, 8:15 AMtypealias FahrenheitFloat = Float
typealias FahrenheitInt = Int
typealias FahrenheitDouble = DoubleRay Rahke
05/06/2024, 8:16 AMRay Rahke
05/06/2024, 8:16 AMJoffrey
05/06/2024, 8:16 AMRay Rahke
05/06/2024, 8:17 AMRay Rahke
05/06/2024, 8:17 AMRay Rahke
05/06/2024, 8:17 AMRay Rahke
05/06/2024, 8:17 AMRay Rahke
05/06/2024, 8:18 AMval distance: Feet<Float>
val distance2: Feet<Int>
val distance3: Miles<Int>Ray Rahke
05/06/2024, 8:19 AMtypealias K<T> = T
is reasonable in my use caseJoffrey
05/06/2024, 8:19 AMJoffrey
05/06/2024, 8:21 AMJoffrey
05/06/2024, 8:21 AMJoffrey
05/06/2024, 8:22 AMRay Rahke
05/06/2024, 8:22 AMvar age = 15
var countries = 52
countries += ageRay Rahke
05/06/2024, 8:22 AMRay Rahke
05/06/2024, 8:22 AMRay Rahke
05/06/2024, 8:22 AMRay Rahke
05/06/2024, 8:22 AMRay Rahke
05/06/2024, 8:22 AMRay Rahke
05/06/2024, 8:22 AMRay Rahke
05/06/2024, 8:23 AMJoffrey
05/06/2024, 8:23 AMRay Rahke
05/06/2024, 8:23 AMRay Rahke
05/06/2024, 8:24 AMRay Rahke
05/06/2024, 8:24 AMJoffrey
05/06/2024, 8:24 AMRay Rahke
05/06/2024, 8:24 AMJoffrey
05/06/2024, 8:24 AMRay Rahke
05/06/2024, 8:24 AMJoffrey
05/06/2024, 8:25 AMRay Rahke
05/06/2024, 8:25 AMJoffrey
05/06/2024, 8:28 AMRay Rahke
05/06/2024, 9:09 AM@JvmInline
value class Fahrenheit<T>(val value: T) {
operator fun plus(other: T): Fahrenheit<T> = Fahrenheit(value + other)
}
this gives error on then +Joffrey
05/06/2024, 9:10 AMJoffrey
05/06/2024, 9:10 AMT, + doesn't existRay Rahke
05/06/2024, 9:11 AM15.seconds, that will then turn into 15.0Ray Rahke
05/06/2024, 9:11 AMRay Rahke
05/06/2024, 9:12 AMFloat instead since this is for a physics simulation thing so memory is more important than precisionRay Rahke
05/06/2024, 9:12 AMDouble in some other rare timesRay Rahke
05/06/2024, 9:13 AMRay Rahke
05/06/2024, 9:14 AMtypealias Fahrenheit = FloatRay Rahke
05/06/2024, 9:18 AMRay Rahke
05/06/2024, 9:19 AMRay Rahke
05/06/2024, 9:25 AMJoffrey
05/06/2024, 9:25 AMWhen I do 15.seconds, that will then turn into 15.0. isn't that deceptive?Not really. What happens internally and your public API are different things. You can definitely store doubles internally, and do all operations with them, and then expose whatever operations in your API using other number types if they make sense.
Ray Rahke
05/06/2024, 9:26 AMRay Rahke
05/06/2024, 9:26 AMvalue class Fahrenheit<T: Number>(val value: T) {
operator fun plus(other: Fahrenheit<T>) = Fahrenheit<T>(value + other.value)
}Ray Rahke
05/06/2024, 9:26 AMRay Rahke
05/06/2024, 9:26 AMJoffrey
05/06/2024, 9:28 AMplus operator is not defined on Number. Why do you want to store farenheit integers sometimes?Ray Rahke
05/06/2024, 9:29 AMRay Rahke
05/06/2024, 9:29 AMJoffrey
05/06/2024, 9:29 AMRay Rahke
05/06/2024, 9:30 AMTheYeah but I don't wantoperator is not defined onplus.Number
number to be a generic argument. I am trying to say "you can only pass in Float, Int, or Double for the generic argument"Ray Rahke
05/06/2024, 9:30 AMYes, sure. Use floats if your system requires floats. But why do you want to use floats AND integers for the same thing?Most of the time I want floats. Some times I want doubles. Time is an example. Most time variables, like delta time, only require float precision. But elapsed time requires double
Ray Rahke
05/06/2024, 9:30 AMRay Rahke
05/06/2024, 9:31 AMRay Rahke
05/06/2024, 9:32 AMJoffrey
05/06/2024, 9:32 AMFarenheit<Double> + Farenheit<Float>?Ray Rahke
05/06/2024, 9:33 AMRay Rahke
05/06/2024, 9:34 AMJoffrey
05/06/2024, 9:34 AMFarenheit or Distance. You can do that too. The operators in it will be different operators (even though they look like + they are different `+`s, so it's not as redundant as you might thinkJoffrey
05/06/2024, 9:35 AMRay Rahke
05/06/2024, 9:35 AMJoffrey
05/06/2024, 9:35 AMRay Rahke
05/06/2024, 9:35 AMJoffrey
05/06/2024, 9:36 AMRay Rahke
05/06/2024, 9:36 AMRay Rahke
05/06/2024, 9:36 AMRay Rahke
05/06/2024, 9:36 AMRay Rahke
05/06/2024, 9:38 AM@JvmInline
value class Fahrenheit<T>(val value: T) {
operator fun plus(other: Fahrenheit<T>) = Fahrenheit<T>(value + other.value)
}
Fahrenheit<Float> + Fahrenheit<Float> should work...Ray Rahke
05/06/2024, 9:38 AM+Ray Rahke
05/06/2024, 9:38 AMT will be a Float | Int | DoubleJoffrey
05/06/2024, 9:39 AMFarenheit class has no reason to believe you will not use Farenheit<String>, hence why + is forbidden hereRay Rahke
05/06/2024, 9:39 AMRay Rahke
05/06/2024, 9:40 AMinterface Arithmetic {
operator fun plus()...
}
@JvmInline
value class Fahrenheit<T: Arithmetic>(val value: T) {Ray Rahke
05/06/2024, 9:40 AMJoffrey
05/06/2024, 9:40 AMthere is not a way to tell Kotlin that T will be a Float | Int | DoubleEven if there were, it wouldn't help. As I said, the
+ operator is not the same on these types. They don't implement a common Addable interface or something, so you cannot call a virtual + operator and get different implementations for each of these types. You would basically have to create your own + operator on your number, and use a switch to use the specific + in every caseRay Rahke
05/06/2024, 9:40 AMFloat + Float will workRay Rahke
05/06/2024, 9:40 AMJoffrey
05/06/2024, 9:41 AM+ symbol once and expecting it to refer to different functions.Ray Rahke
05/06/2024, 9:41 AMT throughout the class. So if you instantiate it as Fahrenheit<Float>, then it's + operator will only operate on other FloatsJoffrey
05/06/2024, 9:42 AM+ operator does the Farenheit class (as a WHOLE) use? Why would it match the one from Float?Ray Rahke
05/06/2024, 9:42 AMRay Rahke
05/06/2024, 9:42 AMJoffrey
05/06/2024, 9:43 AMFarenheit class and compiles it. Now it has to compile the + operator you used between your T instances without knowing T.Ray Rahke
05/06/2024, 9:44 AMRay Rahke
05/06/2024, 9:44 AMRay Rahke
05/06/2024, 9:44 AMRay Rahke
05/06/2024, 9:44 AMJoffrey
05/06/2024, 9:45 AMRay Rahke
05/06/2024, 9:45 AMSecondsInt
SecondsFloat
SecondsDouble
SecondsLongRay Rahke
05/06/2024, 9:45 AMRay Rahke
05/06/2024, 9:46 AM@JvmInline
value class Fahrenheit<T>(val value: T) {
operator fun plus(other: Fahrenheit<T>) = Fahrenheit<T>(value + other.value)
}
If kotlin worked how i expected, this would prevent mixing value types in operations.Joffrey
05/06/2024, 9:46 AMFloat over Double internally yet still allow using Double internally in places where high precision is required. You haven't talked about using integer numbers besides the convenience of creating values, which doesn't require new types.Ray Rahke
05/06/2024, 9:47 AMJoffrey
05/06/2024, 9:48 AMJoffrey
05/06/2024, 9:49 AMRay Rahke
05/06/2024, 9:50 AMT in the relevant scenarios, then
@JvmInline
value class Fahrenheit<T: Arithmetic>(val value: T) {
operator fun plus(other: Fahrenheit<T>) = Fahrenheit<T>(value + other.value)
}
would be a scalable solutionRay Rahke
05/06/2024, 9:50 AMRay Rahke
05/06/2024, 9:50 AMRay Rahke
05/06/2024, 9:50 AMRay Rahke
05/06/2024, 9:55 AM@JvmInline
value class FahrenheitFloat(val value: Float) {
operator fun plus(other: FahrenheitFloat) = FahrenheitFloat(value + other.value)
operator fun minus(other: FahrenheitFloat) = FahrenheitFloat(value - other.value)
operator fun times(other: FahrenheitFloat) = FahrenheitFloat(value * other.value)
operator fun div(other: FahrenheitFloat) = FahrenheitFloat(value / other.value)
}
@JvmInline
value class FahrenheitDouble(val value: Float) {
operator fun plus(other: FahrenheitFloat) = FahrenheitFloat(value + other.value)
operator fun minus(other: FahrenheitFloat) = FahrenheitFloat(value - other.value)
operator fun times(other: FahrenheitFloat) = FahrenheitFloat(value * other.value)
operator fun div(other: FahrenheitFloat) = FahrenheitFloat(value / other.value)
}
This is a lot of boilerplate 😕Ray Rahke
05/06/2024, 9:55 AMJoffrey
05/06/2024, 9:56 AMArithmetic interface, you could definitely do that. What you're missing here is this common interface for primitive numbers. That said, if you had this interface, you would probably use it here, and most likely your performance concerns over float vs double would be dwarved by boxing and virtual dispatch, and would be better off using double everywhere.Joffrey
05/06/2024, 9:56 AMInt, Float, Double don't implement your Arithmetic interfaceRay Rahke
05/06/2024, 9:57 AMNumberkqr
05/06/2024, 9:57 AMRay Rahke
05/06/2024, 9:57 AMFahrenheit<Float>(5) + Fahrenheit<Float<(5) will definitely succeedRay Rahke
05/06/2024, 9:57 AMRay Rahke
05/06/2024, 9:58 AMRay Rahke
05/06/2024, 9:58 AMJoffrey
05/06/2024, 9:58 AMme and you in this conversation can know that Fahrenheit<Float>(5) + Fahrenheit<Float<(5) will definitely succeedI don't. I don't know myself what
+ you expect to be used in Farenheit, unless you define this general + somewhere.Ray Rahke
05/06/2024, 9:58 AMJoffrey
05/06/2024, 9:59 AMTRay Rahke
05/06/2024, 9:59 AMI don't. I don't know myself whatthe + that is built intoyou expect to be used in+, unless you define this generalFarenheitsomewhere.+
TRay Rahke
05/06/2024, 9:59 AMFloat for TRay Rahke
05/06/2024, 9:59 AMJoffrey
05/06/2024, 9:59 AMthe + that is built into TThere is no such thing for any
T. What if you pass InputStream as T?Ray Rahke
05/06/2024, 10:00 AMJoffrey
05/06/2024, 10:00 AMJoffrey
05/06/2024, 10:01 AMNumber as a backing value, and then use when everywhere in your implementations to define the behaviourJoffrey
05/06/2024, 10:02 AMRay Rahke
05/06/2024, 10:02 AMRay Rahke
05/06/2024, 10:03 AMJoffrey
05/06/2024, 10:03 AMplus operatorsJoffrey
05/06/2024, 10:04 AMwhen approach I was mentioning), or using a common interface (which doesn't exist for Int, Float and Double). Kotlin doesn't use duck typing.Joffrey
05/06/2024, 10:15 AMkotlin
@JvmInline
value class FahrenheitFloat(val value: Float) {
operator fun plus(other: FahrenheitFloat) = FahrenheitFloat(value addFloat other.value)
operator fun minus(other: FahrenheitFloat) = FahrenheitFloat(value minusFloat other.value)
operator fun times(other: FahrenheitFloat) = FahrenheitFloat(value timeFloat other.value)
operator fun div(other: FahrenheitFloat) = FahrenheitFloat(value divFloat other.value)
}
@JvmInline
value class FahrenheitDouble(val value: Double) {
operator fun plus(other: FahrenheitDouble) = FahrenheitDouble(value plusDouble other.value)
operator fun minus(other: FahrenheitDouble) = FahrenheitDouble(value minusDouble other.value)
operator fun times(other: FahrenheitDouble) = FahrenheitDouble(value timesDouble other.value)
operator fun div(other: FahrenheitDouble) = FahrenheitDouble(value divDouble other.value)
}
There is almost 0 duplication in this code, despite appearances. I hope this makes things slightly clearer.Joffrey
05/06/2024, 10:23 AMkotlin
@JvmInline
value class Fahrenheit<T: Number>(val value: T) {
init {
check(value is Int || value is Float || value is Double) { "The value must be an Int, Float, or Double" }
}
operator fun plus(other: Fahrenheit<T>) = Fahrenheit<T>(value + other.value)
}
@Suppress("UNCHECKED_CAST")
private operator fun <T: Number> T.plus(other: T): T = when {
this is Int && other is Int -> (this + other) as T
this is Float && other is Float -> (this + other) as T
this is Double && other is Double -> (this + other) as T
else -> error("Unsupported operand types ${this::class.qualifiedName} + ${other::class.qualifiedName}")
}
But honestly I don't find it much better than the straightforward approach.
Again, the different plus operators have to be defined somewhere, and in that case it's in the when.Joffrey
05/06/2024, 10:26 AMPlusable, Minusable, Divable, Timesable, or something). But again, you would then use it in your case here, and this is most probably less performant than just using double everywhere for your use case.Ray Rahke
05/06/2024, 11:05 AMRay Rahke
05/06/2024, 11:05 AMTim McCormack
05/06/2024, 11:33 AMTim McCormack
05/06/2024, 11:33 AMJoffrey
05/06/2024, 11:52 AMJoffrey
05/06/2024, 11:53 AMis that check version a runtime thing?@Ray Rahke yes that's at runtime, so yeah there is in theory a tiny overhead in every constructor invocation. But it would require measuring to see if it actually is noticeable
Ray Rahke
05/06/2024, 12:45 PM