Add an abstraction for Natural Numbers


#1

Hi,

It’s something I wanted to see in Java but it never happens.

I want to have an abstraction covering all natural numbers types (Int, Long, BigInteger or anyone else). The size in memory is finally just a technical concern. When I write extension function on Int for example, most of the time i want to write it on natural numbers.

I give you an example with fizzBuzz :

val Long.fizzBuzz: String
    get() {
        val isFizz = this % 3 == 0L 
        val isBuzz = this % 5 == 0L 
        return when {
            isFizz && isBuzz -> "FizzBuzz"
            isFizz -> "Fizz"
            isBuzz -> "Buzz"
            else -> this.toString()
        }
    }

I must write the following code for Int :

val Int.fizzBuzz: String
    get() = this.toLong().fizzBuzz

Worse than that, I can’t write a method with same name for Iterable and Iterable :

val Iterable<Long>.fizzBuzz
    get() = associate { value -> Pair(value, value.fizzBuzz) }.toSortedMap()
//does not compile 
val Iterable<Int>.fizzBuzz
    get() = associate { value -> Pair(value, value.fizzBuzz) }.toSortedMap()

With a natural number abstraction with all the base operators (+,-,*,/,%) such as Natural Numbers I could write really simpler code keeping the specific Natural Numbers properties.

I add that I don’t want to use ‘Number’ because FizzBuzz does not concern Real Numbers so I do not want BigDecimal, Float or Double !


#2

And what is the problem with Number class? It is a common ancestor to all numbers. You can easily use it in generics construction. You can also create a set of extension function to produce arithmetic operations for numbers. There is a reason why it is not done already. The problem is that in order to perform actual operations you will need to assume some concrete type of the number. It could be done in several ways. For example you can always use double or assume from the left-side value. Groovy converts everything to BigDecimal. All of these ways have some flaws, so I believe that both java and kotlin developers left it for specific user discretion.


#3

Sorry, missed the last part of your post. It is a bit of a strange request. Maybe you should just convert all of your numbers to BigInteger or to Long?


#4

Yes, I can. Here is a design problem not a technical.

In mathematical, natural number have their own properties. We don’t care if it is BigInteger, Int or Long (and most of the time developpers does not too).

I just do not want to choose (in reality I did as you can see here : FizzBuzz.kt but API consumers needs to know that fizzBuzz is only on Int (or Long or BigInteger it does not matter). They have to know how to convert it, or I have to write specific function for each types.

Moreover, AtomicLong and AtomicInteger could be seen as NaturalNumber. Adding property to them could be interesting too.

If someone come one day with a NaturalNumber implementation like a very fast BigInteger implementation ( :slight_smile: ), then you will have to replace all your code, update your libraries except if you have a NaturalNumber abstraction.

We can even imagine optimization on the operator like dealing with Integer, Long or BigInteger following the result size.

Simply, I would like to deal with the mathematical concern only.

And if you write API such as assertion API, I am sure you can delete boilerplates code.


#5

Well, it seems to be interesting, but too narrow to include in standard library. Probably it should be a separate library. To implement this, you need to create a wrapper class called for example NaturalNumber inheriting number class:

class NaturalNumber(val value: Number): Number{
...
}

Then you have to override mathematical operations for it like:

operator fun plus(number: Number): NaturalNumber{
  return NaturalNumber(when(val){
    is Long -> val + number.longValue()
    is Int -> val + number.intValue()
    else -> val.longValue() + number.longValue()
  })
}

Of course, performance would be slightly worse due to additional boxing, but it could not be avoided if you want to work with composites like BigInteger in the same way as with primitives.


#6

I already write something like this two years ago (do not search on the internet, my previous client didn’t like open source).

So implementing this is something possible (in reallity I should do it like this in such a case), but I avoid Float or Double by forcing BigInteger usage.

class NaturalNumber(val value: BigInteger): Number {}
val Int.nn: NaturalNumber
    get() = NaturalNumber(this.toBigInteger())
val Long.nn: NaturalNumber
    get() = NaturalNumber(this.toBigInteger())

That’s true, in this cas it works, but Int and Long are not seen as NaturalNumber. It’s less natural, but probably better than today.

In this cas I can write something like this :

Iterable<NaturalNumber>.fizzBuzz

but I always have to map a set of int or long to nn like following :

set.map { it.nn }.fizzBuzz

And in this case, you have to know that natural number exists. Without this knowledge, you’ll never have the possibility to see interesting extension method. And if you simply extends BigInteger with method and/or property extension, you finally get the same result (am I wrong ?)

I give you another example to illustrate my frustration. Imagine that when you write code in Java or Kotlin you have LittleString, String or BigString. Everyone would dislike it !

Adding just NaturalNumber and RealNumber could really bring an intersting Abstraction. I know that to implement it, you have to modify compiler (such as Any or Int) because you won’t be able to implements this interface for BigInteger) but it could be really good.


