12 mai 2022
Les execrices sont tirés du livre:
Functional programing in kotlin by tutorials
Written by Massimo Carli
repo: https://github.com/kodecocodes/fpk-materials
notions: affichage écran, fonction
FirstProgramTest: source
notions: memoire, variable, valeur, objet, extension de fonction
ExampleUnitTest: source
notions: ensembles, boucles
Introduction to kotlin
BirthdayMessageTestOutput: source
package functional
import kotlin.test.Test
import kotlin.test.assertEquals
class DeclarativeTests {
val input = listOf(
"123", "abc", "1ds", "987", "abdf", "1d3", "de1", "88", "101"
)
fun imperativeSum(list: List<String>): Int {
var sum = 0
for (item in list) {
try {
sum += item.toInt()
} catch (_: NumberFormatException) {
}
}
return sum
}
@Test
fun `test imperative approach`() {
imperativeSum(input).run {
println("Sum $this")
assertEquals(1299, this)
}
}
fun isValidNumber(s: String) = try {
s.toInt()
true
} catch (_: NumberFormatException) {
false
}
fun declarativeSum(list: List<String>) = list
.filter(::isValidNumber)
.map(String::toInt)
.sum()
@Test
fun `test declarative approach`() {
assertEquals(1299, declarativeSum(input).apply {
println("Sum $this")
})
}
}
Implémenter la fonction sumInRange, qui additionne les valeurs dans
une List<String> dans un intervalle donné. La signature est :
fun sumInRange(input: List<String>, range: IntRange): Int
@Test
fun `Exercise 1_1`() {
assertEquals(4, sumInRange(
listOf("1", "10", "a", "7", "ad2", "3"),
1..5
).apply { println("sumInRange 1..5: $this") }
)
}
Essayez-le et vérifiez votre réponse avec la solution.
package functional
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.lang.System.*
import java.lang.Thread.sleep
import kotlin.math.sign
import kotlin.test.Test
import kotlin.test.assertEquals
class BasicsHOFTests {
val ONE_SECOND = 1000L
@Test
fun `high order function`() {
//capture de la sortie standard
val standardOut: PrintStream? = out
val outputStreamCaptor = ByteArrayOutputStream()
setOut(PrintStream(outputStreamCaptor))
3.times { println("Hello") }
assertEquals(
buildString {
repeat(3) { append("Hello\n") }
deleteAt(length - 1)
}, outputStreamCaptor
.toString()
.trim()
)
//libération de la sortie standard
setOut(standardOut)
}
fun Int.times1(fn: () -> Unit) {
for (i in 1..this) {
fn()
}
}
fun Int.times2(fn: () -> Unit) {
for (i in 1..this) fn()
}
fun Int.times3(fn: () -> Unit) =
(1..this).forEach { fn() }
fun Int.times4(fn: () -> Unit) =
repeat((1..this).count()) { fn() }
fun Int.times(fn: () -> Unit) =
(1..this).forEach { _ -> fn() }
}
Implémenter chrono, qui accepte une fonction de type () →
Unit en entrée et renvoie le temps passé à l’exécuter. La signature est :
fun chrono(fn : () -> Unité) : Long
@Test
fun `Exercise 1_2`() {
val waitOneSec = { sleep(ONE_SECOND) }
chrono(waitOneSec).apply {
println("chrono: $this")
assertEquals(1, sign)
}
}
Essayez-le et vérifiez votre réponse avec la solution.
package functional
import kotlin.test.Test
import kotlin.test.assertEquals
fun double(x: Int): Int = 2 * x
fun square(x: Int): Int = x * x
fun squareAndDouble1(x: Int) = double(square(x))
infix fun <A, B, C> ((A) -> B).compose(g: (B) -> C)
: (A) -> C = { a -> g(this(a)) }
class CompositionTests {
@Test
fun composition_impure() {
assertEquals(200, double(square(10)))
assertEquals(200, squareAndDouble1(10))
}
@Test
fun composition_pure() {
val squareAndDouble = ::square compose ::double
assertEquals(200, squareAndDouble(10))
}
}
package functional
import java.io.ByteArrayOutputStream
import java.io.PrintStream
import java.lang.System.out
import java.lang.System.setOut
import kotlin.test.Test
import kotlin.test.assertEquals
var count = 0
//impure car une variable global subit un effet de bord
fun impure(value: Int): Int {
count++
return value + count
}
//impure car utilisation de la sortie standard qui fait muter le system
fun addOneAndLog(x: Int): Int {
val result = x + 1
println("New Value is $result")
return result
}
//pure
fun addOne(x: Int) = (x + 1).run {
Pair(this, "New Value is $this")
}
class PureTests {
@Test
fun `impure fonction`() {
assertEquals(3, impure(2))
val standardOut = out
val outputStreamCaptor = ByteArrayOutputStream()
setOut(PrintStream(outputStreamCaptor))
addOneAndLog(3)
assertEquals(
"New Value is 4",
outputStreamCaptor
.toString()
.trim()
)
setOut(standardOut)
}
@Test
fun `pure fonction`() {
addOne(3).run {
assertEquals(4, first)
assertEquals("New Value is 4", second)
}
}
}
package functional
import org.junit.jupiter.api.assertThrows
import kotlin.Result.Companion.failure
import kotlin.Result.Companion.success
import kotlin.test.Test
import kotlin.test.assertEquals
//NumberFormatException est un effet de bord qui rend la fonction impure
fun strToInt(str: String) = str.toInt()
//pure
fun strToIntOrNull(str: String) = try {
str.toInt()
} catch (nfe: NumberFormatException) {
null
}
//pure avec gestion de l'exception plus élégante
fun strToIntResult(str: String): Result<Int> =
try {
success(str.toInt())
} catch (nfe: NumberFormatException) {
failure(nfe)
}
class ExceptionHandlingTests {
@Test
fun impure() {
assertThrows<NumberFormatException> { strToInt("foo") }
assertEquals(1, strToInt("1"))
}
@Test
fun pure() {
assertEquals(null, strToIntOrNull("foo"))
assertEquals(1, strToIntOrNull("1"))
}
@Test
fun `pure avec result`() {
assertEquals(1, strToIntResult("1").getOrNull())
assertEquals(
"For input string: \"foo\"",
strToIntResult("foo")
.exceptionOrNull()
?.message
)
}
}
Alors que la programmation orientée objet signifie programmer avec des objets, la programmation fonctionnelle signifie programmer avec des fonctions. Vous décomposez un problème en plusieurs sous-problèmes, que vous modélisez avec les fonctions.
Les fonctions d’ordre supérieur acceptent d’autres fonctions en entrée ou renvoient d’autres fonctionnent comme des valeurs de retour. La théorie des catégories est la théorie de la composition, et vous l’utilisez pour comprendre comment composer vos fonctions dans un programme de travail. La valeur de sortie d’une fonction pure ne dépend que de ses paramètres d’entrée, et elle n’a pas d’effets secondaires.
Un effet secondaire est quelque chose qu’une fonction fait au monde extérieur. Ce peut être un journal dans la sortie standard ou la modification de la valeur d’une variable globale. La programmation fonctionnelle fonctionne pour les fonctions pures, mais elle fournit également les des outils pour transformer des fonctions impures en fonctions pures. Vous pouvez rendre une fonction impure pure en déplaçant les effets pour les rendre partie de la valeur de retour.
La programmation fonctionnelle est une question de composition. La gestion des erreurs est un cas typique d’effets secondaires, et Kotlin vous donne les outils pour les gérer de manière fonctionnelle.
La théorie mathématique des catégories:
Pouvez-vous écrire un exemple de fonction mappant des valeurs distinctes
dont le domaine à des valeurs non distinctes dans la plage, comme f(b) et f(c) dans la figure ci-dessous ?
Essayez-le, puis vérifiez le projet de défi pour une solution à voir comment tu as fait.
Vous trouverez des conseils et une explication en suivant le lien vers la
solution.
Can you write the inverse function of twice ?
What are the domain and range for the inverse function?
Check out the challenge project and Appendix B for the solution.
fun chrono(fn : () -> Unité) : Long
@Test
fun `Exercise 1_2`() {
val waitOneSec = { sleep(ONE_SECOND) }
chrono(waitOneSec).apply {
println("chrono: $this")
assertEquals(1, sign)
}
}
Essayez-le et vérifiez votre réponse avec la solution.