Each version of the petrol pump logic is an implementation of the interface given in listing 4.1, where Inputs and Outputs are simple container classes with Stream and Cell type fields. Each class that implements Pump creates the machinery that connects the inputs to the outputs; all the logic you’ll need can be done in this way. When you run the simulator, you can select which pump logic you want to run in a drop-down box at the top.
package pump;
public interface Pump {
public Outputs create(Inputs inputs);
}
Figure 4.5 is a diagrammatic representation of the inputs and outputs of the pump logic encapsulated by Inputs and Outputs, and how they connect to things in the real world.
Listing 4.2 shows Inputs. These are the input streams:
■ sNozzle1, sNozzle2, sNozzle3—For each of the three fuels, a stream that fires when the nozzle is lifted (signaling the start of a fill) or hung up.
■ sKeypad—A stream representing a press of one of the buttons on the preset keypad, used to enter the fill amount. Key is an enumerated type identifying which button was pressed.
■ sFuelPulses—Pulses counted through the fuel-flow meter. The event payload is an integer number of pulses counted since the last event.
■ sClearSale—A signal sent from the point of sale once payment is completed, to unlock the pump for the next fill.
The input cells are as follows:
■ calibration—The multiplier to turn pulses into liters
■ price1, price2, price3—The price of each of the three fuels in dollars per liter Listing 4.1 Interface for the petrol pump logic
69 Code, meet outside world
Java forces you to write a bit of container-class boilerplate here, but we’ve given the entire code in listing 4.2 to make sure it’s clear.
Immutability is extremely important in FRP, so you make all the fields final, which means they can only be modified in the constructor. This is a good habit, and you should follow it whenever you can. In your FRP code, you can do this all the time.
package pump;
import nz.sodium.*;
public class Inputs { public Inputs(
Stream<UpDown> sNozzle1, Stream<UpDown> sNozzle2, Stream<UpDown> sNozzle3, Stream<Key> sKeypad,
Stream<Integer> sFuelPulses, Cell<Double> calibration,
Listing 4.2 Inputs to the petrol pump logic
sNozzle1 sNozzle2 sNozzle3 sKeypad sFuelPulses
Logic goes here
Pump mechanics calibration
price1 price2 price3 sClearSale
sSaleComplete
Point of sale sBeep
priceLCD1 priceLCD2 priceLCD3 delivery presetLCD saleCostLCD saleQuantityLCD
Figure 4.5 The interface between the pump logic and the (simulated) outside world
Cell<Double> price1, Cell<Double> price2, Cell<Double> price3, Stream<Unit> sClearSale) { this.sNozzle1 = sNozzle1;
this.sNozzle2 = sNozzle2;
this.sNozzle3 = sNozzle3;
this.sKeypad = sKeypad;
this.sFuelPulses = sFuelPulses;
this.calibration = calibration;
this.price1 = price1;
this.price2 = price2;
this.price3 = price3;
this.sClearSale = sClearSale;
}
public final Stream<UpDown> sNozzle1;
public final Stream<UpDown> sNozzle2;
public final Stream<UpDown> sNozzle3;
public final Stream<Key> sKeypad;
public final Stream<Integer> sFuelPulses;
public final Cell<Double> calibration;
public final Cell<Double> price1;
public final Cell<Double> price2;
public final Cell<Double> price3;
public final Stream<Unit> sClearSale;
}
Listing 4.3 shows the outputs. The output streams are as follows:
■ sBeep—When this stream fires, the simulator emits a short beep.
■ sSaleComplete—A message that’s sent to the point-of-sale system giving the details of the sale once it’s complete. Recall the input stream sClearSale: the simulated point of sale uses this to convey a response back from the point of sale to clear the sale and unlock the pump for the next fill.
The output cells are as follows:
■ delivery—Specifies whether to deliver fuel and, if so, which fuel and at what speed. Values: OFF, SLOW1, FAST1, SLOW2, FAST2, SLOW3, and FAST3.
■ presetLCD—The LCD that appears above the keypad in which you can enter a preset dollar amount.
■ saleCostLCD—Displays the dollars spent.
■ saleQuantityLCD—Displays the liters delivered.
■ priceLCD1, priceLCD2, priceLCD3—Displays the price for each fuel.
You’re writing the set methods in an unusual way: each method returns a copy of the structure, replacing one field’s value (shown in bold). You’re doing this to make the data structure immutable, meaning it can’t be modified in place. Then you know the values contained in it can’t be changed. This makes the code easier to rea- son about and preserves compositionality. This technique also makes the examples
71 Code, meet outside world
easier to read because you only have to specify the fields you care about; the rest can be left as defaults. We don’t recommend that you use this pattern all the time, but it can often be useful.
NOTE Copying the entire data structure to modify one field may feel ineffi- cient, but it’s not as inefficient as you may think. This pattern isn’t absolutely needed, but we recommend it as a way of making sure you do things the right way. When performance considerations drive software design, the result is usually bad. In fact, most assumptions that people make about performance turn out to be incorrect. For this reason, performance decisions should almost always be made on the basis of profiling.
package pump;
import nz.sodium.*;
public class Outputs { private Outputs(
Cell<Delivery> delivery, Cell<String> presetLCD, Cell<String> saleCostLCD, Cell<String> saleQuantityLCD, Cell<String> priceLCD1, Cell<String> priceLCD2, Cell<String> priceLCD3, Stream<Unit> sBeep,
Stream<Sale> sSaleComplete) { this.delivery = delivery;
this.presetLCD = presetLCD;
this.saleCostLCD = saleCostLCD;
this.saleQuantityLCD = saleQuantityLCD;
this.priceLCD1 = priceLCD1;
this.priceLCD2 = priceLCD2;
this.priceLCD3 = priceLCD3;
this.sBeep = sBeep;
this.sSaleComplete = sSaleComplete;
}
public Outputs() {
this.delivery = new Cell<Delivery>(Delivery.OFF);
this.presetLCD = new Cell<String>("");
this.saleCostLCD = new Cell<String>("");
this.saleQuantityLCD = new Cell<String>("");
this.priceLCD1 = new Cell<String>("");
this.priceLCD2 = new Cell<String>("");
this.priceLCD3 = new Cell<String>("");
this.sBeep = new Stream<Unit>();
this.sSaleComplete = new Stream<Sale>();
}
Listing 4.3 Outputs from the petrol pump logic
public final Cell<Delivery> delivery;
public final Cell<String> presetLCD;
public final Cell<String> saleCostLCD;
public final Cell<String> saleQuantityLCD;
public final Cell<String> priceLCD1;
public final Cell<String> priceLCD2;
public final Cell<String> priceLCD3;
public final Stream<Unit> sBeep;
public final Stream<Sale> sSaleComplete;
public Outputs setDelivery(Cell<Delivery> delivery) { return new Outputs(delivery, presetLCD, saleCostLCD,
saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);
}
public Outputs setPresetLCD(Cell<String> presetLCD) { return new Outputs(delivery, presetLCD, saleCostLCD,
saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);
}
public Outputs setSaleCostLCD(Cell<String> saleCostLCD) { return new Outputs(delivery, presetLCD, saleCostLCD,
saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);
}
public Outputs setSaleQuantityLCD(Cell<String> saleQuantityLCD) { return new Outputs(delivery, presetLCD, saleCostLCD,
saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);
}
public Outputs setPriceLCD1(Cell<String> priceLCD1) { return new Outputs(delivery, presetLCD, saleCostLCD,
saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);
}
public Outputs setPriceLCD2(Cell<String> priceLCD2) { return new Outputs(delivery, presetLCD, saleCostLCD,
saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);
}
public Outputs setPriceLCD3(Cell<String> priceLCD3) { return new Outputs(delivery, presetLCD, saleCostLCD,
saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);
}
public Outputs setBeep(Stream<Unit> sBeep) {
return new Outputs(delivery, presetLCD, saleCostLCD,
saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);
}
public Outputs setSaleComplete(Stream<Sale> sSaleComplete) { return new Outputs(delivery, presetLCD, saleCostLCD,
saleQuantityLCD, priceLCD1, priceLCD2, priceLCD3, sBeep, sSaleComplete);
} }
73 The life cycle of a petrol pump fill