-
Notifications
You must be signed in to change notification settings - Fork 1
lessons
Miguel Gamboa edited this page Jan 4, 2023
·
48 revisions
Lessons:
- 12-09-2022 - Lesson 01 - Introduction and Kotlin Characteristics
- 14-09-2022 - Lesson 02 - Encapsulation and Limit Mutability
- 14-09-2022 - Lesson 03 - Unit Testing
- 19-09-2022 - Lesson 04 - Types
- 21-09-2022 - Lesson 05 - Types
- 21-09-2022 - Lesson 06 - Enumerated Types
- 26-09-2022 - Lesson 07 - Polymorphism versus Pattern Matching
- 28-09-2022 - Lesson 08 - Domain model
- 28-09-2022 - Lesson 09 - Manage instances and states
- 04-10-2022 - Lesson 10 - OOP versus Functional
- 10-10-2022 - Lesson 11 - Generics
- 14-10-2022 - Lesson 13 - Lab 1 - Workout
- 14-10-2022 - Lesson 14 - Lab 2 - Workout
- 17-10-2022 - Lesson 12 - Unit test of console IO
- 19-10-2022 - Lesson 15 - Storage, Data Mapper and Factory
- 19-10-2022 - Lesson 16 - Serialization and TicTactToe between processes
- 24-10-2022 - Lesson 17 - Serialization and TicTactToe between processes
- 26-10-2022 - Lesson 18 - Lab 3 - Workout
- 26-10-2022 - Lesson 19 - Lab 4 - Workout
- 31-10-2022 - Lesson 20 - Storage with MongoDb
- 02-11-2022 - Lesson 21 - Lab 5 - Workout
- 02-11-2022 - Lesson 22 - Lab 6 - Workout
- 07-11-2022 - Lesson 23 - Compose Intro
- 09-11-2022 - Lesson 24 - TicTacToe GUI with Compose (single device without storage)
-
09-11-2022 - Lesson 25 - Compose
Dialog
andMenuBar
- 14-11-2022 - Lesson 26 - UI thread and blocking issues
- 16-11-2022 - Lesson 27 - Non-blocking IO versus Threads versus Coroutines
- 16-11-2022 - Lesson 28 - Coroutines
- 21-11-2022 - Lesson 29 - Asynchronous API and non-blocking IO
-
23-11-2022 - Lesson 30 - Lab 7 -
MongoStorageAsync
and beginning checkers GUI compose - 23-11-2022 - Lesson 31 - TicTacToe GUI multi device
- 28-11-2022 - Lesson 32 - Composing suspending functions
- 30-11-2022 - Lesson 33 - Lab 8 - Workout
- 30-11-2022 - Lesson 34 - Lab 9 - Workout
- 05-12-2022 - Lesson 35 - Exercises
- 07-12-2022 - Lesson 36 - Lab 10 - Workout
- 07-12-2022 - Lesson 37 - Lab 11 - Workout
- 12-12-2022 - Lesson 38 - Constructors and Properties
- 14-12-2022 - Lesson 39 - Lab 12 - Workout
- 14-12-2022 - Lesson 40 - Lab 13 - Workout
- 04-01-2023 - Lesson 41 - Lab 14 - Workout
- 04-01-2023 - Lesson 42 - Exercises
- 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
- 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 functionsnextMonth()
andaddDays(inc)
- e.g.
- Limit Mutability
- Kotlin mutable properties (
var
) versus immutable (val
) - Implement
Stack
interface based on auxiliaryclass 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.
- Strict, written contract, through annotations (e.g.
- 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 (provides implementation of canonical methods based on its properties)
- 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
etoString
- Canonical functions:
- Modifiers:
abstract
,open
, andoverride
- 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()
andequals()
) - Equality - Structural equality (
==
a check forequals()
) - 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
)
- A data type with a set of named values, i.e. constants (e.g.
-
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
withMonth
andIntExprOperator
withOperator
- Sealed classes and interfaces represent restricted class hierarchies that provide more control over inheritance
-
sealed
versusopen
- 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):
tictactoe-model
-
ui
– console single-device -
storage
- persistence in Document Database - NoSQL | MongoDB -
ui
– console between devices -
gui
- graphical user interface
-
tictactoe-model
:Player
,Position
,Move
andBoard
- runtime checks —
require()
andcheck()
-
IllegalArgumentException
andIllegalStateException
- 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 oflesson09-tictactoe-model
-
lesson10-tictactoe-ui\build.gradle.kts
contains:-
dependencies { implementation(project(":lesson09-tictactoe-model"))
... }
-
-
Command processing loop (i.e.
readCommandsOop
):- Read console input arguments
- Parse arguments and find corresponding command
- Execute command and show:
- the new State (i.e.
Board
) on success - error message if failed.
- the new State (i.e.
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 inreadCommandsOop<Board>(mapOf("QUIT" to QuitCommandOop, ..))
- Refactor
tictactoe-ui
with an auxiliaryui-generic
module.
- Kotlin Lambdas
-
return
with label e.g.return@filter
- Function Reference
- Standard input and output, i.e.
System.in
andSystem.out
- Redirect standard IO, i.e.
System.setIn()
andSystem.setOut()
-
java.io
andInputStream
andOutputStream
hierarchy. - Filters e.g.
PrintStream
- IO in memory
ByteArrayInputStream
andByteArrayOutputStream
- Implementing an utility function
fun redirectInOut(stmts: List<String>, block: () -> Unit) : List<String>
- Review development plan for TicTacToe:
-
tictactoe-model
-
ui
– console single-device -
storage
- persistence in MongoDB or File system. -
ui
– console between different processes and/or devices -
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
thendeserialize(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 simpleDummyEntity
- Serialization of a complex graph of objects.
- E.g.
Board
--->
Move
--->
Position
- Implement auxiliary functions
serialize()
anddeserializeTo...()
on each entity:Board
,Move
. - Unit test for
FileStorage<String, Board>
with aStringSerializer<Board>
TicTactToe between processes:
- Remove argument Player (
X | O
) of command play, e.g.play X 1 1
. - Instead of
play X 1 1
now should beplay 1 1
. - Each
tictactoe-ui
process should keep itsPlayer
and theBoard
. - New entity
Game
. data class Game(val name: String, val board: Board, val player: Player = CROSS)
- Serialization of
Board
with different kind of boards:BoardRun
,BoardDraw
andBoardWin
. -
KClass
- Kotlin class information of a class. - Deserialization based on information of
KClass
of each object.
when(kind) {
BoardRun::class.simpleName -> BoardRun(moves, lastPlayer)
BoardDraw::class.simpleName -> BoardDraw(moves)
BoardWin::class.simpleName -> BoardWin(moves, lastPlayer)
}
-
MyClass::class
- getting the runtime reference to a Kotlin class. - 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
-
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"
}
}
- Outline:
- 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()
- File streams:
- 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:- Multithreading:
thread { r1 = f1() ... }; r2 = f2() ...
(!!!! Blocking !!!) - Callbacks:
f1 { err, r1 -> ... }; f2 { err, r2 -> ... }
-
Promises:
f1().then { r1 -> ... }; f2().then { r2 -> ... };
-
Async function:
async function foo { const r1 = await f1(); const r2 = await f2(); }
- Suspend function:
suspend fun foo() { val r1 = f1(); val r2 = f2(); }
- Multithreading:
- Notice 1. despite being concurrent, it is blocking each calling thread.
-
Notice 4. and 5. are running
f1()
andf2()
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 JDKHttpClient
.
- 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 aJob
<=>
Promise<Void>
-
async {...}
- coroutine builder that starts a separate coroutine and returns aDeferred<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.
-
- How to choose third party library for non-blocking IO?
- Popularity
- Idiomatic API according to the programming environment, e.g. kotlin idiomatic suspend functions.
- 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 functionrequestChuckNorris()
. - Implement
GameAsync
to deal withBoard
andStorageAsync
.
- New interface
StorageAsync
for non-blocking IO -
StorageAsync
<|----
FileStorageAsync
---->
AsynchronousFileChannel
- New
Game
extensions dealing withStorageAsync
- New
GameState
commands launching coroutines to interact withStorageAsync
-
InputDialog
for the name of the new game.
- Multiple HTTP requests concurrently.
- Composing suspending functions
- Cancellation
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
)