Properties: Getters, Setters + self vs this & more

 

self vs this

Access to variables of an object from outside the code of class definition (as in the previous example), is the same for python and kotlin. The code p1.name will access the name variable from the object p1.   Code inside the class must work without any actual object name, so another naming system is needed.  The naming for python is self to indicate the current object, and for kotlin this to indicate the current object.  But the python self is needed far more often than the kotlin this, so in python self.name for the object variable or property, and this.name in kotlin, but in kotlin the this. is only needed when there is a parameter or local with the same name, and normally the this. can be omitted.   So a lot less this in kotlin than self in python.

Again, in python the first parameter to each method in a class should be self, this is not included in the parameter list in kotlin.  Again, less this than self.

# consider in method defintion
    def setNames(self, name): # self as first parameter
       self.name = name
       self.otherName = ""
       self.fullName = name # use name as fullname
//method definition in kotlin
   fun setNames(name): // no 'this' in parameter list
       this.name = name // 'this.name' is property, 'name' is parameter
       otherName = "" // only one 'otherName', so do not need 'this.'
       fullName = "" // also only one 'fullName'

properties: getters and setters

Traditionally, java programmers have been taught that encapsulation (a key part of OO) requires building a class so that how things work can be changed without affecting code using the class. To do this ‘getters’ and ‘setters’ are required, to provide for changes to how data inside the class is used. Instead of allowing a variable to be accessed or set from outside the class, a getter method is created to get the value, and a setter method to set the value. The idea is functions already there in place ready for a possible time when getting or setting is to be become more complex.
Modern languages have identified problems with this approach:
almost all getters and setters just get or set the value and do nothing else so they just bloat the program
it is much clearer for the calling code to get the value of a variable or have an assignment statement to set the value – even when what is happening inside the class is more complex

The solution is:
require code only for the complex cases
ensure setting and getting from outside the class looks the same for simple and complex and is most readable.

Consider this python class:


class Person:
    def __init__(self, name):
       self.name = name
       self.otherName = ""
       self.fullName = name

>>> tom = Person("Tom")  #instance object
>>> tom.fullName = "Tom Jones" # set property using object
>>> tom.fullName  # get property
'Tom Jones'

getting and setting is as simple as possible when using the class, but what if we do wish to ‘complicate’ the fullName property changing the value from being simply its own data, to being the result of name together with otherNames?
Consider:

class Person:

    def __init__(self, name):
       self.name = name
       self.otherName = ""

    @property
    def fullName(self):
	    return " ".join([self.name,self.otherName])
    @fullName.setter
    def fullName(self,value):
	    if " " in value:
	        self.name,self.otherName = value.split(" ",1)
	    else:
	        self.name = value
	        self.otherName = ""
>>> bob = Person("Bob")
>>> bob.otherName = "Jones"
>>> bob.fullName
'Bob Jones'
>>> bob.fullName = "Bobby Smith"
>>> bob.name
'Bobby'
>>> bob.fullName
'Bobby Smith'
>>> bob.otherName
'Smith'

Now we have the new implementation, and all code written before the change will still work.

class Person(var name:String) {
// instance variable declared in constructor

    var otherName = "" // an instance variable not from a parameter
    var fullName
        get()= listOf(name,otherName).joinToString(" ")
        set(value) {
            val (first,second) =
                    if(' ' in value) value.split(" " ,limit=2)
                    else listOf(value,"")
            name = first
            otherName = second
        }
}

The kotlin code for having getters and setters is less changed by adding getters and setters. Simply follow the variable (or value) property declaration with the get and/or set methods.

More?

What is not covered?
Super, which I feel needs no explanation, and
Delegated properties and more complex cases with does need more. I will add a separate page on these but for now see this page, and delegated properties are described here.

Extension functions will also be covered separately.

Advertisements

Defining, Construct & Init for Classes

defining classes: constructors and init

python vs kotlin Syntax

# syntax is:
class ():
    def __init__(self, ):
        # put init code here
#now example 1
class Fred:
    def __init__(self, var1, var2):
       self.var1 = var1
       self.var2 = var3
       self.container = []
