Friday, October 28, 2022

Kotlin: Functional Domain Modeling #6

  Kotlin: Functional Domain Modeling #6


In this blog we introduce the repository: a storage place for the principal aggregates of an application. The repository provides a persistent storage for the aggregates and support for retrieval from the store. Usually, a repository has an implementation based on a relational database management system. In this blog we keep the repository as a simple in-memory storage so that in the next blog we can explore injecting it into the services of an application.



Repositories

Repositories are where the larger aggregates of the application live. The nature of the repository structures do not need to fully match that of the aggregate but you can always construct an aggregate from the repository. The repository also supports querying it for a particular aggregate. Here, the aggregate in our library application is a cataloged book.

Here is a simple API for the cataloged book repository:

typealias Error = String

interface LibraryRepositoryIF {

    fun store(book: CatalogBook): Either<Error, CatalogBook>
    fun remove(id: String): Either<Error, CatalogBook>
    fun contains(predicate: (String) -> Boolean): Boolean
    fun contains(id: String): Boolean =
        contains{iden: String -> (iden == id)}
    fun lookUp(id: String): Option<CatalogBook>
    fun adjust(id: String, f: (CatalogBook) -> CatalogBook): Either<Error, CatalogBook>

}   // LibraryRepositoryIF

Note that we have kept the return types of the functions as Either to account for the possible failures that might occur when interacting with the repository.

From here we can have specific implementations to match the chosen storage mechanism. The following is an simple implementation based on the Dogs Map type. The Map class is implemented as a simple balanced binary tree. The library also includes a Map class under the package hamt. The Hash Array Mapped Trie is a structure for organizing data in a broadly-branching tree. The high-branching factor results in the data being stored in a a very shallow tree structure resulting in improved performance over the balanced binary tree representation.

This implementation for the LibraryRepository might, in turn, be used as a mocking repository during testing. Here is the in-memory variant:

object LibraryRepository : LibraryRepositoryIF {

    override fun store(book: CatalogBook): Either<Error, CatalogBook> {
        val catalogBook: Option<CatalogBook> = library.lookUpKey(book.catalogNumber)
        return catalogBook.fold(
            {
                library = library.insert(book.catalogNumber, book)
                right(book)
            },
            { left("LibraryRepository.store: existing book with catalog number: ${book.catalogNumber}") }
        )
    }   // store

    // ...

// ---------- properties ----------------------------------

        // key is catalog number
    var library: Map<String, CatalogBook> = MapF.empty()

}   // LibraryRepository

We show the implementation for function store. First, it looks up the book's catalog number as the key into the Map, returning an Option<CatalogBook>. The generic Option<A> class includes the fold member function with the signature:

fun <B> fold(none: () -> B, some: (A) -> B): B

The first parameter function none is called if the receiving Option is a None. The second parameter function some is called if the receiving Option is a Some with its wrapped value as parameter. In function store we call fold on the catalogBook object. If its a None then we know no such book already exists, and we insert the book into the map and return as a success operation. If it is a Some then we return as a failure.

The following test results in a successful storing of a cataloged book:

val catalogBook: CatalogBook =
    CatalogBook(
        "book0001",
        Book(
            "Kotlin in Action",
            "Manning",
            LocalDate.of(2017, 1, 1),
            ISBN("9781617293290"),
            NonEmptyListF.of(
                Author(Name(LastName("JEMEROV"), FirstName("Dmitry"), none()), 1992),
                Author(Name(LastName("ISAKOVA"), FirstName("Svetlana"), none()), 1992)
            )
        ),
        DeweyClassification("005"),
        LocalDate.now()
    )

assertEquals(
    right(catalogBook),
    repo.store(catalogBook)
)

The next test fails because we attempt to store a book with the same catalog number twice.

val catalogBook: CatalogBook =
    CatalogBook(
        "book0001",
        Book(
            "Jetpack Compose",
            "RayWenderlich Tutorial Team",
            LocalDate.of(2017, 1, 1),
            ISBN("9781950325122"),
            NonEmptyListF.of(
                Author(Name(LastName("BALINT"), FirstName("Tine"), none()), 1992),
                Author(Name(LastName("BUKETA"), FirstName("Denis"), none()), 1992)
            )
        ),
        DeweyClassification("005"),
        LocalDate.now()
    )

assertEquals(
    left("LibraryRepository.store: existing book with catalog number: book0001"),
    repo.store(catalogBook)
)



Populating the repository

In this latest version, the LibraryRepository is pre-populated with four books so we can perform our tests without having to repeatedly create and populate the repository. For that we use the Kotlin init block. During the initialization of an instance, Kotlin executes the initializer block(s) and property initializer(s) in the same order as they appear in the class/object body. Thus we have:

object LibraryRepository : LibraryRepositoryIF {

    // ...

// ---------- properties ----------------------------------

    // key is catalog number
    var library: Map<String, CatalogBook> = MapF.empty()

    init {
        val catalogBooks: List<CatalogBook> = ListF.of(
            CatalogBook(
                "book0001",
                Book(
                    "Kotlin in Action",     // two copies
                    "Manning",
                    LocalDate.of(2017, 1, 1),
                    ISBN("9781617293290"),
                    NonEmptyListF.of(
                        Author(Name(LastName("JEMEROV"), FirstName("Dmitry"), OptionF.none()), 1992),
                        Author(Name(LastName("ISAKOVA"), FirstName("Svetlana"), OptionF.none()), 1992)
                    )
                ),
                DeweyClassification("005"),
                LocalDate.of(2020, 2, 2)
            ),
            // ...
        )

        library = catalogBooks.foldLeft(MapF.empty()){lib, cBook -> lib.insert(cBook.catalogNumber, cBook)}
        
    }

}   // LibraryRepository



Invariants and laws

A functional domain model is characterized as a series of functions that operate on a set of types and honor a number of invariants. The sets are the data types that form the model. The functions that operate on the data types are published as the API to the user. When we define the operations in an API, the invariants define the relationships between these operations.

We need to capture some of the invariants that our APIs have to honor. They can be generic constraints or they can be derived from the domain. One of the basic laws that we should enforce for our repository is: for all cataloged books if we store one in the repository then immediately remove it then it will no longer be present in the repository.

The KwikCheck test framework for Kotlin (as described here) is modeled after the QuickCheck framework. Property-based testing is generative testing. You do not supply specific example inputs with expected outputs as with unit tests. Instead, you define properties about the code and use the generative-testing engine to create randomized inputs to ensure the defined properties are correct. KwikCheck allows us to express these properties and generate the randomized inputs.

Property-based testing is a technique where your tests describe the properties and behavior you expect your code to have, and the testing framework tries to find inputs that violate those expectations. Rather than testing a function against specific inputs we try to reason about its behavior over a range of inputs.

Test properties are presented as logical propositions. For example, for all library repositories repo and all cataloged books bk the following proposition is a statement that affirms or denies the predicate:

repo.store(bk).remove(bk.catalogNumber).contains(bk.catalogNumber) ==> false

We test this property embedded in a Unit test framework. The forAll function accepts a generator that produces a randomized CatalogBook containing property values. The generated CatalogBook is captured in the lambda parameter as catalogBook. Function prop expects a predicate for our logical proposition and wraps it into a Property instance. Function check then runs the test and delivers a CheckResult value which, since we are using a Unit test harness, we can complete with an assert.

val property = forAll(genCatalogBook){catalogBook ->
    repo.store(catalogBook)
    repo.remove(catalogBook.catalogNumber)
    val found: Boolean = repo.contains(catalogBook.catalogNumber)
    prop(!found)
}
val checkResult = property.check()
assertTrue(checkResult.isPassed())

The property we are checking require that all store/remove operations pair on the repository with any arbitrary CatalogBook means the book is not in the repository. We establish the genCatalogBook binding for a Gen<CatalogBook> that delivers a random but valid CatalogBook.

The core of the KwikCheck library comprises the classes Gen<A> and Property. The class Gen<A> is the generator class for values of type A. The class Property represents an algebraic property that may be checked for its truth. Its most important member function is check which delivers a result.

Here is the binding for genCatalogBook:

val genCatalogBook: Gen<CatalogBook> = genCatalogNumber.bind{ nbr ->
    genBook.bind{bk ->
        GenF.value(CatalogBook(nbr, bk, DeweyClassification("005"), LocalDate.now()))
    }
}

The Gen class is monadic and supports the bind operation. As shown, we generate a valid catalog number with genCatalogNumber, bind it to the lambda parameter nbr, we then generate a valid Book instance with genBook and bind it to parameter bk, finally creating a valid CatalogBook.

Continuing in a similar manner we produce genCatalogNumber:

val genCatalogNumber: Gen<String> = GenF.genPosInt(1, 9999).bind{ nbr ->
    val suffix: String = "0000$nbr".takeLast(4)
    GenF.value("book$suffix")
}

and so on.

A typical instance produced by genCatalogNumber is:

CatalogBook(
    catalogNumber=book2256, 
    book=Book(
        title=iwsdxtptcighjjnUlxCdmfskpiFdtaaszwgvklHfv,
        publisher=ajfxOrgzpcgnrotxutsgjmyciptequiqdnUvpwksxIwrfkqsvppafhUrySwuljuoxzgodioupnrrjgezqveEoss,
        publicationDate=2003-09-21,
        isbn=ISBN(isbn=1677465921),
        authors=[
            Author(
                name=Name(
                    lastName=LastName(lastName=EBCHRBHWVR),
                    firstName=FirstName(firstName=Edjpftzfefm),
                    middleName=None
                ),
                yearOfBirth=1996
            ),
            Author(
                name=Name(
                    lastName=LastName(lastName=ZAWNMYDAQS),
                    firstName=FirstName(firstName=Uriygbbzcfl),
                    middleName=None
                ),
                yearOfBirth=2003
            )
        ]
    ), 
    dewey=DeweyClassification(dewey=005), 
    openDate=2022-10-28, 
    withdrawnDate=None
)

The title and publisher are randomly generated strings, the publication date is randomly generated from 2000 to 2020, the ISBN is generated from the 10-digit ISBN. Note how the last names are randomly generated fully capitalized alphabetic strings, while the first names are randomly generated alphabetic strings with a leading capital. All these domain generators are derived from the many base generators provided by KwikCheck.



The code for the Dogs library can be found at:

https://github.com/KenBarclay/TBA
https://github.com/KenBarclay/TBA


Thursday, October 27, 2022

Kotlin: Functional Domain Modeling #5

 Kotlin: Functional Domain Modeling #5


In this blog we introduce the traversable pattern. Traversable structures are collections of elements that can be operated upon with an effectful visitor operation. The visitor function performs a side-effect on each element and composes those side effects whilst retaining the original structure.



Traversables

This is how our Book class appears:

data class Book(
    val title: String,
    val publisher: String,
    val isbn: ISBN,
    val authors: NonEmptyList<Author>
)

Using the smart constructor idiom we have (for the Book class):

fun create(
    title: String,
    publisher: String,
    isbn: String,
    vararg authors: ValidationNel<String, Author>
): ValidationNel<String, Book>

Note the authors parameter to function create. It is marked as a vararg so we can pass a variable number of actual parameters at the call site, including none. The type is ValidationNel<String, Author> and represents the effectful validation returned from calls to the smart constructor Author.create.

Here is the complete class declaration:

data class Book(
    val title: String,
    val publisher: String,
    val isbn: ISBN,
    val authors: NonEmptyList<Author>
) {

    companion object {

        fun create(title: String, publisher: String, isbn: String, vararg authors: ValidationNel<String, Author>): ValidationNel<String, Book> {
            val vISBN: ValidationNel<String, ISBN> = ISBN.create(isbn)
            val vAuthors: ValidationNel<String, Array<Author>> =
                if (authors.isEmpty())
                    failureNel("Book.create: must have one or more authors")
                else
                    authors.sequenceValidationNel()

            return fmap2(vISBN, vAuthors){isn: ISBN, auths: Array<Author> ->
                Book(title, publisher, isn, auths.toNonEmptyList())
            }
        }   // create

    }

}   // Book

Within the body of the create function of class Book the vararg parameter authors is considered an Array<ValidationNel<String, Author>>. The binding for vAuthors is a failure if this array is empty. If authors is non-empty then the binding for vAuthors is to:

authors.sequenceValidationNel()

We shall return to this shortly.



The iterator pattern

Perhaps the most familiar of the object oriented design patterns is the iterator pattern, which provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation. Traditionally, this is achieved by identifying an iterator interface that presents operations to initialize an iteration, to access the current element, to advance to the next element, and to test for completion; collection objects are expected to implement this interface.

This traditional version of the pattern is sometimes called an external iterator. An alternative internal iterator approach assigns responsibility for managing the traversal to the collection instead of the client: the client needs only to provide an operation, which the collection applies to each of its elements.

Now recall the idea behind the functor function fmap. When we use fmap, we can take any functor structure (such as List or Option from the Dogs library) and transform all the underlying elements of that functor, returning a new object with the exact same structure, but different elements. The new elements might be of the same type or they can be entirely different.

Traversable structures are containers of elements that can be operated upon with an effectful visitor operation. The visitor function performs a side-effect on each element of the structure and composes these side effects with an applicative. The traversable abstracts this capability with the traverse function:

fun <A, B> F<A>.traverse(f: (A) -> G<B>): G<F<B>>

Once again F is the place-marker for the actual type for which it applies. The types Option, Either, Validation, ArrayList and Map from the Dogs library all support the traversable. The function parameter transforms an element from the context using an applicative. Thus G is expected to be some applicative type such as Option, Validation or List.

We have already been performing traversals with the functor function fmap and the foldable function foldMap. Function fmap walks across the collection, applies a transformer operation to each element and collects the results by rebuilding the collection. Similarly, function foldMap walks across the collection applies the transforming function and collects the results by combining them with the given monoid. Function traverse provides a further useful way for traversing a collection.

As a comparison consider iterating across a list using fmap and the Option-encoded test for negative Ints:

fun deleteIfNegative(n: Int): Option<Int> =
    if (n < 0) none() else some(n)

assertEquals(
    ListF.of(none(), some(3), some(2), none(), some(0)),
    ListF.of(-5, 3, 2, -1, 0).fmap(::deleteIfNegative)
)

By contrast, function traverse creates an applicative summary of the contexts within a structure, and then rebuilds the structure in the new context.

assertEquals(
    none(),
    ListF.of(-5, 3, 2, -1, 0).traverseOption(::deleteIfNegative)
)

assertEquals(
    some(ListF.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)),
    ListF.closedRange(1, 10).traverseOption(::deleteIfNegative)
)

Given deleteIfNegative, a function that returns an Option effect, function traverseOption threads this effect through the running of this function on all the values in the List, returning a List<Int> in an Option context.

Alongside function traverse, the traversable also includes the function sequence:

fun <A> F<G<A>>.sequence(): G<F<A>>

This function threads all the G effects through the F structure and inverts the structure from F<G<A>> to G<F<A>>. Two examples are:

assertEquals(some(ListF.of(1, 2, 3)), ListF.of(some(1), some(2), some(3)).sequenceOption())
assertEquals(none(), ListF.of(some(1), some(2), none()).sequenceOption())

Function sequence turns the traversable inside out.



The travesrsable Array

The Array extension function sequenceValidationNel has the signature:

fun <E, B> Array<ValidationNel<E, B>>.sequenceValidationNel(): ValidationNel<E, Array<B>>

The function threads all the ValidationNel effects through the Array structure to invert the structure into an ValidationNel<E, Array<B>>. So we turn an Array<ValidationNel<E, B>> into a ValidationNel<E, Array<B>>. The functional community consider sequence as a member of the traversable pattern. Function sequence is somewhat analogous to performing a fold operation - it creates an applicative summary of the contexts (the ValidationNel) within a structure (the Array), and then rebuilds the structure (the Array)  in the new context (the ValidationNel).

