lessons - isel-leic-tds/2223i.LI32D GitHub Wiki

Lessons:


  • Characterizing Kotlin programming environment:
    • Statically typed
    • For JVM and for native and cross-platform
    • Multiparadigm: imperative, object-oriented and functional.
  • Programming languages retrospective and comparison with Kotlin.
  • Since functional programming languages to object oriented, Lisp, Java, JavaScript, C#, Scala, etc
  • Software and source code quality criteria: Reliability, Testability, Readability and Extensibility.
  • Lessons approach:
    • Git repository
    • Multi-module project (one module per lesson) with gradle wrapper (version 7.5.1)
    • Kotlin release 1.6....
    • IntelliJ 2022
    • Homework workouts

  • Interface as a contract (e.g. Stack<T>) with abstract member functions
  • Abstract classes => cannot be instantiated
  • Instantiate => create an instance of a class
  • Object's state => values of the object's properties

  • Restricting and managing access to object’s state (i.e. instance properties or fields).
  • Hide properties inside the class (private)
  • Provide methods/functions to access object’s state:
    • e.g. NaifDate member functions nextMonth() and addDays(inc)

  • Limit Mutability
  • Kotlin mutable properties (var) versus immutable (val)
  • Implement Stack interface based on auxiliary class Node<T>(val item: T, val next: Node<T>?) with the expected behavior:
interface Stack<T> {
  fun push(item: T)
  fun peek(): T
  fun isEmpty(): Boolean
  fun pop(): T
}
fun main() {
  val stk = StackImpl<String>()
  stk.push("ISEL")
  stk.push("TDS")
  println(stk.peek()) // TDS
  while( !stk.isEmpty() ) println( stk.pop() ) // TDS ISEL
  stk.peek() // throws NoSuchElementException
}


  • "isolate each part of the program and show that the individual parts are correct"
  • E.g. refactoring the former main() in individual unit tests:
    • new instance of Stack should be empty
    • last pushed item is the peeked item
    • after pop of a Stack with single item it stays empty
    • pop an empty stack throws NoSuchElementException
    • peek an empty stack throws NoSuchElementException
  • Unit testing frameworks (e.g. JUnit, TestNG, etc) provide:
    • Strict, written contract, through annotations (e.g. @Test, @Expect, etc).
    • API to express and evaluate the expected behavior (i.e. assertions).
    • Automated runtime to scan, perform and collect tests results.
  • E.g. kotlin.test. API:
    • assertEquals(expected, actual)
    • assertTrue(condition)
    • assertFailsWith<ExceptionClass> { ... /* block */ ... }

  • Limit Mutability, E.g. Implementation of an immutable FixedStack
  • Implementation of an immutable NaifDate
  • Classes and data classes
  • Classes may have a primary constructor and one or more secondary constructors.
  • The primary constructor is a part of the class header.
  • Initialization code can be placed in initializer blocks prefixed with the init keyword.

  • Any - the root of the Kotlin class hierarchy
  • Canonical functions: equals, hashCode e toString
  • Modifiers: abstract, open, and override
  • E.g. override fun toString(): String = "$day-$month-$year"
  • Classes and data classes (provides implementation of canonical methods based on its properties)
  • Inspecting resulting data classes with javap (e.g. toString() and equals())
  • Equality - Structural equality (== a check for equals())
  • Referential equality (=== two references point to the same object)
  • Properties with custom accessors - called every time you access the property (this way you can implement a computed property)
  • Property versus Custom getter:
    • val nextMonth = month % 12 + 1 => private final int nestMonth; (JVM field)
    • val nextMonth get() = month % 12 + 1 => private final int getNextMonth(); (JVM function)
  • Implement a prefix calculator for integer expressions.
  • Object oriented approach => interface IntExpr { fun eval() : Int }
    • IntExpr <|----- IntExprLiteral
    • IntExpr <|----- IntExprOperator
  • v1 – without typed operator - Operator is a Char
  • v2 – with a typed operator (i.e. Strongly Typed)
  • Enumerated Type (e.g. Kotlin enum class):
    • A data type with a set of named values, i.e. constants (e.g. SUM, DIV, etc)
    • Each constant is an instance of its enumerated type (e.g. Operator)
  • Companion Object - its members can be called simply by using the class name as the qualifier (e.g. Operator.parse(...))
  • E.g.
