https://kotlinlang.org logo
#getting-started
Title
# getting-started
r

Ray Rahke

03/09/2024, 3:44 PM
so kotlin lacks
static
, and swift lacks
abstract
... pick my poision I guess.
a

Adam S

03/09/2024, 3:48 PM
Cunningham's Law: The best way to get the right answer on the Internet is not to ask a question; it's to post the wrong answer.
😂 2
r

Ray Rahke

03/09/2024, 5:17 PM
hoping the answer is wrong
i mean, beyond the obvious stipulations. (like Kotlin having companion objects)
anyone who has been watching the chat over the last week knows I have been seriously struggling to get this language to play nicely with my style haha
r

Rob Elliot

03/09/2024, 5:20 PM
A fun, val or var outside a class or on a companion object is to all intents and purposes static. You can annotate it with
@JvmStatic
if you want it compiled to a static field / method on the JVM.
r

Ray Rahke

03/09/2024, 5:20 PM
right, but I can't inherit it. 😕
like imagine I am making chess. In chess, you have different types of pieces, and each type has a predetermined worth attribute. 1st observation: the
worth
property exists on all of the pieces, which means
worth
is abstract. 2nd observation: The
worth
property has to do with the types of pieces, and not the instances / dynamic state, which means
worth
is static
if you put those together, you get this
Copy code
abstract class Piece {
  abstract static worth: Int
}

class Rook : Piece {
  static worth: Int = 4
}
class Pawn : Piece { 
  static worth: Int = 1
}
but, thats not possible in Kotlin, or apparently any language in existence currently. Even though it is so simple...
r

Rob Elliot

03/09/2024, 5:25 PM
So make it non static. As a val not provided by the constructor it’s guaranteed to be the same for all instances.
r

Ray Rahke

03/09/2024, 5:26 PM
if it is non static, it becomes impossible to ask what the worth of a Rook is if you don't have access to a Rook instance in that scenario
r

Rob Elliot

03/09/2024, 5:51 PM
In my view types should model the shape of data, not the data itself. Use instances for that.
Copy code
enum class ChessPieceType(
  val worth: Int
) {
  PAWN(1),
  ROOK(5), // etc
}

class ChessPiece(val type: ChessPieceType, val x: Int, val y: Int) {
  val worth = type.worth
}
5
r

Ray Rahke

03/09/2024, 7:00 PM
That looks like it is just a more indirect way to achieve what I am after
and a rust procedural style rather than OOP (which kotlin should support given it's Java heritage)
imagine each Piece sub type has their own polymoprhic implementation of a method
r

Rob Elliot

03/09/2024, 7:02 PM
You can’t do what you want to do in Java either
1
👆 1
r

Ray Rahke

03/09/2024, 7:03 PM
sure. I just don't think our languages have gotten that advanced yet. majority of the explanations i'm finding for why different OOP languages can't do what I want tend to entail "our compiler was not made with that in mind, so it isn't really feasible."
or people saying "why would you even want to do that?"
but as I just showed with this trivial chess example
there are very obvious use cases
yeah you can do an alternativer procedural enum + switch case style, like you would in rust.
but why would I want to do that as an OOP domain driven design developer
And that alternative ends up getting pretty rough for method subtyping
imagine each piece subtype has their own
is_legal_move(from, to): boolean
r

Rob Elliot

03/09/2024, 7:05 PM
imagine each Piece sub type has their own polymoprhic implementation of a method
Like this.
Copy code
enum class ChessPieceType(
  val worth: Int?
) {
  PAWN(1) {
    override fun validMoves(piece: ChessPiece): List<ChessPiece> {
      TODO("Not yet implemented")
    }
  },
  ROOK(5) {
    override fun validMoves(piece: ChessPiece): List<ChessPiece> {
      TODO("Not yet implemented")
    }
  };

  abstract fun validMoves(piece: ChessPiece): List<ChessPiece> 
}

class ChessPiece(
  val type: ChessPieceType,
  val x: Int,
  val y: Int,
) {
  val worth = type.worth
  
  fun validMoves(): List<ChessPiece> = type.validMoves(this)
}
r

Ray Rahke

03/09/2024, 7:06 PM
okay if you can put methods on enums that is not as bad
but now it is getting to the point where we have to wonder why we have two separate abstractions for this
you're already coupling data and behavior onto the enum
and then you have a class that couples data with behavior...
you could just unify this into a single class. that represents the Piece Type, and conveniently has a way to instantiate new instances from it.
you could move the x,y properties into the enum too
and then not even have a class at all
or maybe not. nvm
well, you could create a
new(x,y)
method on each enum variant
which returns an anonymous object containing the
x
,
y
as properties
and then voilla, you have reinvented classes
the usefulness of classes has been that they provide a simultaneous way to both create a type definition and provide a mechanism for creating instances of that type. I don't see an advantage in splitting that process between two separate constructs.
Copy code
interface PieceInstance {
    val x: Int
    val y: Int
}

enum Piece(worth: Int) {
    Rook(4) {
        fun new(x: Int, y: Int): PieceInstance {
            return object : PieceInstance {
                val x = x;
                val y = y;
            }
        }
    },
a worse version of classes
a

Arjan van Wieringen

03/10/2024, 11:20 AM
Isn’t companion object the static counterpart?
s

Stephan Schröder

03/10/2024, 12:34 PM
"it becomes impossible to ask what the worth of a Rook is if you don't have access to a Rook instance" as it should be. Or if you come from the other direction, it really sounds like if you want to know the worth of something, you already should be having access to that something.
o

okarm

03/11/2024, 12:36 PM
okay if you can put methods on enums that is not as bad
This quote, coupled with the nonsensical piece of code from the last message, indicates to me that Ray should have been directed to
sealed classes
, which is what he reinvented in his last message.
r

Ray Rahke

03/12/2024, 4:53 PM
@Stephan Schröder You are having tunnel vision on the chess example, because it is hard in this particular case to imagine when you might want to know stuff about a piece type without having an instance of that piece type. But I assure you I have hit multiple scenarios where you need to know that. Here is another example, that is closer to one i've hit IRL
Copy code
abstract class Animal (val name: String, var health: Int) {
    abstract static conflicts: List<KClass<Animal>>
    // we may refuse to let a type of animal to exist in same ecosystem as another animal
}

class Cat (name: String, health: Int) : Animal(name, health) {
    static conflicts: listOf(Dog)
}

class Dog (name: String, health: Int) : Animal(name, health) {
    static conflicts: listOf(Cat)
}


class Ecosystem {
    animals: List<Animal>
    speciesfilter: List<KClass<Animal>> // we can restrict this ecosystem to certain animal types

    fun no_conflicts(a: Animal, b: Animal) {
        return (b::class !in a::class.conflicts && a::class !in b::class.conflicts)
    }
    fun add(animal: Animal) {
        if (species.all { no_conflicts(it, animal) })
            if (speciesfilter.all { it.isInstance(animal) } )
                animals.add(animal)
        else fail
    }
}
So: 1. We have ecosystems, and we want to be able to say that a specific ecosystem can only admit animals of some list of types, like "only cats, dogs, monkeys allowed" etc. assuming no conflicts 2. once you add a dog to the ecosystem, any animal whose type definition inherently conflicts with dogs, cannot be allowed.
you a system that filters for certain types of objects, but those types of objects may require co-dependencies of other types of objects or express inherent conflicts with other types of objects.
> coupled with the nonsensical piece of code from the last message, certainly there was some sense in the code snippet... I am showing that you could, if you wanted to, recreate classes through enums with methods that construct objects