beginner
I have:
A
.I want to have:
A
.If you have several values of the same type (at least one), you can combine
them into a single value. combine
is a binary operation; i.e., you need two values of type A
to combine them into one.
If you have more than two elements, you can either chain multiple combine
calls, or use combineAll
, which accepts a variadic number of parameters.
This operation is available in the Semigroup type class, and it is an associative operation. Your type must conform to Semigroup
in order to have combine
available.
Basic types like numeric types or Strings already conform to Semigroup
in Bow. For instance, we can combine Int
values like:
let x = 2
let y = 5
let z = 8
x.combine(y) // Returns 7
Int.combineAll(x, y, z) // Returns 15
The operation is associative; that is, we can first combine x
and y
, and then z
, or first y
and z
, and then x
.
x.combine(y).combine(z) == x.combine(y.combine(z))
let a = "Hello"
let b = "World"
a.combine(b) != b.combine(a)
I have:
A
.I want to have:
A
.The problem described here is similar to the problem above, with the difference of potentially having no values. We can use combine
, but we need to provide a default value when no values are combined.
This default value is empty
and it is available in the Monoid type class. empty
has the property of being combinable with any other element of the type, yielding the same value it is combined with.
Like with Semigroups, basic types in Swift already conform to Monoid
in Bow.
x.combine(Int.empty()) // Returns x
Int.empty().combine(x) // Returns x
I have my own type and I need to combine elements of this type.
You need to use Swift extension
mechanisms to make your type conform to Semigroup
and Monoid
, guaranteeing that your implementation honors the properties of both type classes.
Consider a type to model a shopping cart:
struct ShoppingCart<Product> {
let products: [Product]
let amount: Double
}
If I need to give it the ability to combine its values, I need to determine how each of its properties are combined with the ones in other values. In this case:
products
can be combined by concatenating the arrays of the two carts that are being combined.amount
can be combined by adding the amounts of the two carts.Therefore, I can make an extension
to conform to Semigroup
:
extension ShoppingCart: Semigroup {
func combine(_ other: ShoppingCart<Product>) -> ShoppingCart<Product> {
ShoppingCart(
products: self.products + other.products,
amount: self.amount + other.amount
)
}
}
If I need to give it the ability to have an empty value, I need to determine how each of its properties need to be initialized to an empty value. In this case:
products
can be initialized to an empty array.amount
can be initialized to 0.0
.Therefore, I can make an extension to conform to
Monoid`:
extension ShoppingCart: Monoid {
static func empty() -> ShoppingCart<Product> {
ShoppingCart(
products: [],
amount: 0.0
)
}
}
If your type conforms to Monoid
it also must conform to Semigroup
, but the reverse is not true. You may be able to combine values of a type, but not be able to provide an empty value.
One example of this is the NonEmptyArray
type, which, as its name suggests, is never empty; therefore, it cannot provide an empty value for the combination.