enum class Operator(private val opr: Char) {
  ...
  companion object {
      fun parse(opr: Char) : Operator {
          return values().find { it.opr == opr } ?: throw IllegalArgumentException()
      }
  }
}

  • Object declarations - object - singleton pattern.
  • Operator overloading, e.g. binary operations
  • Association ---> versus Inheritance -----|>
  • UML classes diagrams.
  • E.g. NaifDate with Month and IntExprOperator with Operator
  • Sealed classes and interfaces represent restricted class hierarchies that provide more control over inheritance
  • sealed versus open
  • Implementing functional approach for IntExpr
  • Dynamic dispatch (polymorphism) versus Pattern matching
  • E.g. dynamic dispatch - invokeinterface
  • E.g. pattern matching
fun IntExpr.eval(): Int = when(this) {
    is IntExprLiteral -> nr
    is IntExprOperator -> opr.eval(a.eval(), b.eval())
}
  • Structuring modules to build a TicTacToe game for UI console and GUI (graphical user interface):
    1. tictactoe-model
    2. ui – console single-device
    3. storage - persistence in Document Database - NoSQL | MongoDB
    4. ui – console between devices
    5. gui - graphical user interface
  • tictactoe-model: Player, Position, Move and Board
  • Control illegal arguments and states:
    • IllegalArgumentException and IllegalStateException
  • runtime checks — require() and check()
    • IllegalArgumentException and IllegalStateException
  • Manage Position instances
  • private constructor
  • Make Position(..., ...) forward to:
    • companion object { operator fun invoke(l: Int, c: Int): Position {...}}
  • Parentheses are translated to calls to invoke with appropriate number of arguments.
  • Manage invalid states
  • Type checks and casts
  • "Unsafe" cast operator, E.g. (board as BoardWin).winner
  • lesson10-tictactoe-ui module depending of lesson09-tictactoe-model
  • lesson10-tictactoe-ui\build.gradle.kts contains:
    • dependencies { implementation(project(":lesson09-tictactoe-model")) ... }
  • Command processing loop (i.e. readCommandsOop):
    1. Read console input arguments
    2. Parse arguments and find corresponding command
    3. Execute command and show:
      1. the new State (i.e. Board) on success
      2. error message if failed.
  • fun readCommandsOop(cmds: Map<String, CommandOop>)
  • Command OOP versus Functional:
interface CommandOop {
  fun action(board: Board?, args: List<String>) : Board?
  fun show(board: Board)
  val syntax : String
}
...
object CmdQuitOop : CommandOop {
  override fun action(board: Board?, args: List<String>) = null
  override fun show(board: Board) {}
  override val syntax: String get() = "quit"
}
class CommandFp (
  val action: (Board?, List<String>) -> Board?,
  val show: (Board?) -> Unit,
  val syntax : String
)
...
val cmdQuitFp = CommandFp(
    action = { _, _ -> null },
    show = { },
    syntax = "quit"
)
  • Generic classes with type parameters, e.g. class A<T> { ... }
  • Generic functions with type parameters, e.g. fun <T> foo() { ... }
  • Generic classes or functions are used with type arguments:
    • A<String>()
    • foo<Int>()
  • If the type arguments can be inferred, for example, from the actual parameters, you can omit the type arguments
  • E.g. Board may be inferred in readCommandsOop<Board>(mapOf("QUIT" to QuitCommandOop, ..))
  • Refactor tictactoe-ui with an auxiliary ui-generic module.
  • Kotlin Lambdas
  • return with label e.g. return@filter
  • Function Reference

  • Standard input and output, i.e. System.in and System.out
  • Redirect standard IO, i.e. System.setIn() and System.setOut()
  • java.io and InputStream and OutputStream hierarchy.
  • Filters e.g. PrintStream
  • IO in memory ByteArrayInputStream and ByteArrayOutputStream
  • Implementing an utility function
