Skip to content

Instantly share code, notes, and snippets.

@EricDw
Created February 15, 2019 14:52
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save EricDw/07eb81453bd315eb97b092c2a4cf3728 to your computer and use it in GitHub Desktop.
Save EricDw/07eb81453bd315eb97b092c2a4cf3728 to your computer and use it in GitHub Desktop.
KotlinFunctionsProgram
@file:Suppress("KDocUnresolvedReference")
package com.examples.standardfunctions
import androidx.annotation.Nullable
import java.util.*
import java.util.function.Function
// region Person
// region Abstract Person
data class UserId(val value: String = UUID.randomUUID().toString())
abstract class Person(
val id: UserId,
val name: String,
val age: Int?,
val profession: String?,
val friends: List<UserId> = emptyList()
)
val bob = object : Person(
UserId(),
"Bob",
25,
"Kotlin Programmer"
) {}
// endregion Abstract Person
// region Purl
data class Purl(
val knownProgrammingLanguages: List<String> = emptyList()
) : Person(
id = UserId(),
name = "Purl",
age = 25,
profession = "Software Developer"
) {
val purlPrinter = {
println("$name $age $profession $friends $knownProgrammingLanguages")
}
}
// endregion Purl
// region Lacy
data class Lacy(
val knownDesignPatterns: List<String> = emptyList(),
val knownProgrammingLanguages: List<String> = emptyList()
) : Person(
id = UserId(),
name = "Lacy",
age = 32,
profession = "Software Architect"
) {
val lacyPrinter = {
println("$name $age $profession $friends $knownProgrammingLanguages $knownDesignPatterns")
}
}
// endregion Lacy
// endregion Person
// region Person Functions
// Different ways of declaring functions
fun printDetailsFor(person: Person) {
println("${person.name} ${person.age} ${person.profession} ${person.friends}")
}
val personDetailsPrinter = { person: Person ->
println("${person.name} ${person.age} ${person.profession} ${person.friends}")
}
fun Person.print() {
println("$name $age $profession $friends")
}
fun Person.printExpression(): Unit = println("$name $age $profession $friends")
// A function that returns a function
fun getPersonPrinter(): (Person) -> Unit {
return personDetailsPrinter
}
// A function expression that returns a function
fun getPersonPrinterExpression(): (Person) -> Unit = personDetailsPrinter
// Different ways of calling functions
fun callFunctions(vararg functions: () -> Unit) {
functions.forEach { it() }
}
fun callPrinters() = callFunctions(
{ printDetailsFor(bob) },
{ bob.print() },
{ bob.printExpression() },
{ personDetailsPrinter(bob) },
{ getPersonPrinter()(bob) },
{ getPersonPrinterExpression()(bob) }
)
// endregion Person Functions
/** TERMINOLOGY LEGEND
*
* Receiver == [this]
*
* block == A Function
*
* [it] == Automatically declared variable based on type
*
* Scope == Everything that can be accessed from in a function,
* [this] is often implied to be the Scope.
*
* Jump/Return/Break Label == Also known as scope targets e.g. return@run
**/
fun main() {
// callPrinters()
// doRun()
// doWith()
// doTRun()
doLet()
// doApply()
// putItAllTogether()
}
// region run()
fun doRun() {
// Used to execute a block of code from the scope of another function
// Signature ==
fun <R> runArbitraryCode(block: () -> R): R = block()
run {
println("Arbitrary Code Was Run")
}
runArbitraryCode {
println("Arbitrary Code Was Run")
}
// Functionally ==
fun randomCode() {
println("Arbitrary Code Was Run")
}
randomCode()
// Q: When do I use it?
// A: When you need to run something in a function
// Q: When do I use run() instead of T.run()?
// A: When you don't need T and you don't need a reference to the function
}
// endregion run()
// region with()
// Signature ==
fun <T, R> runCodeScopedTo(receiver: T, block: T.() -> R): R = receiver.block()
// Different ways to call with()
fun doWith() {
with(bob) {
println("$name $age $profession $friends")
}
with(bob) {
printDetailsFor(this)
}
with(bob) {
printExpression()
}
with(bob, { print() })
runCodeScopedTo(bob) {
print()
}
// Syntactically ~= too but using generics:
fun doStuffWithVariable(variable: Person) {
val name = variable.name
val age = variable.age
val profession = variable.profession
val friends = variable.friends
println("$name $age $profession $friends")
}
doStuffWithVariable(bob)
println("${bob.name} ${bob.age} ${bob.profession} ${bob.friends}")
// Q: When do I use it?
// A: When accessing multiple properties / functions of an object
// Q: When do I use with() instead of T.run() or T.apply()?
// A: It really depends but almost never due to awkward null handling
// and a more verbose syntax.
}
// endregion with()
// region T.run()
// Signature ==
fun <T, R> T.runArbitraryScopedCode(block: T.() -> R): R = block()
// Functionally ~=
fun javaStyleRun(argument: Any?, function: Function<Any?, Any?>) {
function.apply(argument)
}
fun doTRun() {
// Usage
bob.run { println("$name $age $profession $friends") }
// Argument automatically filled in!! Freakin Sweet!
bob.runArbitraryScopedCode(personDetailsPrinter)
javaStyleRun(bob, object : Function<Any?, Any?> {
override fun apply(t: Any?): Any? {
t as Person
println("${t.name} ${t.age} ${t.profession} ${t.friends}")
return t
}
})
// Or this
javaStyleRun(bob, Function { person ->
person as Person
println("${person.name} ${person.age} ${person.profession} ${person.friends}")
})
}
/*
Q: When do I use it?
A: When accessing multiple properties / functions of an object.
Q: When do I use T.run() instead of with()?
A: It really depends but almost always due to better null handling
and a less verbose syntax.
Q: When do I use T.run() instead of T.let()?
A: I want the function to be scoped to T and
I don't need the scope to be named explicitly.
Q: When do I use T.run() instead of T.apply()?
A: I want the function to be scoped to T and
I am not modifying the state of T or
I need to return a different value then T.
*/
// endregion T.run()
// region T.let()
// When do I use it?
// Mostly to reduce ambiguity caused from nested T.run()s.
// Signature ==
inline fun <T, R> T.letRunOnIt(block: (T) -> R): R = block(this)
fun doLet() {
val oldestAgeWithRuns: Int =
bob.age?.run bob@{
buildStockLacy().age?.run lacy@{
buildStockPurl().age?.run purl@{
var result = this@bob
// do work
if (result < this@lacy) result = this@lacy
if (result < this@purl) result = this@purl
return@bob result
// Wow, that's a lot of @s!
// We can do better!
}
}
} ?: 0
val oldestAgeWithLets: Int =
bob.age?.let theOuterLet@{ bobsAge ->
buildStockLacy().age?.let { laciesAge ->
buildStockPurl().age?.letRunOnIt { purlsAge ->
var result = bobsAge
// do work
if (result < laciesAge) result = laciesAge
if (result < purlsAge) result = purlsAge
return@theOuterLet result
// That's super nice!!
}
}
} ?: 0
println(oldestAgeWithLets)
println(oldestAgeWithRuns)
/*
Functionally == too?
When to use it?
*/
}
/*
Q: When do I use it?
A: The same reason you would use run but you need a named object.
To remove ambiguity in nested T.run()s.
Q: When do I use T.let() instead of with()?
A: It really depends but almost always due to better null handling,
less verbose syntax and to reduce ambiguity around [this]
Q: When do I use T.let() instead of T.run()?
A: Mostly for the same reasons to use it over with()
Q: When do I use T.let() instead of T.apply()?
A: I want the function to be scoped named and
I need to return a different value then T.
*/
// endregion T.let()
// region T.apply()
fun doApply() = {
// We need to make sure the algorithm works
// so we can use T.apply() to inject some logging between the age!
val oldestAgeWithLets: Int =
bob.age?.let theOuterLet@{ bobsAge ->
buildStockLacy().age?.let { laciesAge ->
buildStockPurl().age?.let { purlsAge ->
var result = bobsAge
if (result < laciesAge) result = laciesAge
if (result < purlsAge) result = purlsAge
return@theOuterLet result
}
}
} ?: 0
println(oldestAgeWithLets)
}
// endregion T.apply()
// region Putting It All Together
// Lacy builder DSL made using what we have learned
class LacyBuilderScope {
private val _programmingLanguages = mutableListOf<String>()
private val _designPatterns = mutableListOf<String>()
fun addProgrammingLanguage(
block: () -> String
) = _programmingLanguages.add(block())
fun addDesignPattern(
block: () -> String
) = _designPatterns.add(block())
fun build() = Lacy(_designPatterns, _programmingLanguages)
}
fun buildLacy(block: LacyBuilderScope.() -> Unit): Lacy =
LacyBuilderScope().run {
block()
return@run build()
}
fun buildStockLacy(block: LacyBuilderScope.() -> Unit = {}): Lacy =
LacyBuilderScope().run {
addProgrammingLanguage { "Kotlin" }
block()
return@run build()
}
// Purl builder DSL also made using what we have learned
class PurlBuilderScope {
private val _programmingLanguages = mutableListOf<String>()
fun addProgrammingLanguage(
block: () -> String
) = _programmingLanguages.add(block())
fun build() = Purl(_programmingLanguages)
}
fun buildPurl(block: PurlBuilderScope.() -> Unit): Purl =
PurlBuilderScope().run {
block()
return@run build()
}
fun buildStockPurl(block: PurlBuilderScope.() -> Unit = {}): Purl =
PurlBuilderScope().run {
addProgrammingLanguage { "Kotlin" }
block()
return@run build()
}
fun putItAllTogether() {
val lacy = buildLacy {
addDesignPattern {
"Builder Pattern"
}
addProgrammingLanguage {
"Kotlin"
}
}
val purl = buildPurl {
addProgrammingLanguage {
"Kotlin"
}
}
fun girlsHaveTheSameLanguageSkillSets(
maybePurl: Purl?,
maybeLacy: Lacy?
): Boolean {
maybePurl?.run {
maybeLacy?.run {
// Both of the class specific functions are available to us!
purlPrinter()
lacyPrinter()
// but how do we differentiate from the two with elegance?
}
}
// With a let run or a let let!
var result = false
result = maybePurl?.let { purl: Purl ->
maybeLacy?.run {
return@let knownProgrammingLanguages == purl.knownProgrammingLanguages
}
} ?: false
return result
}
// Why is this better then Java?
fun girlsHaveTheSameLanguageSkillSetsJavaStyle(
@Nullable maybePurl: Purl?,
@Nullable maybeLacy: Lacy?
): Boolean {
var result: Boolean = false
if (maybePurl != null && maybeLacy != null) {
maybePurl.purlPrinter()
maybeLacy.lacyPrinter()
result = maybeLacy.knownProgrammingLanguages == maybePurl.knownProgrammingLanguages
}
return result
}
// Look at everything that is happening here in just 10 lines of code!
fun girlsHaveTheSameLanguageSkillSetsExpression(
maybePurl: Purl? = buildStockPurl(),
maybeLacy: Lacy? = buildStockLacy()
): Boolean = maybePurl?.let { purl: Purl ->
maybeLacy?.run {
purl.purlPrinter()
lacyPrinter()
return@let knownProgrammingLanguages == purl.knownProgrammingLanguages
}
} ?: false
}
// endregion Putting It All Together
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment