My Chocolate Cake recipe

Here is a chocolate cake recipe.
Please note that this is not only a recipe, but it is code too…
You can compile that in Kotlin successfully and you can imagine a cookbook site using this kind of implementation.

fun main(args: Array<String>) {
val chocolateCake = `recipe consisting of` { -
(7 `ounces of` "black chocolate") +
(4 `ounces of` "flour") +
(3 `ounces of` "sugar") +
(1 ["baking powder"]) +
(1 ["vanilla sugar powder bag"]) +
(2 `glasses of` "oil") +
(1 `ounce of` "white cheese") +
(3 ["eggs"]) +
(1 `tea spoon of` "orange zest") +
(1 `ounce of` "butter")}
.`then apply` { "black chocolate" `melted in` "a pan containing water" }
.`then make sure that all the` {listOf(
"flour", "sugar", "black chocolate", "baking powder", "vanilla sugar powder"
)`is disposed in the` "bowl"}
.`mix everything`
.`scramble the`("eggs")
.`then pour all the` {listOf("oil", "white cheese", "eggs", "orange zest"
) `in the` "bowl"}
.`mix everything`
.`beat with a whip`
.`bake for`(45.minutes)
print(chocolateCake)
}

Here is the necessary support code to make that compile :

data class Operation(val verb: String,
val ingredient: Ingredient ? = null,
val where: String ? = null,
val duration: String = "immediate")

data class Ingredient(val name: String,
val quantity: Int,
val unit: ConversionUnit)

data class Recipe(val ingredients: List < Ingredient > = emptyList(),
val operations: List < Operation > = emptyList()) {

fun operationsBuilder(ingredientNames: List < String >, verb: String, place: String ? = null, duration: String ? = null) =
ingredientNames.map {
ingredientName ->
Operation(verb,
ingredient = this@Recipe.ingredients.find { it.name == resolveRealNameFrom(ingredientName) }
?: throw IllegalArgumentException("ingredient not found"),
where = place,
duration = duration ?: "immediate")
}

fun`then apply`(operationsToDo: Recipe.() -> List<Operation>) = Recipe(ingredients, operations = operations + operationsToDo.invoke(this))

fun`then make sure that all the`(operationsToDo: Recipe.() -> List<Operation>) = Recipe(ingredients, operations = operations + operationsToDo.invoke(this))

fun`then pour all the`(operationsToDo: Recipe.() -> List<Operation>) = `then make sure that all the`(operationsToDo)

infix fun List<String>.`is disposed in the`(place: String) = operationsBuilder(this, verb = "dispose", place = place)
infix fun List<String>.`in the`(place: String) = this `is disposed in the` place

infix fun String.`melted in`(place: String) = operationsBuilder(listOf(this), verb = "melt", place = place,
duration = 10.minutes)

val`mix everything` get() = Recipe(ingredients, operations = operations + Operation("mix"))

val`beat with a whip` get() = Recipe(ingredients, operations = operations + Operation("beat with a whip"))

fun`scramble the`(ingredientName: String) =
Recipe(ingredients, operations = operations + operationsBuilder(listOf(ingredientName), "scramble"))

fun`bake for`(duration: String) =
Recipe(ingredients, operations + Operation(verb = "bake", duration = duration))

operator fun plus(newIngredient: Ingredient) = Recipe(ingredients + newIngredient)
}

enum class ConversionUnit { OUNCES, QUANTITY, GLASSES, TEA_SPOON }

infix fun Int.`ounces of`(name: String) = Ingredient(name, this, ConversionUnit.OUNCES)
infix fun Int.`ounce of`(name: String) = this `ounces of` name
infix fun Int.`glasses of`(name: String) = Ingredient(name, this, ConversionUnit.GLASSES)
infix fun Int.`tea spoons of`(name: String) = Ingredient(name, this, ConversionUnit.TEA_SPOON)
infix fun Int.`tea spoon of`(name: String) = this `tea spoons of` name
fun`recipe consisting of`(theIngredientsImplementation: () -> Recipe) =
theIngredientsImplementation.invoke()

operator fun Int.get(name: String) = Ingredient(resolveRealNameFrom(name),
quantity = this, unit = ConversionUnit.QUANTITY)

val Int.minutes get() = "$this minutes"

fun resolveRealNameFrom(name: String) =
name.replace(Regex("s$"), "")
.replace(Regex(" bag$"), "")

operator fun Ingredient.unaryMinus() = Recipe(listOf(this))

Enjoy the cake.

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.