fun redirectInOut(stmts: List<String>, block: () -> Unit) : List<String>
  • Review development plan for TicTacToe:
    1. tictactoe-model
    2. ui – console single-device
    3. storage - persistence in MongoDB or File system.
    4. ui – console between different processes and/or devices
    5. gui - graphical user interface
  • Design storage operations for Board of tictactoe domain.
  • Use a unique identifier such as a String name.
  • CRUD - four basic operations of persistent storage.
  • Design generic interface Storage<K, T> inspired by Data Mapper design pattern.
  • Implementation of FileStorage<K,V> for file system.
  • Dependency of factory, i.e. () -> T inspired by Abstract Factory Pattern
interface Storage<K, T> {
    fun new(id: K): T
    fun load(id: K): T?
    fun save(id: K, obj: T)
    fun delete(id: K)
}
  • Serialization - process of translating object state into a stream that can be stored or transmitted, and reconstructed.
  • serialize(object) -> stream
  • deserialize(stream) -> object
  • Resulting stream can be in binary or text format (e.g. XML, YAML, JSON, or other).
  • Given obj: Any then deserialize(serialize(obj)) == obj
  • Define interface for String serialization:
interface StringSerializer<T> {
    fun write(obj: T): String
    fun parse(input: String): T
}
  • Unit tests for FileStorage with a simple DummyEntity
  • Serialization of a complex graph of objects.
  • E.g. Board ---> Move ---> Position
  • Implement auxiliary functions serialize() and deserializeTo...() on each entity: Board, Move.
  • Unit test for FileStorage<String, Board> with a StringSerializer<Board>
  • Serialization of a complex graph of objects.
  • E.g. Board ---> Move ---> Position
  • Implement auxiliary functions serialize() and deserializeTo...() on each entity: Board, Move.
  • Unit test for FileStorage<String, Board> with a StringSerializer<Board>

  • Serialization of Board with different kind of boards: BoardRun, BoardDraw and BoardWin.
  • KClass - Kotlin class information of a class.
  • Deserialization based on information of KClass of each object.
when(kind) {
  BoardRun::class.simpleName -> BoardRun(moves, moves.last().player)
  BoardDraw::class.simpleName -> BoardDraw(moves)
  BoardWin::class.simpleName -> BoardWin(moves, moves.last().player)
}
  • MyClass::class - getting the runtime reference to a Kotlin class.
  • Remove argument Player (X | O) of command play, e.g. play X 1 1.
  • Instead of play X 1 1 now should be play 1 1.
  • Each tictactoe-ui process should keep its Player and the Board.
  • New entity Game.
  • data class Game(val name: String, val board: Board, val player: Player = CROSS)
  • Reimplement commands for Game, i.e. CommandOop<Game>
  • New commands depend of Storage<String, Board>
  • Deploy and run tictactoe UI:
gradlew lesson17-tictactoe-ui-storage:jar
java -jar lesson17-tictactoe-ui-storage\build\libs\lesson17-tictactoe-ui-storage.jar
  • MongoDB data model: db -->* collection -->* document
  • collection like a folder or table and document like a file or row.
  • cloud.mongodb.com: organization -->* project -->* cluster -->* db
  • Implement class MongoStorage<K : String, T>(...) : Storage<K, T>
  • tictactoe-ui-storage-mongo

Compose Mental Model:

  • Declarative UI model => regenerate the entire screen from scratch, then apply only the necessary changes.
  • Compose is a declarative UI framework - build UI by defining a set of composable functions.
  • composable function = take in data and emit UI elements. E.g.:
@Composable fun Greeting(name: String) { Text("Hello $name") }
  • composable function is idempotent and free of side-effects.
    • indempotent - behaves the same way when called multiple times with the same argument.
    • free of side-effects, such as modifying properties or global variables.
  • When the user interacts with the UI, the UI raises Events e.g. onClick.
  • Events => notify the app logic => change the app's State
  • State changes => composable functions are called with new data => UI elements are redrawn
  • Recomposition:
    • Composable functions can execute in any order
    • Composable functions can run in parallel
    • Recomposition skips as much as possible
  • composable function's arguments are representations of the UI State.
  • Managing State

  • gradle Run scripts
  • InteliJ Settings - Build and run using: Gradle
  • build.graddle.kts:
