Into Adventure - Part 2 - Chaining
Fluent Interface or Fluent Style has been defined for some time. It is something specific to OOP due to the way that method calls are being made (you are calling the method on an object). A subset, or standard way of doing Fluent style is through the use of method chaining.
As a C# developer, the first time I found the term Fluent Style was with the introduction of LINQ on .Net 3.5
// C#
class MyTestObject
{
public String Name {get; set;}
public int Value {get; set;}
}
class Manipulator
{
public List<MyTestObject> TestObjects {get; set;}
public MyTestObject DoSomeOperations ()
{
this.TestObjects.Where(x => x.Name != "").First(x => x.Value > 5)
}
}
You can see that upon TestObjects (a List, which inherits from IEnumerable) we do an operation, Where, that returns an IEnumerable, upon which we do another operation, First, that will return a single object.
Of course, method chaining and fluent style have been present from the beginning in C#. Due to the String class being inmutable, you could chain method calls if you wanted to do multiple operations to a string.
public String DoSomething (String originalString)
{
return originalString.Trim().Replace("a", " ").Replace("d", "e");
}
You can do this on other OO languages
## Ruby
class ThreeDObject
def initialize(x, y, z)
@x = x
@y = y
@z = z
end
def translate(x, y, z)
# we translate the object
self
end
def rotate(x, y, z)
# we rotate the object
self
end
def scale(x, y, z)
# we scale the object
self
end
end
player = ThreeDObject.new(5,4,3)
player.scale(1,1,1).translate(1,0,0).rotate(90,0,0)
As you can see, the rule that needs to be followed is that you must return the next object (on this case self) that is going to have a method called.
You can even combine returning different objects to chain several method calls.
## Ruby
class Player
def initialize (weapon)
@weapon = weapon
end
def AttackWithWeapon
@weapon
end
end
class Weapon
def initialize (damage)
@damage = damage
end
def getDamage
@damage
end
end
sword = Weapon.new(5)
player = Player.new(sword)
puts player.AttackWithWeapon().getDamage()
But what about damaging a monster?
## Ruby
class Monster
def initialize (life)
@life = life
end
def receiveDamage (damage)
@life = @life - damage
end
end
monster = Monster.new(10)
monster.receiveDamage(player.AttackWithWeapon().getDamage())
This is where the chain breaks. getDamage() returns an integer, and there is no way to chain that result into monster. This is due to the way that you need to return the object unto which you are going to act next.
This idea of chaining works sligthly different on FP languages.
## Elixir
defmodule Player do
defstruct weapon: nil
end
defmodule Weapon do
defstruct damage: 0
end
defmodule Attack do
def attackWithWeapon do
weapon = %Weapon{damage: 5}
player = %Player{weapon: weapon}
IO.puts player.weapon.damage
end
end
Attack.attackWithWeapon
Really, what I’m doing here is just accessing members of structures and returning them. If you consider each member like a get property you actually get the same behaviour as with the Ruby code.
But what about that proposition with monster? How do I achieve that?
Enter the pipe operator: |> (on Elixir and F#, Clojure has the reader macros -> and -»)
## Elixir
defmodule Monster do
defstruct life: 0
def receiveDamage(damage, monster=%Monster{}) do
%Monster{monster|life: monster.life-damage}
end
end
defmodule Demonstration do
def demo do
weapon = %Weapon{damage: 5}
player = %Player{weapon: weapon}
monster = %Monster{life: 17}
monster = player.weapon.damage |> Monster.receiveDamage monster
IO.puts monster.life
end
end
Demonstration.demo
What the pipe operator is doing in this case is pass the result on the left as the first parameter to the function on the right.
The pipe operator becomes the abilitator of chaining. While in OOP languages you do a call on the return value (the context), on FP languages you pass the return value (the context) as parameter to the next function. With this behaviour, what you can achieve is to chain far more easily.
Looking at the chaining of strings, for example, we get this:
## Elixir
defmodule Chaining do
def stripchars(str, chars) do
String.replace str, ~r/"|#{Enum.join(String.split(chars, ""), "|")}"/, ""
end
def isAllUppercase(str) do
upper = (stripchars str, "123456789,") |> String.upcase
upper |> String.strip |> String.length > 0 && stripchars(str, "1234567890,") == upper
end
end
(Chaining.isAllUppercase "Is it?") |> IO.puts
Of course, on FP working with lists/sequences is the most natural thing to do.
// F#
type SumOfMultiples(multiples) =
new () = SumOfMultiples([3;5])
member this.AccumulateMultiples upperLimit divisor =
let maximumMultiplier = (upperLimit - 1) / divisor
List.map (fun number -> number * divisor) [1..maximumMultiplier]
member this.To upperLimit = multiples
|> List.map (fun number -> this.AccumulateMultiples upperLimit number)
|> List.reduce (fun acc elem -> acc @ elem)
|> Set.ofList
|> Set.fold (fun acc number -> acc + number) 0
The above code is from an exercise done on exercism.io
What you see there is that the chaining what it does is make you follow the transformations of your data in the order in which is processed (paraphrasing from a Hacker News commenter). It is quite similar to what you can achieve using LINQ on C# (which operates on IEnumerables).
For FP languages, compared to OOP, though, I think that the biggest difference is the one expressed before: instead of having to return an object to make the next call, the return becomes a parameter to the next call. It allows for more flexibility on chaining calls.
Let’s going to look back at the player/weapon duo from before, but with some more code in it.
defmodule Weapon do
defstruct max_damage: 0, min_damage: 0, speed: 0
def do_random_damage(weapon) do
weapon.max_damage
:random.seed(:erlang.now)
value = :random.uniform(weapon.max_damage - weapon.min_damage)
(value + weapon.min_damage)
end
def to_string(weapon) do
"Weapon speed: #{weapon.speed}"
end
end
defmodule Player do
defstruct weapons: nil, damage_done: 0
def select_quickest_weapon(player = %Player{}) do
select_quickest_weapon player.weapons
end
def select_quickest_weapon([weapon|tail]) do
select_quickest_weapon tail, weapon
end
def select_quickest_weapon([weapon = %Weapon{speed: speed1}|tail], %Weapon{speed: speed2} ) when speed1 < speed2 do
select_quickest_weapon(tail, weapon)
end
def select_quickest_weapon([_|tail], current) do
select_quickest_weapon(tail, current)
end
def select_quickest_weapon([], current) do
current
end
def remove_weapon(weapon, player) do
%Player{player| weapons: (player.weapons -- [weapon])}
end
def to_string(player) do
weapons_string = Enum.map(player.weapons, &(Weapon.to_string(&1)))
["Player weapons: "] ++ weapons_string
end
end
defimpl String.Chars, for: Player do
def to_string(player) do
Player.to_string(player)
end
end
defimpl String.Chars, for: Weapon do
def to_string(weapon) do
Weapon.to_string(weapon)
end
end
defmodule Demonstration do
def demo do
weapon_slow = %Weapon{max_damage: 10, min_damage: 1, speed: 5}
weapon_quick = %Weapon{max_damage: 4, min_damage: 1, speed: 3}
player = %Player{weapons: [weapon_slow, weapon_quick], damage_done: 0}
player
|> Player.select_quickest_weapon
|> Weapon.do_random_damage
|> IO.puts
end
end
Demonstration.demo
defmodule Demonstration2 do
def demo do
weapon_slow = %Weapon{max_damage: 10, min_damage: 1, speed: 5}
weapon_quick = %Weapon{max_damage: 4, min_damage: 1, speed: 3}
player = %Player{weapons: [weapon_slow, weapon_quick], damage_done: 0}
updated_player = player
|> Player.select_quickest_weapon
|> Player.remove_weapon player
IO.puts updated_player
end
end
Demonstration2.demo
On Demonstration.demo we have the line |> Weapon.do_random_damage. On Demonstration2.demo we have the line |> Player.remove_weapon. Because the result of the previous call is passed as parameter, it does ensue that is more easy to change which one is the next call. Trying to achieve the same thing with OO will led to a badly shared interface, most probably.
Go Back to Into Adventure Intro
Conclusions?
Method Chaining is very nice syntactic sugar to create flows that are far more easy and natural to follow. They do make things harder to debug. But their flexibility and power compensates.