In function create of class Book we use function sequenceValidationNel to traverse the array of ValidationNel<String, Author> produced by the function Author.create. A simple test is:

assertEquals(
    successNel(
        Book(
            "Kotlin in Action", "Manning", ISBN("9781617293290"),
            NonEmptyListF.of(
                Author(Name(LastName("JEMEROV"), FirstName("Dmitry"), none()), 1992),
                Author(Name(LastName("ISAKOVA"), FirstName("Svetlana"), none()), 1995)
            )
        )
    ),
    Book.create("Kotlin in Action", "Manning", "9781617293290",
        Author.create("JEMEROV", "Dmitry", "", 1992),
        Author.create("ISAKOVA", "Svetlana", "", 1995)
    )
)



The code for the Dogs library can be found at:

https://github.com/KenBarclay/TBA
https://github.com/KenBarclay/TBA

Sunday, October 9, 2022

Kotlin: Functional Domain Modeling #0

 Kotlin: Functional Domain Modeling #0


In this blog we start investigating how to use the Kotlin type system to accurately capture application domain models in code. We will see that types can act as documentation; documentation that does not get out of sync with the design because the latter is represented in the code itself.



Functions

In functional programming, the concept of a pure function is one that holds the following properties:
  • The function is defined solely in terms of its parameters.
  • The function should always return the same result for the same input parameters.
  • The function should not cause any side effects by mutating any external state.
In other words, a pure function has no observable effect on the execution of the program other than to compute its result given its inputs; it has no side effects. One familiar pure function is the length function of a String. For any given string, the same length is always returned.

Functional programming (FP) is based on this simple premise: we construct our programs using only pure functions. This raises many issues for those with an OO background: how do we program without mutating state; how do we write programs that loop; how do we handle errors without throwing exceptions; how do we program with I/O. Over the course of these blogs we will show how to express all our programs without side effects. We will see how pure functions are easier to test, reuse and reason about.

Important in FP is the notion of higher order functions: functions that are parameters to other functions. We see that functions can also be the results of executing functions. In this way we create functions as values in our executing program, rather than defining them directly.

Through functions as parameters and functions as results we are able to fully exploit the machinery of these general functions, such as function composition. The resulting function definitions can be both more concise and more readable than traditional approaches.

This capability is important for a number of reasons. Having higher order functions means that you can eliminate entire categories of functions for a standard data structure by building general mechanisms that traverse the structures and apply higher order functions to each element. By enabling functions as return values, we have the opportunity to build dynamic, adaptable systems.

A common technique in FP is the concept of currying. Currying is the process of transforming a function of multiple parameters into a series of functions, each of which accepts a single parameter. We call a curried function with its single parameter as in f(a). This in turn delivers another function also requiring a single parameter as in f(a)(b). This continues until all the parameters have been provided. The Dogs library includes constructs to curry given functions.

Closely related to curried functions is the notion of partial function application. When we partially apply a curried function by providing some of the parameters the result is a function which may have the required signature that we can supply to a higher order function.

Functions are the building blocks that can be composed to build other functions. For example, consider the function that converts a String into an Int, and the function that converts an Int into a Boolean. Using function composition we can build larger functions out of smaller ones. We can compose these two functions to create one that converts a String into a Boolean. Whilst Kotlin does not support function composition out of the box, it provides enough features that we can create the support ourselves. This feature is provided by the Dogs library.



Managing side effects

In a banking application consider the function to open a new Account. The function may expect various parameters that define the properties of the Account such as the account number, the initial balance, the date the account is opened, etc. One would automatically expect the return type for open to be an Account type. But then the open function could fail because of validation errors. For example, the opening date may be before the current date. In the functional world we do not throw exceptions and expect users to catch them.

The Dogs library offers abstractions that helps address the issue of exceptions. You manage exceptions as effects that compose along with other abstractions in your domain model. An effect adds capabilities to your computation so you do not need to use side effects.

An effectful computation adds some capabilities to a computation. An effect is modeled with a type constructor that incorporates these additional capabilities. For example the open function might return the type Either<String, Account> which adds the effect of giving a String error in the event of failure otherwise the Account is wrapped in an Either.



Algebraic data types

An algebraic data type (ADT) is a kind of composite type: a type formed by combining other types. Two examples of algebraic types are product types (record types) and sum types (variant types). An ADT is a data types defined by one or more data constructors, each of which may contain zero or more type arguments. We say that the data type is the sum of its data constructors, and each data constructor is the product of its type arguments. These ideas map directly to constructs in Kotlin. The sum type is captured by Kotlin's sealed class, and the product type by a simple class declaration.

ADTs define the structure of data in your model. Sum types and product types provide the necessary abstractions for structuring the various data of our domain models. Sum types let you model the variations within a data type, product types help to cluster related data into larger abstractions.

An advantage of working with ADTs is that the compiler validates the various tags to ensure it contains valid combinations of data types. The compiler enforces the rules you have defined for an ADT.



Immutability

One essential characteristic of FP is that all data is immutable: an object whose state cannot be modified after it is created. This is in contrast to a mutable object which can be modified after it is created. This is typical of how a Java programmer would develop his code. FP encourages immutability and eschews in-place mutation.