#and example 2
class Fred(BaseClass):
    def __init__(self, var1, var2):
       super().__init__(var1)
       self.var1 = var1
       self.var2 = var3
       self.container = []
           # rest of init here

Hopefully the above python code is self explanatory. Example 2 adds a base class.  I do not deal with multiple inheritance at this time, and will devote a specific page at some future time as for python it gets complex, and for kotlin it requires additional concepts.  Now here is the kotlin ‘imitate python’ equivalent to example 1.

//syntax is:
//
 class (): 
//
// example 1a:bad version- to be python like - not best kotlin
class Fred{
    val var1: Int
    val var2: String
    val container: MutableList

    constructor(var1: Int, var2:String){
        this.var1 = var1
        this.var2 = var2
        this.container = mutableListOf()
    }
}
// example 1b: still bad version- to be python like again- not best kotlin
//  move 'constructor' to class definition, now contructor body is 'init'
class Fred constructor(var1: Int, var2:String){
    val var1: Int
    val var2: String
    val container: MutableList

    init{
        this.var1 = var1
        this.var2 = var2
        this.container = mutableListOf()
    }
}

// example 1c:better - 'constructor' keyword omitted and defines variables
// in the primary constructor
class Fred(val var1: Int, val var2: String){
    val container: MutableList

    init{
        container = mutableListOf()
    }
}

First the ‘not the best kotlin way’ examples. Is constructor or init the best equivalent python init? The first example keeps the class definition more similar to python, and uses a constructor to perform the role of the python init. In kotlin, a class can have multiple different constructors to enable constructing an object from different types. So there could be another constructor accepting String in place of Int for the parameters.

But where the python code simply assigns to self.var1 without first declaring var1, in kotlin all variables must be declared, so example aside from the declarations of the three instance variables (var1, var2 and container) and constructor in place of init, 1a above looks almost directly like the python version. However, in this form, there is more code than the python version.