plugins {
    id('org.jetbrains.kotlin.jvm') version "1.7.20"
    id("org.jetbrains.compose") version "1.2.0"
}
dependencies {
    implementation(compose.desktop.currentOs)
    ...
}
compose.desktop {
    application {
        mainClass = "pt.isel.AppKt"
    }
}

  • TicTacToe GUI with Compose (single device without storage)
    • @Composable fun Cell(lin: Int, col: Int, player: Player?, action: (Position) -> Unit )
    • @Composable fun BoardView(board: Board, action: (Position) -> Unit)
  • Blocking IO, e.g.:
    • File streams: val str = file.readText()
    • MongoDB driver: val doc = collection.findOneById(id)
    • HTTP: val body = URL(...).readText()
  • Blocking IO => the Result == Returned value, i.e. str, doc, body
  • Blocking IO => the caller's thread waits (i.e. blocking) for operation completion.
  • UI Thread is blocking whenever it performs blocking IO.
  • UI Thread is blocking => GUI will freeze (e.g. circular tap shadow in the center of the board).

  • "For decades, as developers we are confronted with a problem to solve - how to prevent our applications from blocking."
  • Synchronous Calls => the Result == Returned value (Blocking)
  • Concurrent Calls => many approaches:
    1. Multithreading: thread { r1 = f1() ... }; r2 = f2() ... (!!!! Blocking !!!)
    2. Callbacks: f1 { err, r1 -> ... }; f2 { err, r2 -> ... }
    3. Promises: f1().then { r1 -> ... }; f2().then { r2 -> ... };
    4. Async function: async function foo { const r1 = await f1(); const r2 = await f2(); }
    5. Suspend function: suspend fun foo() { val r1 = f1(); val r2 = f2(); }
  • Notice 1. despite being concurrent, it is blocking each calling thread.
  • Notice 4. and 5. are running f1() and f2() NOT concurrently (although, non-blocking).
  • requestChuckNorrisParallel() - New thread to perform blocking IO.
    • Avoid it !!!! Threads are for CPU bound work!
  • requestChuckNorrisNio() - With non-blocking IO via native JDK HttpClient.

  • How to choose third party library for non-blocking IO?
    1. Popularity
    2. Idiomatic API according to the programming environment, e.g. kotlin idiomatic suspend functions.
    3. Reliability => ensure it really uses native non-blocking IO.
  • !!! NOTICE !!! Many libraries with Asynchronous API are not using non-blocking IO techniques.
  • Using Ktor library to implement suspend function requestChuckNorris()
  • Coroutine - instance of suspendable computation.
  • A coroutine is NOT bound to any particular thread.
  • Structured Concurrency:
    • new coroutines can be only launched in a specific CoroutineScope, which delimits the lifetime of the coroutine.
    • ensures that coroutines are not lost and do not leak.
    • An outer scope cannot complete until all its children coroutines complete.
  • Special functions:
    • launch {...} - coroutine builder that starts a separate coroutine and returns a Job <=> Promise<Void>
    • async {...} - coroutine builder that starts a separate coroutine and returns a Deferred<T> <=> Promise<T>
    • delay() - special suspending function
    • runBlocking {...} - coroutine builder that bridges the non-coroutine world with coroutines.
    • coroutineScope {...} - used inside any suspending function to perform multiple concurrent operations.
  • New interface StorageAsync for non-blocking IO
  • StorageAsync <|---- FileStorageAsync ----> AsynchronousFileChannel
  • New Game extensions dealing with StorageAsync
  • New GameState commands launching coroutines to interact with StorageAsync
  • InputDialog for the name of the new game.

TDS-2122_T1-parte2.pdf

fun main() = application {
  MaterialTheme {
    val state = WindowState(width= 250.dp, height= Dp.Unspecified)
    Window(onCloseRequest= ::exitApplication, state= state, title= "Occupancy") {
      MainContent()
    }
  }
}
  • Primary and secondary constructors.
  • init - Initialization code in initializer blocks.
  • Default values in constructor parameters.
  • Defining properties in the primary constructor.
  • Getter in properties readOnly(val)
  • Setter on read/write properties (var)
  • Getters and Setters on global properties.
  • Late-init read/write properties (lateinit)
  • Delegated properties (by)

TDS-2122_T2.pdf

TDS-2122_T2.pdf

⚠️ **GitHub.com Fallback** ⚠️