In Kotlin once you define an immutable ADT you cannot modify any of its properties. To effect a change you create another abstraction from the original with the modified property. For example, we might represent a bank Account with a number an a balance property. The function credit would have an amount to be credited as parameter. The function would return another Account with the same number and a balance obtained from adding the original balance and the credit amount.

The data types provided by the Dogs library are all immutable. The library includes immutable Lists, immutable Options, immutable MultiMaps, etc. Imperative data structures are ephemeral in the sense that making a change to the structure destroys the old version leaving only the new version. A distinctive property of functional data structures is that they are persistent - updating a persistent functional data structure does not destroy the existing version, but creates a new version that coexists with the old one. Because nodes in the data structure are never modified, all nodes that are unaffected by an update can be shared between the old and the new version without worrying that a change in one will be visible to the other. Dogs data types are immutable and persistent.



Modeling Simple Values

As developers we have a tendency to focus on technical issues. However, the domain experts for whom we are developing an application think in terms of domain concepts such an order id, a product code, a customer name or a customer address. If we are to have a successful project it is important that we, as developers, fully embrace the domain experts requirements. To do that we must use the same vocabulary. So, instead of thinking in terms of Int and String, we think in terms of order id and address, even where the order id is an Int and the address is a String.

As our development proceeds it is important that a product code and a name are not mixed up. Just because they are both represented by Strings, say, they are not interchangeable. To make clear these types are distinct we employ a wrapper type that wraps the primitive representation. In an application, the classes ProductCode and Name would be wrapper classes around a String. Creating simple types like this ensures that they cannot be accidentally mixed.

However, wrapper classes  introduce a runtime overhead due to additional heap allocations. Moreover, if the wrapped type is primitive, the performance hit is terrible, because primitive types are usually heavily optimized by the runtime, while their wrappers don't get any special treatment. To solve such issues, Kotlin introduces a special kind of class called the inline classAt runtime, instances of the inline class will be represented using this single property. This is the main feature of inline classes, which inspired the name inline: data of the class is inlined into its usages (similar to how the content of inlined functions is inlined to call sites).

Almost always simple types are constrained in some way, such as having to be in a certain range or match a certain pattern. It is very unusual to have an unbound Int or String in a real-world domain. For example, a customer last name may be represented by a string, but that does not mean that it should contain a tab character or a symbolic character such as #.

The standard technique for constructing objects that need to honour a set of constraints is known as the smart constructor idiom. You prohibit the user from invoking the standard constructor and instead provide a factory function that ensures the user gets a data type from which she can recover a valid instance or a reason for failure. This would be an example of an effectful computation. Thereafter, because the data is immutable, the value never needs to be checked again.



Modeling Aggregate Values

We said that product types help to aggregate related data into larger abstractions. The clustering is represented by a Kotlin class. The individual class properties might themselves be represented by other ADTs. Consider a banking application with a customer Account class that contains properties such as the customer Address and the Date the account was opened. If the Account, Address and Date properties are immutable, then to change a customer's address we need to make the change at the aggregate Account level, not at the level of the Address.

The aggregate acts as the consistency boundary: when one part of the aggregate is updated, others might also require updating to ensure consistency. For example, in an order taking system, an aggregate Order might have a number of OrderLines and might have a total price property. If one order line changes its unit price then the total must be updated to keep the Order data consistent.

The aggregate is also where invariants are enforced. In our order taking example, there might be a business rule that any Order must have at least one OrderLine. If we try to delete multiple OrderLines from an Order then we must ensure there is at least one OrderLine remaining.

Nested data structures are a pain to inspect and change. The pain increases with the depth of the data structures. The most common functional approach is to avoid in-place mutation and generate a new instance instead with the updated values. Kotlin's data classes provide syntactic sugar for this operation. Consider the classes:

data class Address(val streetNumber: Int, val streetName: String)
data class Person(val name: String, val age: Int, val address: Address)

val addr = Address(10, "High Street")
val newAddr = addr.copy(streetNumber = 11)

delivers a new Address instance with the streetNumber as 11 and the streetName as High Street.

We update the street number of  for the address of a specific person using the same approach:

val per = Person("Ken", 25, addr)
val newPer = per.copy(address = per.address.copy(streetNumber = 11))

The greater the level of nesting of the objects, the more cluttered the syntax becomes. This situation demands a better abstraction and is provided by lenses. We can describe a lens as a group of functions that allows us to manipulate data inside a deeply nested class structure.



Enforcing invariants with the type system

An invariant is a condition that is always true. A business application might require that a customer last name must be all uppercase letters. That is an example of an invariant. Of course, the smart constructor is one technique to ensure invariants are honoured at the point of creation.

An Order comprises a number of OrderLines. A business requirement is that there must always be at least one OrderLine in an Order. We can capture this invariant in the type system if we use the class NonEmptyList rather than the more usual List class. The NonEmptyList class is provided by the custom Dogs library.

A customer Name might be presented as a product type comprising a LastName, a FirstName and a possible MiddleName. The Dogs library supports the Option type and we might use Option<MiddleName> to ensure the correct semantics.



Functional patterns in domain models

Functional design patterns are reusable abstractions. Object oriented design patterns are also reusable abstractions that are examples of best-practice. OO design patterns offer standard solutions for problems that occur repeatedly in programming. However the OO programmer has no code from which to develop a solution, but has to implement the patterns against the problem domain and will have to do this repeatedly each time the pattern is deployed. OO design patterns offer little reusability.

Functional design patterns offer much more reusability than their OO counterparts. Each functional pattern includes generic and reusable code which is invariant across the contexts where they are used. Context specific implementations are provided for instances where the pattern is applied which can be deployed in application code. Further, these patterns are accompanied with a set of laws. All instances of such patterns are expected to behave in a certain way and satisfy the laws associated with the respective implementations.

The KwikCheck test framework for Kotlin (as described here) is modeled after the QuickCheck framework. Property-based testing is generative testing. You do not supply specific example inputs with expected outputs as with unit tests. Instead, you define properties about the code and use the generative-testing engine to create randomized inputs to ensure the defined properties are correct. KwikCheck allows us to express these properties and generate the randomized inputs. We use KwikCheck to test the functional design pattern laws on the instances of the pattern. The KwikCheck test framework is built on the Dogs library.

We also use KwikCheck to verify the properties of our domain models. As an example, when we model the transfer of funds between two customer accounts in a banking application, a domain rule for this is the amount debited from one account must equal the amount credited to the other. We can think of this domain rule as a property that must be verified for a transfer operation.

We are familiar with the map function from the List class which applies a transformer function to each element in the List and delivers a new List of transformed values. Because List, Option, etc all share this behavior, we can document it this way as an extension function, in which F is a place-marker for the actual types for which it applies:

fun <A, B> F<A>.fmap(f: (A) -> B): F<B>

This is called a functor and abstracts the capability of mapping over a data type with a regular function. If F is List then we expect the List extension function:

fun <A, B> List<A>.fmap(f: (A) -> B): List<B>

In the Dogs library the data types Option, Either, List, Map and others are functors and have their implementation for function fmap.

The signature for fmap might suggest it would be a member function of a Functor interface which the data types Option, Either, List etc would implement. However, in such an interface, F would be a generic type so that F<A> and F<B> would give rise to what are called higher-kinded types that Kotlin does not support.

One immediate use for this abstraction is to discover other useful functions. If we have an F<Pair<A, B>> we can distribute the F over the Pair to get a Pair<F<A>, F<B>>. Again, with F the List type:

fun <A, B> List<Pair<A, B>>.distribute(): Pair<List<A>, List<B>> =
    Pair(this.fmap{pr -> pr.first}, this.fmap{pr -> pr.second})

We get two Lists of the same length, one with all the As and one with all the Bs.

In a library application we might have the following functions:

fun classifiedBooks(dewey: String): List<Book> = ...
fun isWithdrawn(book: Book): Boolean = ...

Function classifiedBooks returns a List of Books that match the dewey decimal classification. Function isWithdrawn determines if the given Book has been withdrawn from service. We can now use fmap to implement a domain specific behavior:

fun withdrawnByClassification(dewey: String): List<Boolean> =
    classifiedBooks(dewey).fmap(::isWithdrawn)

The functor is just one of many functional design patterns we include in the Dogs library.

A second functional design patterns is known as the monad and is defined for various types defined in the Dogs library. Monads allow the programmer to build up computations using sequential building blocks. The monad determines how combined computations form a new computation and frees the programmer from having to code the combination manually each time it is required. It is useful to think of a monad as a strategy for combining computations into more complex computations.

Again, we document this as an extension function bind, in which F is the place-marker for the actual types for which it applies:

fun <A, B> F<A>.bind(f: (A) -> F<B>): F<B>

It looks similar to the function fmap defined for the functor. The only difference is the signature of the function f that bind takes as parameter. For function fmap the function parameter returns a value, but for function bind it returns an instance of the type of the monad, here F. When you apply f to the receiver type F<A> in bind, you effectively end up with F<F<B>>. However, bind then flattens them into a single F<B>. Semantically, bind is equivalent to an fmap operation followed by a flatten operation. For this reason bind is often also known by the name flatMap.

The basic intuition behind the bind operation is that it allows us to combine two computations into one more complex computation and allows them to interact with one another. The receiver type F<A> represents the first computation. Significantly, the parameter type of bind is (A) -> F<B> which can, given the result of the first computation, produce a second computation to run. In other words fa.bind{a -> ... } is a computation which runs fa (some instance of type F<A>) and binds the value wrapped in fa to the function literal parameter a. The function body then decides what computation to run using the value for a.

The monad also includes a second function inject. Function inject lets us put a value into a monadic context. For example, for the List type inject is the same as creating a List with a single element. Often inject is simply the class constructor. Its signature is:

fun <A> inject(a: A): F<A>

The Dogs library supports these functions for the data types Option, Either, List, NonEmptyList, etc. 

Consider the function divide which returns an optional integer Option<Int>. If the numerator is exactly divisible by the denominator the function succeeds and returns their quotient wrapped in a Some, otherwise it fails and returns a None. Some and None are the concrete implementations of the Option type and are created with the factory functions some and none.

fun divide(num: Int, den: Int): Option<Int> {
    return if (num % den != 0) none() else some(num / den)
}

Consider now the function bindDivision which returns an optional pair of integers if the first two parameters are exactly divisible by the third parameter. The returned pair are quotients of the first and third parameter, and the second and third parameter.

fun bindDivision(a: Int, b: Int, c: Int): Option<Pair<Int, Int>> {
    return divide(a, c).bind{ac: Int ->
        divide(b, c).bind{bc: Int ->
            inject(Pair(ac, bc))
        }
    }
}

Consider the implementation of bindDivision. The outer call to divide(a, c).bind{ ac -> ...} has an Option value produced from the expression divide(a, c). Should this be a Some value, then the function literal is called and its formal parameter ac is bound to the result from the Some value. In the inner call divide(b, c).bind{ bc -> ...} the inner function literal is called with the formal parameter bc bound to the Some value produced from divide(b, c). If the two calls to divide both deliver Some values then a final Some result is produced carrying the pair we seek. If a None value is delivered by either call to divide then a None value is the final result.

Functional languages use monads to turn complicated sequences of functions into succinct pipelines that abstract away control flow, and side-effects. The programmer composes a sequence of function calls (a pipeline) with several bind operators chained together in an expression. Each function call transforms its input plain-type value, and the bind operator handles the returned monadic value, which is fed into the next step in the sequence. The side effect of the divide function returning None is automatically handled. The bind function only executes the following operation if the value is a Some value, otherwise it just passes the None along.

In later blogs we shall explore other functional design patterns such as the applicative functor, the foldable, the traversable alongside others. These patterns forms the basis of effectful computation in programming. They offer abstractions for handling effects within the domain models we develop. We will demonstrate the support provided by the custom Dogs library.



The code for the Dogs library can be found at:

https://github.com/KenBarclay/TBA
https://github.com/KenBarclay/TBA



Wednesday, April 6, 2022

Kotlin: Functional Domain Modeling #4

 Kotlin: Functional Domain Modeling #4


In this blog we introduce the foldable pattern. Foldable structures are collections of elements that can be folded to a summary value.



Collections

An invariant is a condition that stays true no matter what else happens. For example, we have said that the last name of an author must be fully capitalized. Consider now a class representing a library book. We expect the book to have an ISBN, a title, a publisher, and at least one author. This last condition is an example of an invariant that can be captured directly in the type system. To ensure that we have one or more book authors, we use the NonEmptyList class. The definition of this class requires that there must always be at least one element, and never empty. With this change, the constraint that there is always at least one author is now enforced automatically. The code is self-documenting and there is no need to write unit tests for this requirement.

This is how our Book class appears:

data class Book(
    val title: String,
    val publisher: String,
    val isbn: ISBN,
    val authors: NonEmptyList<Author>
)

Using the smart constructor idiom we have (for the Book class):

fun create(title: String, publisher: String, isbn: String, vararg authors: Author): ValidationNel<String, Book>

Note the authors parameter to function create. It is marked as a vararg so we can pass a variable number of actual parameters of type Author at the call site, including none.

Here is the complete class declaration:

data class Book(
    val title: String,
    val publisher: String,
    val isbn: ISBN,
    val authors: NonEmptyList<Author>
) {

    companion object {

        fun create(title: String, publisher: String, isbn: String, vararg authors: Author): ValidationNel<String, Book> {
            val vISBN: ValidationNel<String, ISBN> = ISBN.create(isbn)
            return if (authors.isEmpty())
                failureNel("Book: at least one author required")
            else {
                val list: List<Author> = ListF.from(*authors)
                vISBN.fmap{isbn: ISBN ->
                    Book(title, publisher, isbn, NonEmptyListF.from(list))
                }
            }
        }   // create

    }

}   // Book

The class ISBN is formulated as per the classes LastName, FirstName, etc. Within the body of the create function of class Book the vararg parameter authors is considered an Array<Author> and is transformed into a List<Author> from the Dogs library.

The following assert shows successfully creating a Book with two authors:

assertEquals(
    successNel(
       Book(
            "Kotlin in Action", "Manning", ISBN("9781617293290"),
            NonEmptyListF.of(
                Author(Name(LastName("JEMEROV"), FirstName("Dmitry"), none()), 1992),
                Author(Name(LastName("ISAKOVA"), FirstName("Svetlana"), none()), 1995)
            )
        )
    ),
    Book.create("Kotlin in Action", "Manning", "9781617293290",
        Author(Name(LastName("JEMEROV"), FirstName("Dmitry"), none()), 1992),
        Author(Name(LastName("ISAKOVA"), FirstName("Svetlana"), none()), 1995)
    )
)



The Foldable

Given the authors for an individual book we might wish to implement the following behaviors: (a) compute the sum of each author's age; (b) identify the oldest age among the authors. Although the details of the two behaviors we desire, some similarity exists. For example, in both we will have to process each individual author in the collection of authors.

The foldable type represents structures that can be folded to a summary value. In the case of a collection (such as List or Vector from Dogs) the functions will fold together (combine) the values contained in the collection to produce a single result. The foldable abstracts this capability and is implemented with two basic functions:

fun <A, B> F<A>.foldLeft(e: B, f: (B) -> (A) -> B): B
fun <A, B> F<A>.foldRight(e: B, f: (A) -> (B) -> B): B

Both functions are overloaded with variants that accept the function parameter in its uncurried form. Consider the simple list [1, 2, 3]. You sum the numbers of this list with 0 as the initial value (e) and the integer addition operation for f. Since foldLeft is left-associative, the execution of this fold would be ((0 + 1) + 2) + 3. Using a foldRight would evaluate as 0 + (1 + (2 + 3)). Since integer addition is associative, both yield the same result. For non-associative operations, the functions can produce differing results. Here are two simple examples:

assertEquals(25, ListF.of(5, 4, 8, 6, 2).foldLeft(0){ sum, elem -> sum + elem})
assertEquals(105, ListF.of(1, 3, 5, 7).foldRight(1){elem, prod -> prod * elem})

The use-cases for our book example might be implemented as:

object Analytics {

    fun totalAges(authors: NonEmptyList<Author>): Int {
        fun age(author: Author): Int = LocalDate.now().year - author.yearOfBirth
        return authors.foldLeft(0){ages: Int, author: Author -> ages + age(author)}
    }   // totalAges

    fun oldest(authors: NonEmptyList<Author>): Int {
        fun age(author: Author): Int = LocalDate.now().year - author.yearOfBirth
        return if (authors.size() == 1)
            age(authors.head())
        else
            authors.foldLeft(age(authors.head())){old: Int, author: Author -> if (age(author) > old) age(author) else old }
    }   // oldest

}   // Analytics

with the NonEmptyList a foldable. Two tests are:

val vBook: ValidationNel<String, Book> =
    Book.create("Kotlin in Action", "Manning", "9781617293290",
        Author(Name(LastName("JEMEROV"), FirstName("Dmitry"), none()), 1992),
        Author(Name(LastName("ISAKOVA"), FirstName("Svetlana"), none()), 1995)
    )
assertEquals(57, vBook.fold({0}, {book -> Analytics.totalAges(book.authors)})) // in 2022
assertEquals(30, vBook.fold({0}, {book -> Analytics.oldest(book.authors)}))    // in 2022

The implementations for totalAges and oldest have similarities that we can refactor into more generic patterns. For example: (a) both implement a fold over the collection; (b) both take an Int zero as the seed of the fold and and perform a binary operation on two Ints on each iteration. We can unify these seemingly different functions using a monoid.



The Monoid

Let us consider a simple algebraic structure, the monoid, which is defined only by its algebra. Other than satisfying the same laws, instances of the monoid may have little or nothing to do with one another. Nonetheless, we’ll see how this algebraic structure is often all we need to write useful, polymorphic functions.

Monoids are simple, ubiquitous, and useful. Monoids come up all the time in everyday programming, whether we’re aware of them or not. Working with lists, concatenating strings, or accumulating the results of a loop can often be phrased in terms of monoids. We’ll see how monoids are useful to assemble complex calculations from simpler pieces.

Consider the algebra of string concatenation. We can add "foo" + "bar" to get "foobar", and the empty string is an identity element for that operation. That is, if we say (s + "") or ("" + s), the result is always s. Furthermore, if we combine three strings by saying (r + s + t), the operation is associative—it doesn’t matter whether we parenthesize it: ((r + s) + t) or (r + (s + t)).

The term for this kind of algebra is monoid. The laws of associativity and identity are collectively called the monoid laws. A monoid consists of the following:

interface Semigroup<A> {

    fun combine(a: A, b: A): A

}   // Semigroup

interface Monoid<A : Any> : Semigroup<A> {

    val empty: A

}   // Monoid

where empty is the identity for the combine operation, and combine is an associative binary operation that takes two value of some type and combines them into one value of the same type. The Dogs library presents these two interfaces as well as a range of instances such as stringMonoid, intAdditionMonoid, ListSemigroup, ListMonoid, etc.

The implementation for function totalAges implements a fold over the collection with an Int zero as the seed for the fold and a binary addition on two Ints. Using intAddMonoid as an instance of Monoid we can define totalAges with:

object Analytics {

    fun totalAges(authors: NonEmptyList<Author>): Int {
        val mi: Monoid<Int> = intAddMonoid
        fun age(author: Author): Int = LocalDate.now().year - author.yearOfBirth
        return authors.foldLeft(mi.empty){ages: Int, author: Author -> mi.combine(ages, age(author))}
    }   // totalAges

    // ...

}   // Analytics

The operation within the fold is now an operation on a monoid instead of hardcoded operations on a specific type.



The Foldable foldMap

The foldable type also includes the foldMap function:

fun <A, B> F<A>.foldMap(mb: Monoid<B>, f: (A) -> B): B

Function foldMap is similar to our earlier fold operations, but maps every value of type A into a value of type B and then combines them using the given monoid. Here is our final version of Analytics:

object Analytics {

    fun totalAges(authors: NonEmptyList<Author>): Int {
        fun age(author: Author): Int = LocalDate.now().year - author.yearOfBirth
        return authors.foldMap(intAddMonoid, ::age)
    }   // totalAges

    fun oldest(authors: NonEmptyList<Author>): Int {
        val intLargerMonoid: Monoid<Int> = object: Monoid<Int> {
            override val empty: Int = 0
            override fun combine(a: Int, b: Int): Int = max(a, b)
        }
        fun age(author: Author): Int = LocalDate.now().year - author.yearOfBirth
        return authors.foldMap(intLargerMonoid, ::age)
    }   // oldest

}   // Analytics

Both functions use foldMap. In function totalAges the intAddMonoid is used to accumulate the individual age of each author. In function oldest the custom intLargerMonoid is defined to find the largest age of each individual author. So given the foldable type NonEmptyList<A>, a type B that is a monoid, and a mapping function between A and B, we package foldMap into a combinator that abstracts the requirements for totalAges and oldest.



The code for the Dogs library can be found at:

https://github.com/KenBarclay/TBA
https://github.com/KenBarclay/TBA