Copy constructors and lenses

After examining our immutable example implementation, we are not able to say that it covers all the functionalities of the imperative approach. For instance, it does not provide us with a way to change the producer of a product. After all, we cannot change it.

Whenever we need to change any property of the product, we need to go through the following process:

let mexicanBananas = FunctionalProduct(name: bananas.name,
                                      price: bananas.price,
                                   quantity: bananas.quantity,
                                   producer: Producer(name: "XYZ",
                                                   address: "New Mexico,
                                                     Mexico"))

This solution is verbose and does not look nice. Let's examine how we can improve this process.

Copy constructor

The first solution is to provide a new init method that copies the current instance. This approach is called copy constructor. Let's add our new init method and leverage it:

init(products: [FunctionalProduct],
    lastModified: NSDate) {
 
    self.products = products
    self.lastModified = lastModified
    }
 
    init(productTracker: FunctionalProductTracker,
               products: [FunctionalProduct]? = nil,
           lastModified: NSDate? = nil) {
 
    self.products = products ?? productTracker.products
    self.lastModified = lastModified ?? productTracker.lastModified
 }

We added the default init as well because by adding a new init method to our struct, we lost the benefit of automatic init generation. We also need to change our addNewProduct to accommodate these changes:

  func addNewProduct(item: FunctionalProduct) -> FunctionalProductTracker {

    return FunctionalProductTracker(productTracker: self,
                                          products: self.products + [item])
}

Whenever we need to modify our object partially, we will be able to do so easily using this technique.

Lens

In the previous section, we covered copy constructors. Here, we will examine a functional structure called lens. Simply put, lenses are functional getters and setters that are implemented for a whole object and its parts:

  • Getters: We can look through the lens at an immutable object to get its parts
  • Setters: We can use the lens to change a part of an immutable object

Let's implement a Lens:

struct Lens<Whole, Part> {
    let get: Whole -> Part
    let set: (Part, Whole) -> Whole
}

Let's use it to change our FunctionalProduct object to get and set the producer property:

let prodProducerLens: Lens<FunctionalProduct, Producer> =
  Lens(get: { $0.producer},
       set: { FunctionalProduct(name: $1.name,
                               price: $1.price,
                            quantity: $1.quantity,
                            producer: $0)})

Let's change the producer for mexicanBananas:

let mexicanBananas2 = prodProducerLens.set(Producer(name: "QAZ",
                                                 address: "Yucatan,
                                                   Mexico"),
                                           mexicanBananas)

Through our lens, we can change it as shown in the preceding code.

Let's examine another example. Suppose that we have a Producer object as follows:

let chineeseProducer = Producer(name: "KGJ",
                             address: "Beijing, China")

We want to change the address:

let producerAddressLens: Lens<Producer, String> =
  Lens(get: { $0.address },
       set: { Producer(name: $1.name,
                    address: $0)})

let chineeseProducer2 = producerAddressLens.set("Shanghai, China",
  chineeseProducer)

Suppose that we had mexicanBananas2 and needed to have a Chinese banana producer, then we could use:

let chineseBananaProducer = prodProducerLens.set(
  producerAddressLens.set("Shanghai, China", chineseProducer), 
  mexicanBananas2)

This syntax does not look very simple, and it seems that we did not gain much after all. In the next section, we will simplify it.

Lens composition

Lens composition will help to simplify our lens; let's examine how:

infix operator >>> { associativity right precedence 100 }

func >>><A,B,C>(l: Lens<A,B>, r: Lens<B,C>) -> Lens<A,C> {
    return Lens(get: { r.get(l.get($0)) },
                set: { (c, a) in
                    l.set(r.set(c,l.get(a)), a)
    })
}

Let's test this:

let prodProducerAddress = prodProducerLens >>> producerAddressLens
let mexicanBananaProducerAddress = prodProducerAddress.get(mexicanBananas2)
let newProducer = prodProducerAddress.set("Acupulco, Mexico",
  mexicanBananas2)
print(newProducer)

The result is going to be:

FunctionalProduct(name: "Banana",
                  price: 0.79,
                  quantity: 2,
                  producer: Producer(name: "QAZ", address: "Acupulco, Mexico"))

Using lenses and composition, we were able to get and set a product's producer address.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset