From 02844d87bf50b8c0670252d02efb5735e8dca522 Mon Sep 17 00:00:00 2001 From: Hauke Sommerfeld Date: Tue, 17 Sep 2024 17:30:53 +0200 Subject: [PATCH] Corrects minor mistakes --- www/src/pages/docs/50_StoreMapping.md | 101 +++++++++++++------------- 1 file changed, 51 insertions(+), 50 deletions(-) diff --git a/www/src/pages/docs/50_StoreMapping.md b/www/src/pages/docs/50_StoreMapping.md index 80c1a857b..490217fd9 100644 --- a/www/src/pages/docs/50_StoreMapping.md +++ b/www/src/pages/docs/50_StoreMapping.md @@ -150,7 +150,7 @@ This can be used to identify your elements semantically (for validation or autom If you have deep nested structures or a lot of them, you may want to automate this behavior. -fritz2 offers an annotation `@Lenses` you can add to your data-classes, sealed-classes and sealed-interfaces in the +fritz2 offers an `@Lenses` annotation you can add to your data-classes, sealed-classes and sealed-interfaces in the `commonMain` source-set of your multiplatform project: ```kotlin @Lenses @@ -178,10 +178,11 @@ the browser and backend. ::: info There are other lenses generated as well. Especially when dealing with `sealed`-hierarchies, fritz2 offers more -helpful variants that commonly needed to apply different idioms and patterns like *validation* or holding the base +helpful variants that are commonly needed to apply different idioms and patterns like *validation* or holding the base type in some `Store` for type based UI design. -You can find out in the two applications in the sections [Dealing with Sealed Type Hierarchies](#dealing-with-sealed-type-hierarchies) +You can find out more in the two applications described in the sections +[Dealing with Sealed Type Hierarchies](#dealing-with-sealed-type-hierarchies) and [Delegating Validation in Sealed Hierarchies](/docs/validation/#delegating-validation-in-sealed-hierarchies). ::: @@ -490,7 +491,7 @@ to encapsulate the store mapping directly into the former presented convenience ### Dealing with sealed Type Hierarchies There are special needs when you integrate `sealed` types into your model. It does not matter whether this is at the -root or nested inside your model-tree. There are always similar idioms that needs to be applied. Those idioms need +root or nested inside your model-tree. There are always similar idioms that need to be applied. Those idioms need special lenses, that will be generated by our `lenses-annotation-processor` for you. Imagine the following example model: @@ -513,19 +514,19 @@ data class Wishlist( } ``` -As you can see, we want to model some wishlist, which holds simply a list of wishes. +As you can see, we want to model a wishlist, which simply holds a list of wishes. A `Wish` is a `sealed interface` that defines a `label` for all common children. It is also annotated by the known `@Lenses`-annotation in order to tell the annotation processor to create lenses for this base type too. -The `WhishList` simply serves as some root node of the model to integrate the sealed hierarchy into another type. +The `WhishList` simply serves as a root node of the model to integrate the sealed hierarchy into another type. :::info -There is one restriction to our support: -All children of an annotated sealed base type need to be `data class`es that are themselves annotated by the `@Lenses` -annotation. The annotation processor will fail with an error, if this constraint is violated. +Our support for sealed type hierarchies comes with an exception: +All children of an annotated sealed base type need to be `data class`es that are themselves annotated with the `@Lenses` +annotation. The annotation processor will fail with an error if this constraint is violated. ::: -There might be different kind of wishes though, that all differ in their special properties. +There might be different kinds of wishes, though, which all differ in their special properties. Take those for example: ```kotlin @Lenses @@ -549,10 +550,10 @@ data class LightSaber( } ``` -Our `WhishList.wishes` list can take arbitrary objects of `Computer` and `LightSaber`, while both types needs different -aspects to be well defined: -- a computer needs RAM - as we refer to good old homecomputer's era, we store those values in kilobytes. -- a light-saber needs a color; as fritz2's logo is rather *petrol*, we make this possible for our merch. +Our `WhishList.wishes` list can hold arbitrary objects of `Computer` and `LightSaber`, while both types need different +aspects to be well-defined: +- a computer needs RAM - as we refer to the good old homecomputer's era, we store those values in kilobytes. +- a lightsaber needs a color - as fritz2's logo is somewhat *petrol*, we make this possible for our merch. Look at an example with a `Store` of type `WishList`: ```kotlin @@ -571,22 +572,23 @@ We would like to implement a small UI to manage those wishes. It might look simi ![Wishlist App](/img/sealed-hierarchy-example-app.png) The overall example is rather good [domain modelling](https://arrow-kt.io/learn/design/domain-modeling/), -but it imposes some problems, when it comes to store-mapping: -1. to map the main model down to the nodes of the tree (`Whislist -> wishes -> some single wish -> Computer -> ramInKb`), -there is the need to map a store of the base type to some specific type. As mapping involves `Lens`es, we need for -example some `Lens`. How could this be done? -2. another difficulty arises by the fact, that we also want to modify the *common* properties of the base type. But -there is no implementation though, so how should we implement the `setter` of a `Lens` for the +but it imposes some problems when it comes to store-mapping: + +1. To map the main model down to the nodes of the tree (`Whislist -> wishes -> some single wish -> Computer -> ramInKb`), +there is the need to map a store of the base type to a specific type. As mapping involves `Lens`es, we need +a `Lens`, for example. How could this be done? +2. Another difficulty arises by the fact that we also want to modify the *common* properties of the base type. +There is no implementation, though, so how should we implement the `setter` of a `Lens` for the `Whish.label`-property? -There are solutions with simple idioms for those two problems, that you can craft by hand. But fritz2's lenses -processor create the needed `Lens`es automatically for you. +There are solutions with simple idioms for those two problems that you can craft by hand. fritz2's lenses +processor creates the needed `Lens`es automatically for you, however. -The following two sections show and explain those solutions. +The following two sections demonstrate the aforementioned solutions. #### Up-Casting and Down-Casting Lenses -Let us recap tht pathes we need to take for a store-mapping from top to bottom elements: +Let us recap the paths we need to take for a store-mapping from top to bottom elements: - `Whislist -> wishes -> some single wish -> Computer -> ramInKb` - `Whislist -> wishes -> some single wish -> LightSaber -> color` @@ -611,13 +613,13 @@ val ramInKb: Lens = TODO() val color: Lens = TODO() ``` -The two problematic lenses are the ones, that need to implement the up-casting from the base type to the specific type: +The two problematic lenses are the ones that need to implement the up-casting from the base type to the specific type: - `Lens` - `Lens` -We definitely need to know the specific type, which we can typically solve by an appropriate `when`-expression. +We need to know the specific type which we can typically solve with an appropriate `when`-expression. Remember the type notation of the `getter`-property of a `Lens` is `(P) -> T` which would translate to -`(Wish) -> Computer` in our example case. To gain the correct return type, we can simply cast inside the +`(Wish) -> Computer` in our example case. To obtain the correct return type, we can simply cast inside the `getter`-expression of our `Lens` to the specific type: ```kotlin val computerLens: Lens = lensOf( @@ -633,11 +635,11 @@ As the `setter` has the type notion `(Wish, Computer) -> Wish` we can simply ret the correct specific type. :::info -As we cast from the base type to a more specific type, this is called up-casting. -And since we apply this to a lens, we call this kind of lens *up-casting* lens. +Casting from the base type to a more specific type is called up-casting. +Since we apply this to a lens, we call this kind of lens *up-casting* lens. ::: -Armed with such up-casting lenses, we can easily access or change values of our example `WishList`-object: +Armed with such an up-casting lenses, we can easily access or change values of our example `WishList`-object: ```kotlin val wishlist = Wishlist( "Christmas wishes", @@ -674,11 +676,11 @@ println(ramInKbLens.get(wishlist)) // prints: 64 println(ramInKbLens.get(upgradedList)) // prints: 512 ``` -As we already have mentioned, fritz2's annotation processor will generate those up-casting lenses for you. -They are named like the classname with starting lowercase letter, thus `Computer` will become `computer()` as factory -name. +As mentioned before, fritz2's annotation processor will generate those up-casting lenses for you. +They are named after the classname starting with a lowercase letter. Thus, `Computer` will have `computer()` as it's +factory name. -We could now apply this to map the `Store` and provide some form elements to change the state: +We can now use this to map the `Store` and provide some form elements to change the state: ```kotlin val storedWishList: Store = storeOf(wishlist) val storedWishes = storedWishList.map(Wishlist.wishes()) @@ -728,8 +730,8 @@ storedWishList.data.map { it.wishes.withIndex().toList() }.renderEach { (index, As there may be use-cases where we need to access the other way round, fritz2 also creates so called *down-casting* lenses for each child of a sealed base type. -Those are lenses, that have a specific type as parent and the base type as lens outcome. -Translated to the example there is a `Lens`-factory created on the type `LightSaber` called `wish()`, that looks like +Those are lenses, that have a specific type as their parent and the base type as the lens's outcome. +Translated to the example there is a `Lens`-factory created on the type `LightSaber` called `wish()` that looks like this: ```kotlin @@ -746,8 +748,8 @@ public fun LightSaber.Companion.wish(): Lens = lensOf( The second problem of sealed type hierarchies is the access of the *common* properties. -As a sealed base-type is an `interface` or an `abstract class` and *no* `data class`, there is no `copy()`-function -we could apply in order to provide a useful `setter` implementation! +As a sealed base-type is an `interface` or an `abstract class` and *not a* `data class`, there is no `copy()`-function +we could use in order to provide a useful `setter` implementation! Let us illustrate the problem for the `Wish.label`-property: ```kotlin @@ -789,15 +791,15 @@ val labelLens: Lens = lensOf( ) ``` -Because the work is *delegated* to the different children types, we call this kind of lens *delegating lens*. +As the work is *delegated* to the different child types, we call this kind of lens *delegating lens*. As this is tedious to write, the fritz2's annotation processor will create those lenses for you. Because it needs to -be sure, it can use the `copy()`-functions, there is the restriction, that every child of a sealed base type needs +be sure it can use the `copy()`-functions, there is the restriction that every child of a sealed base type needs to be a `data class`! You have already heard about this at the start of top level section, but now you understand the reason for that. -Now we can provide some input-field for the common `label`-property, that gets mapped to the appropriate +Now we can provide an input-field for the common `label`-property that gets mapped to the appropriate child object's field: ```kotlin @@ -823,9 +825,9 @@ storedWishList.data.map { it.wishes.withIndex().toList() }.renderEach { (index, #### Exclude Fields from Lenses Generation -Sometimes you may not want to create a `Lens` for all public fields of a sealed base type. -Typical use cases may be properties, that should be static for all instances, like some kind of screen -name of the type or alike. +Sometimes you may not want to create a `Lens` for each public field of a sealed base type. +Typical use cases may be properties that should be static for all instances, like some kind of display +name of the type. For those cases fritz2 offers the `@NoLens`-annotation. @@ -859,9 +861,9 @@ data class Computer( #### Complete Wishlist Example -As this example is rather complex, we want you to show the full working implementation of the preceding wishlist -code snippets. The styling is done with [tailwindcss](https://tailwindcss.com/), so you can copy and paste it in -our well known [fritz2-tailwind-template](https://github.com/jwstegemann/fritz2-tailwind-template) project: +As this example is rather complex, the full working implementation of the preceding wishlist +code snippets can be found below. The styling is done with [TailwindCSS](https://tailwindcss.com/), so you can copy and +paste it in our well known [fritz2-tailwind-template](https://github.com/jwstegemann/fritz2-tailwind-template) project: ```kotlin // somewhere in your `commonMain`-source-set: @@ -1004,9 +1006,8 @@ render { ``` :::warning -The above example is intentionally kept very simply structured to reduce complexity and show the code mostly -straight forwarded. +The above example is intentionally kept very simple in to reduce complexity and show the code in a straight-forward way. For real world application we encourage you to [structure](/docs/render/#structure-ui) it better and divide the UI -into smaller and reusable portions of course. +into smaller and reusable parts, of course. ::: \ No newline at end of file