Version 1b above moves the constructor(var1: Int, var2:String to the class declaration. Doing this makes this the default constructor for the class, but the body of the constructor method cannot be on this line declaring the class so the body of the constructor is now called init, and the class declaration reads: class Fred constructor(var1: Int, var2:String).
init is the special reserved word to identify the block of code which is the body of the default constructor.

So example 1b is very similar to example 1a, but introduces the concept of a default constructor and the init block.

Example 1c introduces some improvement. A common pattern is that values to the constructor (init) as saved as instance variables. Simply adding var or val in the constructor means the parameter is the declaration, plus this result in the constructor automatically saving the values passed in. So we lose 4 lines of code as unnecessary (two declarations, plus 2 assignments). We can also omit the word ‘constructor’ for the primary constructor except for some rare special cases. So now the code is almost as brief as the python code. But there are still optimisations to come.

// example 1d:best
class Fred(val var1: Int, val var2: String){
    val container = mutableListOf()
}
// example 2
class Fred(val var1: Int, val var2: String):BaseClass(Var1){
    val container = mutableListOf()
}

So for example 1d, the code is now more concise than python, despite the declaration of variables. Yes, container does now look like a python class variable, but this is how instance variables are in kotlin. So the code is brief with types, and perhaps more so than the code without types. This is because the remaining code in the kotlin constructor prior to this step, initialisation of container, can happen at the declaration of container. So no Normally, all code needed in a constructor is setting initial values, so normally no init block is needed as initial values at the definition, either automatically in the case of default constructor parameters, or at the definition in the main block of other instance variables.

Example 2 covers a class with a base class, just for completeness, to have the syntax covered.

What is a DSL?

With Kotlin the term ‘Kotlin DSL’ usually refers to DSLs built in Kotlin using specific Kotlin features (as discussed in ‘Kotlin DSLs’), this page, however, is about DSLs in general.

  • Introduction:
    • DSL: The general definition
    • DSL vs ‘a DSL’
  • The Types of DSL:
    1. External DSL
    2. Internal Detached DSL
    3. Internal Augmentation DSL
  • Detached vs Internal DSL: A continuity?
  • Language Augmentation in general Vs An Augmentation DSL
  • Conclusion: DSL types can be quite different

Introduction:

DSL: The general definition

The acronym ‘DSL’ stands for ‘Domain Specific Language’. A ‘Domain’ being effectively a particular application or field of expertise. ‘Specific’ is self-explanatory, but what exactly is meant by ‘language’ does warrant further exploration later.

Contrasting with ‘general purpose languages’ which attempt to allow for solving any programming problem, a DSL can be purpose designed for a specific ‘domain’ or a specific type of problem.

The term DSL is a broad term, covering some different types of DSLs.  Sometimes people use the term DSL when they are referring to a specific type of DSL, resulting in the term appearing to mean different things in different contexts.

Martin Fowler (who has written books on DSLs that can be very worthwhile reading) described two different main types of DSL, External and Internal, which differ by how they are implemented.  Next, Martin Fowler explains that the second Implementation type, Internal, itself provides two types of DSL, the Internal Mini-language and the Internal Language Extension. This results in a total of three different types of DSL.

DSL vs a DSL

There is a sematic difference between ‘language’ and ‘a language’.  Consider the two phrases “he likes to use language which is considered antiquated’ and “he likes to use a language which is considered antiquated”.  The first suggests vocabulary within a language e.g. antiquated words within the English language, the second suggests use of a language such as ancient Greek or Latin.

Similarly. ‘domain specific language’ can be though of a terms within a language which are specific to a particular domain’ while ‘a domain specific language’ suggests an entirely new language developed for use in a specific domain.

The Types of DSL: External,  Detached & Augmentation

DSLs come in two main forms: external and internal. An external DSL is a language that is parsed independently of the host general purpose language: good examples include regular expressions and CSS: Martin fowler.

These are DSLs like SQL, or HTML. Languages only applicable within a specific domain (such a databases, or web pages) which are stand-alone languages, but with functionality focused on that specific field or domain, and too limited to be used as a general purpose language.  Implementing a DSL as an external DSLs enables the DSL to be unrelated to the programming language used to write the DSL.

Externals DSLs generally have the same goal as a Detached DSL, but built using a different implementation method.

The key advantage for external DSLs is that by being independent of any base language, they work unchanged with any general language.  So SQL is the same DSL when working with Java, Python, Kotlin or C#.

The first problem with independent DSLs is that the task written using the DSL often also need some general purpose language functionality. So the task will then be written in two languages.  A general purpose language for part of the solution, and a DSL for another part.  The project requires two different languages.

The second problem with independent DSLs is that the features of the general purpose language are not accessible from within the DSL. This means the DSL may need to duplicate features already available in any general purpose languages. Such duplicated features are generally inferior to those in general purpose languages.  E.g. numeric expressions in SQL are not as powerful as most general purpose languages, and there is often a syntax change from the general purpose language.

2. Internal Detached DSLs

When people talk about internal DSLs I see two styles: internal mini-languages and language enhancements.

An internal minilanguage is really using an internal DSL to do the same thing as you would with an external DSL.  Source: Martin Fowler.

Unlike an external DSL, you are limited by the syntax and programming model of your host language, but you do not need to bother with building a parser. You are also able to use the host language features in complicated cases should you need to.

Martin Fowler

Under Martin Fowlers definition, a detached DSL is the first of two types of Internal DSL.  These Internal Detached DSLs, like External DSLs,  are building their own ‘mini-language’ for a specific domain.  Detached DSLS are building ‘a domain specific language‘ as opposed to ‘domain specific language’ vocabulary for an existing language.  With a Detached DSLs, the new stand-alone language is created within an existing language. To achieve being a standalone language,  the DSLs needs to be separated or ‘detached’ from the host language.  Even if such a language is ‘fully-detached’ from the host language, it is will normally be the case that some host language syntax is available from within the DSL.  In all cases, the rules and syntax of the DSL will be shaped by what can be built within the framework of the host language.

This Detached DSL is the type of DSL usually referred to in the discussion of Kotlin DSLs, and of Gradle build files are an example of a Groovy Internal, Detached DSL.

As the goals are the same as External DSLs in creating what can be seen as a standalone language, these DSLs ideally require little understanding of the host language by those using the DSL.  So build.gradle files require, at least in theory, almost no understanding of the Groovy language, or perhaps more realistically, an understanding of only a tiny subset of the host language.  Kotlinx.html is a Kotlin example of this type of DSL built within Kotlin, and the actual Kolinx.html syntax can seem very different to regular Kotlin syntax, even though all code is actually Kotlin.

3: Internal Augmentation DSL.

The alternative way of using internal DSLs is quite different to anything you might do with an external DSL. This is where you are using DSL techniques to enhance the host language. A good example of this is many of the facilities of Ruby on Rails.  Martin Fowler.

Why build a complete language if you can just add features to an existing language?  This third type of DSL no longer has the goal of creating a standalone language. It is ‘domain specific language’ more as a parallel to a set of jargon words for a specific domain can be used in a conversation that is based in English.  The jargon provides new language, but the conversation overall is still in English.   To understand the conversation, you need to know English as well as the specific jargon.  Code using an augmentation DSL will still also make use of the host language.   The program is still seen as in the original language, but using some additional definitions specific to the augmentation DSL. The goal of the augmentation DLS is to add new vocabulary or capability to an existing language, and this makes Augmentation DSLs quite different to the previous DSL types. Instead of an entire stand alone new language, the result is an extension or augmentation to an existing ‘host’ language.  Effectively extending the power of the original host language to have new vocabulary and perhaps also new grammar. This enables the simple and concise expression of ideas and concepts from a specific domain while continuing to use the host language. The augmentation is to be used in combination with the power and flexibility of the host language, which allows for more general areas of a programming in combination with programming for the specialist domain.

Such augmentations still require users to know the host language, but  provide a more homogenous solution than the combination of a stand-alone language with a general purpose language.   For example, while a Python program can build SQL commands to send directly to an SQL database server, an augmentation to python such as SQLAlchemy allow the same power as the SQL language, all within the general syntax of Python.

Detached vs Augmentation DSLs: A continuity?

Both Detached DSLs and Augmentation DSLs are build inside an existing language, and the same set of language features can be used to build either type of DSL.   It is only the goal that is different.  Build a syntax that feels detached from the host language,  or build a syntax that integrates with the host language.

The reality is not every detached DSL is fully detached from the host language, and many do require knowing the host language.

There is a clear test for a fully Detached DSL:  If the DSL can be read, or written, by people with knowledge only of the DSL without needing knowledge of the host language, then it is a fully detached language. Gradle Build files are an example of a internal detached DSL that passes this test, as you can write build files without knowing  the host language (which can be either Groovy or Kotlin).

However,  just because the DSL syntax can be used fully detached from the host language, does not mean actual code in the DSL always will be fully detached from the host language.   For example, Gradle build files can make use of the host language syntax within the build file, and when that host syntax is used, the result is  a build file that does require a knowledge of the host language (which can actually be either Groovy or Kotlin). So for some code, even with a DSL capable of fully detached use,  working with that code will require knowledge of the host language.

Fully detached code can be designed to be  possible, but with the host language syntax available, it cannot be guaranteed all code will be fully detached.

Further, in practice many examples seek to be only partially detached from the host language.  In fact our own example all fit this pattern, as the semi-detached code actually exists interspersed with Kotlin code and there is no goal to enable code be read without knowing Kotlin.

Martin Fowler quotes the examples of the Rake DSL as being able to be categorised as either an independent language or an extension, which in my terminology would suggest it is more to the centre of the continuum.

When we use the term ‘Kotlin DSL’ or even ‘Python DSL’, we mean a DSL made by augmenting Kotlin or Python with both extra ‘vocabulary’ for domain specific features, are rarely.  The DSL is a set of new language constructs which extends an existing language.

Technically, this is always an extended language, but if the goal is to allow the use of these extensions by themselves you have independent language DSL, and if the goal is to allow programs in the host language access to new additional syntax, you have a Language Extensions DSL

An Augmentation DSL vs Language Augmentation

As discussed in languages, all but the simplest human communication makes use of language augmentation, and all but the simplest programs defines variables, functions and other elements that then become part of the language used elsewhere in the program.  An augmentation DSL is created when a specific block of language augmentation (definitions of variables, functions classes or even syntax) is separated from any specific application using that augmentation, and is provided for the use of any application which may require the same functionality.

Conclusion: DSL types can be quite different.

The Rake DSL(Detached/Augmentation hybrid DSL), or Gradle(Detached DSL) or HTML(External DSL):  these are all greatly different examples that all can be called DSL.

When the term DSL is used, it can refer to DSLs in general, but more often one of three entirely different types can be being discussed, and being discussed as if all DSLs are of that type, which can be confusing if you are often dealing with one of the other DSL types.  The term DSL is an extension of the language programming jargon, but perhaps it would be useful to have three additional terms, (making a four-word language extension) with an agreed adjective for each of the three types of DSL.

Class Level Objects, Companion Objects & Instances

Topics for this section:

  • Class Level Objects & Companion Objects:  Key Terminology.
    • the simplest case – one object type
    • Python – an additional object type requires precise terminology
    • Kotlin: the companion object equates to the Python class object
  • Class & Companion Variables and Methods vs instance methods and variables
  •  Terminology for Clarity: Class Level Object, Variables and Methods
  • instance vs class variables in python
  • an alternative explanation of instance vs class for python

Class Level Objects & Companion Objects: Settling on Terminology

This bliki assumes a reader has knowledge of what class is, and what is an object is, but precise usage of the terminology does change from language to language, so please  tolerate a review of very basic concepts in order to be specific about exact terminology

Consider a Person class, that will have two instances, one for “Jack” and one for “Jill”.

The simplest case: the base of confusion

With a compiler language, the compiler will read the class definition and generate the code for all methods required by the class.  The compiler will also store in an internal data structure of the compiler all relevant information about Person class.  For any code relating to the class, the compiler will use the information it has stored about the class to enable generation of the appropriate code.

At run time the Jack and Jill instances Jack and Jill of the person class are created.  So there are two object, both instances of same class: the Person class.  In this environment, any reference to a Person object must be a reference to the Jack or Jill instances.

Python: An additional object type requires precise terminology.

Now consider Python.  The python language also builds the internal data structure containing the relevant information about the person class, and in fact that data structure is an object of the ‘type’ class.  But with python, there is not the same distinction between compile time and run time.  The running program can also access the ‘internal data structure’ – which in fact makes it no so internal.  At run time, there will be an instance of the type class : Person, and once Jack and Jill are instanced, there will be two instances of the Person class: Jack and Jill.  As there is only one Person class regardless of how many instances of Person are generated,  there will always be one Person class level object, regardless of whether there are zero or one hundred instances.  So with the Jack and Jill example with python, there are now three objects available to the running program.  A type object: Person, and two Person Objects: Jack and Jill.  When we say ‘Person object’, do we mean the object describing the class, or one of the two objects which are instances of the class?

In python, the convention is that the object describing the class is the class, and a class variable is a variable within that object describing the class, as opposed to a variable within an instance of the class such as Jack and Jill.  The variables within these instances are instance variables.  This contrasts with the previous simple case, where, with only one instances type of object for any class, the term class variable can only mean an instance variable.

Kotlin: The companion Object equates to the Python class object

Kotlin also provides for an object holding data at the class level, the companion object. Unlike Python, not all classes have a companion object.  Classes with a companion object at class level, must specifically declare a companion object. Keeping the companion object as optional allows the metaprogramming power of Python, or the closer to C++ internal simplicity of Java.

Like the Python class object, there is one companion object for a class, regardless of how many (or even any) instances of the class are created.

Class & Companion Variables and Methods vs instance methods and variables

Variables.

With Kotlin, the developer can declare variables within the companion object, and with Python class variables can be declared at class level.  In both cases, the provides a single variable shared by every instance of the class, and accessible even without the need for an instance.

Java and several other languages have static variables, which have the same property of  a single shared variable regardless of instances of the class and the possibility of access independent an any instance.   However,  there the key difference is with Python and Kotlin, this shared variable is also contained in an object, while a static variable has name scope within a class, but beyond that name, there is no relationship between a static variable and any object.  An example of this difference is that a companion or class object can be part of a family of related classes and implement an interface or have inheritance or duck typing, allowing passing the object as parameter allowing code to operate on the class/companion variable without knowing the specific class.

Simply put, the companion/ class level object is a more object oriented approach that supports more metaprogramming.

Methods

Python supports both class methods and static methods.  Static methods being an equivalent to other static methods in that they have name scope, but have no other relationship with any object.

Kotlin has companion object methods with are similar to python class methods, in that they have access to the object containing all settings, class variables and methods.  They are just like instance methods, but operation on a companion type, or type object, in place of operation on an instance of the class itself.

Terminology for Clarity: Class Level Object, Variables and Methods

The python term ‘class method’ or ‘ class variable’ has a very high protentional for confusion with regular instance variables or instance methods.

The proposal current in use within this bliki is to use the term ‘class level method‘ or ‘class level variable‘.  Another contender in place of ‘level’ was ‘singleton’ but the simpler level is the choice…at least for now.

 

Enum

binaryEnum is an abbreviation for enumerated.  The overall concept is processing information where each value is one of a very limited set of values.  This is a look from the basics to the more advanced in how a dynamic language like python treats enum, through the advanced treatment available in kotlin.

TLDR; Just read the headings until you find sections of interest.

Ascii characters are one of 127 possible characters (although now we use larger character sets), and the original ‘integer’ values were 0,1,2, …. 65,535,  but what about when you have your own finite set of possibilities, like expressing location as ‘at home’, ‘at work’ or simply ‘elsewhere’? Enter, the Enum.

Background

With Digital Data Everything is a number.

In digital computers, every value becomes a number.  Some values, for example a speed, or a length measurement, just are numbers already so to store the value as a number is obvious.  In fact at the simplest level, we have to types of data: integers and floats which are numbers.

Strings: All data can be a string.

But what about all the things that are not normally numbers? Since we use a language based on characters, any value we like can reduced to a string of characters, and as there are established ways of transforming each character into a number for storage in a computer, we could simply store each item as a string of characters.  We call this the string type.  We can store anything, regardless of whether the characters happen to be number or not, so we could even store our numbers as strings.   The string is effectively the common representation we can use for all types of data.  In a sense, all other types are subsets of the possible strings. Integers are subsets where only numeric characters are allowed.  Simplistically, the syntax of float like an integer with the addition of a single “.” character. Both integer and float are just subsets of the valid strings.  In fact you could design a language where string is the only type.

Why not keep everything as a string?

There are advantages not simply treating all data as a string. An integer or float is not just a string restricted to numeric characters, there are also special operations that can only be done with numbers, and special relationships between numbers.  These special subsets has their own rules which only apply to these subsets of possible strings.  For any given length of number, there also less possible alternatives than there are with strings. In the UTF-8 character set,  there are 1,112,064 possible values for each character.  In an integer, there are 10 possibilities for each character.  This allows for far more efficient storage of as an entry within the possible integers, than keeping them as string format which means keeping them as one of the far greater number of possible strings.  The less possible values, the more efficient the storage.  Actually keeping numbers in such a format also simply works with the fact that computers are by nature digital, so they are most efficient with numbers.

So now we have integer, float and string, and again, this would be sufficient, but there are advantages to even more types.

Boolean: The simplest ‘Enum’.

If you are reading this, it can be assumed you understand Boolean, but reviewing the basics is still worthwhile.  Boolean is a type where the values are not characters and not numbers, and therefore fits the classic definition of an Enum type: data with a limited number of possible values that we can ‘enumerate’. For Boolean, there are just two possible values: false and true.

Anything with a fixed number of choices fits the pattern on an enum.  Think of any data you fill out on a form where the answer is one of a fixed set of choices, such as gender: male/female/not-saying, or Title: Mr/Mrs/Dr/Prof/Miss/Ms  or even agreement: strongly-agree/agree/unsure/disagree/strongly-disagree.

Each of the possible choices could be represented as a number, for example:

TITLE_MR = 1
TITLE_MRS = 2
TITLE_DR = 3
#  etc.......

The value for title could be kept in an integer.  In fact a language could do the same with Boolean, and simply have false=0 and true=1 would cover most requirements.  So technically, no Enum values are needed at all.  Just as we could not even bother with the constants and just remember that title of Mrs is number 2.  But having enums which specifically declare what the possible values are makes the code intent far clearer, and the code more easily scanner by a language or a reader for errors.

History

Enums have been added to Python 3.4 as described in PEP 435. It has also been backported to 3.3, 3.2, 3.1, 2.7, 2.6, 2.5, and 2.4 on pypi.

Prior to the introduction of Enum in the language officially, code could use constants for the alternate values as described in background, or made use of a class implement enums.   In fact libraries for Enum were sufficiently widely in use that these were the basis for Enum as added to the python language.

Enum in Python

There is a very simple format for declaring an enum:


>>> from enum import Enum
>>>
>>> Title = Enum("Title", "Mr Mrs Dr") # create type for title
>>> from enum import Enum # do import
>>> title= Title(2) #instance using value
>>> title
<Title.Mrs: 2>
>>> title2=Title["Dr"] # instance using name
>>> title2
<Title.Dr: 3>
>>> title.name # the attributes of 'title'
'Mrs'
>>> title.value
2
>>>

This example shows creating and using an Enum by specifying the names each alternative within the Enum.  In python, each alternative also has a value, and with this format, values for each alternative are assigned automatically, in a sequence starting with 1, so this case uses the values 1, 2 and 3.

Why start at one and not zero? Because in python the number zero can also be considered false, and the design is for every value by default to be true.

An alternative form allows choosing values as well as names.

>>> class Title(Enum):
Mr=0
Mrs = 2
Dr = 5
>>> title = Title(0)
>>> title
<Title.Mr: 0>
>>> title2 = Title["Dr"]
>>> title2.name
'Dr'
>>> title2.value
5
>>>

Note: this does allow starting at zero if you wish, and there are problems as a result
This alternative form can also work with automatic values:


>> from enum import Enum, auto
>>> class Title(Enum):
Mr = auto()
Mrs = auto() # using all automatic values
Dr = auto()
#
>> title2 = Title.Dr # same value as Title["Dr"]
>>> title2
<Title.Dr: 3>
>>> class Title(Enum):
Mr = auto()
Mrs = 5 # choose value # can mix auto() with other values
Dr = auto() # value will follow from previous chosen
#
>>> title2 = Title.Dr
>>> title2
<Title.Dr: 6>

In all these example, the value has been integer, but value can be anything: a float, a string, None, True or False and even a mutable list or a user created class. Auto() still works mixed with any other values, but ignores any non-numeric so

Mr = auto()
Mrs = None # Could be True or even [1,2,3]
Dr = auto()
#  will have values 1, None, 2

This has provided a simple review of enums in python, and illustrated the general concept. See the official python documentation for more details. Note that some cases provided by python are oriented to supporting implementations made before Enum was added to the language.

Enum in Kotlin

enum class Title{
    Mr(),
    Mrs(),
    Dr()
}
val title = Title.Mrs
println("$title  ${title.ordinal} ${title.name}")

This is almost an exact match for the python ‘auto()’ allocation of values. The differences are about ordinal vs value. Firstly, the name is different (ordinal vs value), and ordinal and starts from the more usual zero, not one, kotlin does not need to avoid the clash with python ‘Boolean safety’.

But the biggest difference is that ordinal is fixed. Like the indexes of an array, it must be the sequence of integers from zero … eg 0,1,2 …. It cannot be of another type, nor any other integers.

However, with kotlin, unlike the value in python, ordinal is not the only attribute beyond the name: you can add whatever extra fields you wish by adding them to the enum class.

enum class Title(val value:Int){
    Mr(2),
    Mrs(3),
    Dr(5)
}
println(Title.Mrs.value)
 

This does not provide an exact equivalent for value in python as there is no inbuilt method to find the Enum from the value as here:

>>> class Title(Enum):
Mr=0
Mrs = 2
Dr = 5
>>> title = Title(0)
>>> title
<Title.Mr: 0>

This does not allow ‘value’ to vary in type between class members either, but that could be done in this manner (although it is difficult to see the use):

enum class Title{
    Mr() {val value = 2},
    Mrs() { val value = "abc" }
}

Although this can be done, creating value this way results in reflection being needed to access value, so this is not practical. To have different properties, object should have different classes, so enter sealed classes.

Generally, the Kotlin Enum is an effective replacement for the python Enum. Where it gets interesting, is that in kotlin it does not stop there.

Beyond Enum: Sealed Classes

So far our example has been ‘Title‘, and just keeping it breif dictates that the examples have not even attempted to cover all possibilities. While any reasonable list would of course include Ms and Miss, what about Dame, Madam, Duchess, Duke or Sir?  In Italy Engineer is actually a title so we need to add Eng.  Could we ever be sure to cover every title a person might consider as their appropriate title?  One solution is to simply have people tick “other”.  A problem is that simply recording “other” is almost the same as having no data.  While adding a “please specify” may feel better, without actually recording what is specified, this is almost misleading.

Neither a python Enum,  nor a kotlin Enum, can handle the “other” case.  Every value in an enum is effectively mapped to a number.  Why not add a “specify” property?  Because it does not achieve the goal.  If we have four possible values “Mr/Mrs/Dr/Other” these could be mapped to 1,2,3,4 for storage.  Each choice is just one value.  Every time “Other” is recorded it is exacly the same, including any properties of other, so there is only one value “specify”.  To have additional values for specify we need a new entries in our list in the Enum defintition.  Something like “Mr/Mrs/Dr/Other1(“Miss”)/Other2(“Ms”) ….. in other words we still need to state every possible value of all properties at the time the Enum is declared.

Now consider, having a custom type (and a type is really a class) for each possible value.  So we have a Mr class and a Mrs class, and a Dr class and a Other class.   Then we can have the possible values for our enum as the classes.  Our Enum is no longer constrained (in this example) to four(4) specific values for Mr/Mrs/Dr/Other, but instead to 4 specific classes, and each class can have any number of different objects of that class.  While in a standard Enum type, if there are four possible values then variables of that type could be stored as a number between 1 and 4 (or 0 to 3), with a sealed class with four possible types, the reference to object matching one of the four classes is required, not just a number indicating which class is matched.   Despite this significant difference in how things work internally, the code can look very similar. This sample is attempting to look as similar as
possible to the enum example, but needs adjustment before being useful.

sealed class Title {  // no class parameter required
    object mr   // each entry can be an object definition or class definition
    object mrs  // note this sample shows entries with no base class ...
    object dr   // ... which valid syntax but not very useful
    class Other(val text:String)  // introduction of class member of 'enum'
}

The above example is difficult to use as the alternative values have no common
class so a different variable type is required for each value.

The example below fixes is not just valid syntax, but provides the recommended structure.

sealed class Title(val value:Int) {  // value to match previous enum examples
    object mr: Title(0)   // each object or class based on sealed class
    object mrs: Title(1)  // which gives a common base class
    class Dr: Title(2)   // entries matching the enum patter can be class or object
    // but entries making use of the power of sealed classes must be a class
    class Other(val text:String): Title(5)
}

In kotlin, object declares a singleton class where there is only ever a single instance of the object.  This means there will only ever be one instance of mr which is referenced everywhere, which is similar to the way Enum is implemented.
Both mr and mrs could also be implemented as classes, but since there is no data at all in objects of these classes, every instance of Dr (or a Mr or Mrs class) would be identical and therefore could be references to the same instance.   But the goal of using sealed classes is to provide for classes which do have data so every instance has the potential to be different.

Another example of sealed classes can be found here, with the code here.  Plus the official kotlin information could also be useful.

If there are questions, add a comment and I will attempt to address them.