#7

Clojure does dynamic typing, so it does this sort of promotion automatically in the background. All numbers are Longs until they have to be BigIntegers. You might take a look at how they do that and port it over to your class.


#8

I ignored it, but main problem will be that Kotlin is (for the best I think) staticaly typed.

If I take my previous example, you really need a strong type to be able to use, it.

To illustrate, my purpose, I add a typealias (but this syntax does not work in Kotlin could work in Ceylon or Typescript).

typealias NaturalNumber = Int|Long|BigInteger
typealias NaturalNumberRange = IntRange|LongRange
typealias FizzBuzzSet = Map<NaturalNumber, String>
/**
 * This is the main extension function solving
 */
val NaturalNumber.fizzBuzz: String
    get() {
        //just an example of 'by lazy usage', here we could simply
        //initialize isFizz or isBuzz directly
        //but the examples shows how lambda can be smart
        val isFizz by lazy { this % 3 == 0 }
        val isBuzz by lazy { this % 5 == 0 }
        return when {
            isFizz && isBuzz -> "FizzBuzz"
            isFizz -> "Fizz"
            isBuzz -> "Buzz"
            else -> this.toString()
        }
    }

/**
 * Using property exension to add fizzBuzz to IntRange
 */
val NaturalNumberRange.fizzBuzz: FizzBuzzSet
    get() = asIterable().fizzBuzz

/**
 * Iterable is a Java interface more general than Collection. It just means i can browse some values. A lot of Kotlin extensions functions are avaialable through Iterable.
 *
 *
 */
val Iterable<NaturalNumber>.fizzBuzz: FizzBuzzSet
    get() = associate { value -> Pair(value, value.fizzBuzz) }.toSortedMap()


val FizzBuzzSet.plainText: String
    get() = when {
        isEmpty() -> "No fizz buzz !"
        else -> "FizzBuzz all !!!! :\n" + map { (key, value) -> "$key : $value" }.reduce { prev, next -> "$prev\n$next" }
    }

Note that having typelias with multiple possible type could solve the problem too (and even more). The only problem is to find a subset of property/methods commons to all types defined in the alias.

And you could do in that case :

1.fizzBuzz
1L.fizzBuzz
1.toBigInteger().fizzBuzz

#9

This could be solved with type classes (from Haskell). However this would be a big change and many programming languages (C#, F#, also kotlin and others) are having debates on how to implement this feature in the best way. (In c# they are calling it shapes).

The current idea is that instead of depending on a specific class, interface (stuck with only instance level operations) you depend on a shape.
The shape is pretty much an interface with the addition of static functions (in kotlin I don’t know how companion object would work with this…), operators and maybe constructors.

This is can then be used to make all numbers implement the shape of the group under +, or *. Or you can make a shape of Natural numbers.

Type classes are also fundamental in functional programming.

for e.g:

typeclass AddGroup {
    operator  fun plus(a : AddGroup)
    val Zero : AddGroup
}

or something of the sort.

I suggest you read about this in the other discussions.


#10

Thank, you I found following KEEP : https://github.com/Kotlin/KEEP/pull/87 and some others discussions about that subject


#11

I think the right place to add this would be the JDK, wouldn’t it? It is open source too of course. It could be an interface that is implemented on the integer (boxing) types only. Kotlin would perhaps eventually pull it into its own standard library as a consequence, as Kotlin’s stdlib is basically a shadow of Java’s.

Note that the auto-boxing it might introduce can be easily eliminated by JIT compilers in most circumstances.


#12

Yes it could probably be in JDK. But typeclasses are even more interesting because it could solve that problems and any ohters of the same type.