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 = Double
Ray 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 += age
Ray 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.0
Ray 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 = Float
Ray 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
SecondsLong
Ray 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 AMNumber
kqr
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 AMT
Ray 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 generalFarenheit
somewhere.+
T
Ray 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