#Annotate type as either Int or Float
1 messages · Page 1 of 1 (latest)
<@&1008423204219531294> please have a look, thanks.
oh Number is a thing
that is great
Yes, also again, Java is not Typescript :P. For simple calculators, do everything in float, otherwise I would make a CalculatorValue that has an IntValue and a DoubleValue
Just use float, there is no reason to use int here
the reason is that at the level of semantics a calculator should accept integers. that is what a calculator does conceptually
representing everything as a float is an abstraction leakage
im writing kotlin code not c++
domain driven design
make your code reflect the entiites you're tryning to model
not what the machine is doing
except your method does the same thing for both so there is no point at doing that
also, doesn't kotlin support overloading ?
the reason is that on the outside, when i call calculator.add, I will need to say calculator.add(3f, 2f) instead of (3, 2)
which is a leaky abstraction
the outside consumer does not need to know the difference between floats and integers
the philosophical notion of arithmetic does not make that distinction
it is not a big deal but this is a small subtlety that goes against this sort of semantic / domain driven school of thought in software
where we are trying to make our code read as very high level and about ideas rather than concerned with computer stuff
i want to think in terms of the entities and processes in my domain, not in terms of how bytes are represented.
if you really want, then use an interface and different implementations of a number
would that allow for literal values
i want to just say 3. not Number(3)
You are asking for it tho
no I want the language to consider 3 an element of both integers and floats effectively
Kotlin has Number
which does that
so I am satisified
...it is by default
that's not what number is
number can be any kind of number
including stuff like byte
3 is an int
you could cast it to either float or number, in both cast you need a cast
floats, irrational, reals, imaginary
it makes just things really complicated
since
what will you do of it
number can be anything
and so number + number
how do you do this ?
You are talking that you don't want unsafe stuff but you are asking for unsafe stuff
this doesn't make sense
saying that is wanting unsafe stuff
because 3 is int by default
but 3 can be casting into any number
some of them are implicit casts
why would you not want 3 as float but you would want 3 as number ?
both are as unsafe as the other
if you don't want an unsafe structure, you want to specifically annotate this, by using oop, MyFloat(3) + MyInt(3) here there is no incertitude and everything is static and safe
nah im saying for this particular use case I will not run into a scenario where the byte representation. matters
i just want to talk about numbers as a concept
so having a union type type number = int | float (how you do it in python/typescript) is good for that
and because the compiler is intelligent, it actually will not let you do anything unsafe
if you try to do something that only works for integers, it will say "wait, it is not known whether or not the number is a float or integer yet. You must narrow this type to ensure safety"
def a(x: int): ...
def b(x: float): ...
def fn(x: int | float):
a(x) # WARNING! unsafe. x might be a float, and a() only takes integers! Narrow this first!
if (x instanceof int) {
a(x) # no error. x has been narrowed to an integer, safety guaranteed.
} else {
b(x) # x logically must be a float - this is a dysjunctive syllogism
}
i might be wrong but my current conclusion is that a language failing to have type unions is simply a failure of design; it is an obviously good thing to have that is totally safe.
compilers/language servers just need to be capable of doing dysjunctive syllogisms for type narrowing.
java is probably too old to have that and Kotlin inherits i am guessing
Just make it a custom value, have overloaded methods that take either, and convert them at the edges
hmm, in kotlin you have to be explicit what types you pass, in Java an int gets autoconverted to float
BigDecimal?
@late oxide instead of float, use double
all int values fit into double
fun f(x: Int) {
f(x.toDouble())
}
fun f(x: Double) {
println(x * 2)
}
and just have a conversion step
it does mean for add you define...4 methods
fun add(x: Int, y: Int): Double {
return add(x.toDouble(), y.toDouble())
}
fun add(x: Int, y: Double): Double {
return add(x.toDouble(), y)
}
fun add(x: Double, y: Int): Double {
return add(x, y.toDouble())
}
fun add(x: Double, y: Double): Double {
return x + y;
}
fun main() {
println(add(3, 6.5))
}
but you are going to find doing this tricky no matter what
So for actual union types, Java is better, but you can do them like this
sealed interface Num
data class IntNum(val value: Int): Num
data class DoubleNum(val value: Double): Num
you can't make unions with types you didn't define
but you can wrap types you didn't define into unions
Java does have unions, in the form of sealed interfaces
But you still need a wrapper, because otherwise it would be too weakly typed
sealed interface Num
data class IntNum(val value: Int): Num
data class DoubleNum(val value: Double): Num
fun main() {
val n: Num = IntNum(5)
when (n) {
is IntNum -> {
println("Int value: " + n.value)
}
is DoubleNum -> {
println("Double value: " + n.value)
}
}
}
now, would you want to do this for your calculator code?
i wouldn't really
So you are half right and half wrong
it is totally safe to have unions
in fact java has unions with exception catching
try {
doThing();
} catch (NumberFormatException | OtherException e) {
handle(e);
}
but you are wrong in that it is a failure of design to not have them
because they do have downsides
some are super subtle and have to do with type system soundness. not that java's type system is 100% sound, but the rules become way more complicated with "anonymous unions"
because the type system needs to understand how you discriminate between the variants in that union
and that explodes in two places - the user model and error messages
look at a .d.ts file sometime. It can get real wacky real fast
and then there is the more user oriented question of why you would do this
if you make an A | B presumably A and B share some functionality
so there should be a supertype of A and B which has their common methods
Int and Double don't really have any common functionality other than what is defined in Number
so you might as well use Number and .doubleValue()
the other situation is you have distinct A and B cases
where you just need to box up both possibilities
in this situation giving a name to the union of A and B - what that combination represents - has value
hence why Java chose to do the interface thing
and...okay this is a deep rabbit hole, but VM descriptors work best with nominal types too
its a whole thing
When you go down the rabbit hole of wanting this feature, you get scala's implicits
which feels nice at first, but quickly fucks up a codebase let me tell you
....eh? Questionable value here
if you were modeling an actual calculator you would be modeling buttons and a display
and taking input as validated strings or something
...or just float or double
Yeah... the benefit there is not accepting a BigDecimal that is too big to turn into a double
well
then just accept BigDecimal
it does everything
and the only type better than it for a calculator would be a custom type anyway
pretty sure calculator don't have different datatypes, the only thing they represent is a number with the most precision possible
and for this, the best is to make your own datatype
it kinda does actually!
there is an absolutely fucked set of college classes you can take
all about deriving number theory from absolutely minimum principles
like peano arithmetic
all numbers are either
0
Successor(n)
and you can define arithmetic in terms of that, painfully
AHA, I always had a feeling validating types is a huge pain with union types, thanks for confirming this 🙂
for a taste of that
java generics are invariant
And man do I hate that sometimes
meaning if I have a List<Integer> I can't assign it to a List<Number>
Trying to make your own generic value types
List<Integer> l = List.of(1, 2, 3);
List<Number> l2 = l; // boom
The function signatures get quite spiceh
this is for a good reason though, because if they were "variant" you could do this
flatmap is my favorite Function<? super T, ? extends Optional<? extends U>> mapper
this is a long explanation - let me finish
its gonna be hard to read chopped up
And you might say, "but Optional is a final class, why does that matter?", well, I've had it happen (with a different method in the exact same vein), that javac can't infer the actual type
So ? extends Optional is an actual good idea
List<Number> l = new ArrayList<>(List.of(1, 2, 3));
List<Object> l2 = l;
so if you allow this
then you can write code like
yeah, but list is invariant for good reason, because you can both add and get
List<Integer> l = new ArrayList<>(List.of(1, 2, 3));
List<Object> l2 = l;
l2.set(0, "abc");
System.out.println(l.get(0)); // Not a Number
but for custom types, where you basically only do "out" like Optional it makes the API so much easier
if you allowed for anonymous union types, you can end up with this
List<Integer | String> l = List.of(1, 2, "a", "b");
which doesn't seem bad until you consider that there are contexts where java infers the type
var l = List.of(1, 2, "a", "b");
so in this case java will currently infer a List<Serializable & Comparable<? extends Serializable & Comparable<?> & Constable & ConstantDesc> & Constable & ConstantDesc>
lol, where you get that type from 😄
which is the common supertype between those two things
since they share a few interfaces and comparable is a recursive-ish type
so this won't work
List<Object> l2 = l;
but importantly, even if you added Integer | String as a thing you can type, it won't be inferred correctly all the time
and the errors you get when it can't be will suck terribly
List<Integer | String> l = List.of(1, 2.5, "a", "b");
I don't think there's a strong typed language with unions?
yes there are multiple
ah
scala is the easiest example
def f(x: Int | Double): Unit = {
println(x)
}
f(5)
f(3.5)
wonder what the underlying code looks like...
but arrays are covariant 