#Annotate type as either Int or Float

1 messages · Page 1 of 1 (latest)

late oxide
#

How can I annotate a variable type as either a float or integer
I am making a calculator type app where both types of numbers need to be accepted and treated the same way

fun add(a: Int|Float, b: Int|Float) {
  return a + b
}```

kotlin apparently does not feature type unions.
weary hearthBOT
#

<@&1008423204219531294> please have a look, thanks.

merry silo
#

if u want to add any number, use Number instead

#

otherwise use generics

late oxide
#

that is great

storm acorn
#

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

pulsar birch
late oxide
#

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

pulsar birch
late oxide
#

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.

pulsar birch
late oxide
#

i want to just say 3. not Number(3)

pulsar birch
late oxide
#

Kotlin has Number

#

which does that

#

so I am satisified

pulsar birch
#

number can be any kind of number

#

including stuff like byte

late oxide
#

i probably like that even more

#

number should be all possible numbers

pulsar birch
late oxide
#

floats, irrational, reals, imaginary

pulsar birch
#

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

pulsar birch
#

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

late oxide
#

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

storm acorn
#

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

tired flicker
#

BigDecimal?

fathom widget
#

@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

fathom widget
#
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

pulsar birch
fathom widget
#
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

fathom widget
#

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

fathom widget
#

which feels nice at first, but quickly fucks up a codebase let me tell you

fathom widget
#

if you were modeling an actual calculator you would be modeling buttons and a display

#

and taking input as validated strings or something

pulsar birch
fathom widget
pulsar birch
#

well

#

then just accept BigDecimal

#

it does everything

#

and the only type better than it for a calculator would be a custom type anyway

pulsar birch
#

and for this, the best is to make your own datatype

fathom widget
#

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

storm acorn
fathom widget
#

java generics are invariant

storm acorn
fathom widget
#

meaning if I have a List<Integer> I can't assign it to a List<Number>

storm acorn
#

Trying to make your own generic value types

fathom widget
#
List<Integer> l = List.of(1, 2, 3);
List<Number> l2 = l; // boom
storm acorn
#

The function signatures get quite spiceh

fathom widget
#

this is for a good reason though, because if they were "variant" you could do this

storm acorn
#

flatmap is my favorite Function<? super T, ? extends Optional<? extends U>> mapper

fathom widget
#

its gonna be hard to read chopped up

storm acorn
#

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

fathom widget
#
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

storm acorn
#

yeah, but list is invariant for good reason, because you can both add and get

fathom widget
#
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
storm acorn
#

but for custom types, where you basically only do "out" like Optional it makes the API so much easier

fathom widget
#

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>

storm acorn
#

lol, where you get that type from 😄

fathom widget
#

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");
storm acorn
#

I don't think there's a strong typed language with unions?

fathom widget
storm acorn
#

ah

fathom widget
#

scala is the easiest example

storm acorn
#

wait, scala has it? (I did some scala 10 years ago)

#

Neat!

fathom widget
#
def f(x: Int | Double): Unit = {
  println(x)
}

f(5)

f(3.5)
storm acorn
#

wonder what the underlying code looks like...

pulsar birch