# Comp  sition

James Dabbs

jamesdabbs

## Composition

• Object composition (not to be confused with function composition) is a way to combine simple objects or data types into more complex ones
• Function composition (not to be confused with object composition) is an act or mechanism to combine simple functions to build more complicated ones

Wikipedia

## Composition

Goals for this talk

• illuminate object and functional composition
• confuse object and functional composition
• develop a phrasebook for those interested in spending more time in functional country
• Legos
• Topology
• Category Theory
• Haskell
• Today

### Functional core

"Haskell is easy! You just need to grok category theory and then you can starting printing stuff."

— A. Strawman

## Category Theory

Lessons Learned

A category is

• a collection of objects
• functions between those objects
• the means to compose functions

Category Theory is the study of composition

Lessons Learned

# Isomorphism

## Category Theory

Lessons Learned

### Isomorphic

• Literally: same shape
• Two things that may appear different, but function identically

The Curry-Howard isomorphism says that proving theorems and writing programs are the same activity

a, b, ab, ba, aaa, aba, bab, bbb, ...

## Category Theory

Lessons Learned

See e.g. the Nullstellensatz in Algebraic Geometry, the Monstrous Moonshine theorem, Homotopy Type Theory, ...

Lessons Learned

## Category Theory

Lessons Learned

### Abstraction

• Abstraction is the process of unifying concepts by ignoring their differences
• You often know you're at the right level of abstraction when there's only one thing that could possibly work (and that thing works)
• Function composition is a surprisingly robust abstraction

## Object Composition

• In OO, composition refers to an object holding a reference to one or more other objects that it collaborates with to fulfill its responsibilities
• has-a relationship (as opposed to inheritance's is-a)

Favor object composition over class inheritance

— Gang of Four

## Object Composition

class Topologist
attr_reader :jokes

def initialize jokes:
@jokes = jokes
end

def tell_joke
@jokes.sample
end
end
emmy = Topologist.new jokes: Topologist::JOKES
puts emmy.tell_joke
"Klein bottle for sale, enquire within"

a Klein bottle

## Object Composition

class AlgebraicTopologist < Topologist
def tell_joke
@jokes.select { |joke| gets? joke }.sample
end

private

def gets? joke
joke.length < 200 || joke.include?("group")
end
end

hopf = AlgebraicTopologist.new jokes: Topologist::JOKES
puts hopf.tell_joke
"Q: What's purple and commutes?
A: An abelian grape."

## Object Composition

class LoudTopologist < Topologist
def tell_joke
super.upcase + "!!!"
end
end

lej = LoudTopologist.new jokes: Topologist::JOKES
puts lej.tell_joke
"A COMATHEMATICIAN IS A DEVICE FOR TURNING COTHEOREMS INTO FFEE!!!"

## Object Composition

class LoudAlgebraicTopologist < AlgebraicTopologist
def tell_joke
super.upcase + "!!!"
end
end

or

class LoudAlgebraicTopologist < LoudTopologist
def jokes
@jokes.select { |joke| gets? joke }.sample
end

private

def gets? joke; ...; end
end
class LoudAlgebraicTopologist < ??
end

## Object Composition

class Algebraist
def initialize topologist
@topologist = topologist
end

def tell_joke
@topologist.jokes.shuffle.find { |joke| gets? joke }
end

private

def gets?(joke); ...; end
end
emmy = Topologist.new jokes: Topologist::JOKES
hopf = Algebraist.new emmy
puts hopf.tell_joke
"I have more jokes but they are too small for this margin"

## Object Composition

class Loudspeaker
def initialize jokester
@jokester = jokester
end

def tell_joke
@jokester.tell_joke.upcase + "!!!"
end
end
topologist                = Topologist.new jokes: Topologist::JOKES
algebraic_topologist      = Algebraist.new topologist
loud_topologist           = Loudspeaker.new topologist
loud_algebraic_topologist = Loudspeaker.new algebraic_topologist
• Collaborating objects allow us to parameterize behavior
• We do not need a distinct class for each combination of behaviors
topologist                = Topologist.new jokes: Topologist::JOKES
algebraic_topologist      = Algebraic.new topologist
loud_topologist           = Loudspeaker.new topologist
loud_algebraic_topologist = Loudspeaker.new algebraic_topologist

## Observations

• This is still brittle because of the inconsistent API between objects
• This is far from the only way to design the API of these objects
topologist = Topologist.new jokes: Topologist::JOKES
error      = Algebraic.new Loudspeaker.new topologist

puts error.tell_joke

# => ...

## Function Composition

f(x) = x + 2
$f(x) = x + 2$
g(x) = x^2
$g(x) = x^2$
(f \circ g)(3)
$(f \circ g)(3)$
f(1) = 1 + 2 = 3
$f(1) = 1 + 2 = 3$
g(5) = 25
$g(5) = 25$
= f(g(3))
$= f(g(3))$
= f(9)
$= f(9)$
= 11
$= 11$

## Function Composition

f :: Int -> Int
f x = x + 2

g :: Int -> Int
g x = x**2

(f . g) 3 = f (g 3) = f 9 = 11
f \circ g
$f \circ g$

is itself a function

f . g :: Int -> Int
. :: (B -> C) -> (A -> B) -> (A -> C)
\circ
$\circ$

is a function too!

newFunction = f . g
newFunction 3 = 11
map newFunction [1,2,3] = [3,6,11]

## Consider

-- WAI (2.0)
type App        = Request -> IO Response
type Middleware = App -> App
-- Rack
type App        = Env -> IO (Status, Headers, Body)
type Middleware = App -> App
serveStatic :: String -> Middleware
:: String -> (Request -> IO Response) -> (Request -> IO Response)
middlewareChain = allowOrigin "localhost" . serveStatic "/build" . logStdoutDev
appDev          = middlewareChain appBase

Pure

## Advantages

Pure Data-in, data-out

• No implicit dependencies
• Easy to test and trust
• Easy to parallelize

## Advantages

Equational reasoning

f x = x + 2

g (f x) = g (x + 2)

Compare with

max x y = if x > y then x else y

inc x = x ++

max (inc x) (inc y) = if x++ > y++ then x++ else y++

## Advantages

Tansparent composition

f :: A -> B
g :: B -> C

g . f  :: A -> C
f >> g :: A -> C

>> :: (A -> B) -> (B -> C) -> (A -> C)
• Functions always have one input type and one output type
• Functions are composable whenever their output and input types match up

## Topologists in Haskell

topologist :: [String] -> String
topologist jokes = head (shuffle jokes)
topologist :: [Joke] -> Joke
topologist :: [String] -> String
topologist = shuffle >> head
topologist jokes = (head . shuffle) jokes
-- >> = flip .
topologist jokes = (shuffle >> head) jokes

Pointless

## Topologists in Haskell

algebraicTopologist :: [String] -> String
algebraicTopologist jokes = (filter gets >> shuffle >> head) jokes
where
gets joke = length joke < 200 || isInfixOf "group" joke


But wait ...

topologist          =                shuffle >> head
algebraicTopologist = filter gets >> shuffle >> head

algebraicTopologist = filter gets >> shuffle >> head
where
gets joke = ...

## Topologists in Haskell

topologist          = filter (\_ -> True) >> shuffle >> head
algebraicTopologist = filter gets         >> shuffle >> head

algebraicTopologist :: [String] -> String
algebraicTopologist jokes = (filter gets >> shuffle >> head) jokes
where
gets joke = length joke < 200 || isInfixOf "group" joke

algebraicTopologist = filter gets >> shuffle >> head
where
gets joke = ...

But wait ...

## Topologists in Haskell

filter isFunny >> shuffle >> head
makeTopologist :: SenseOfHumor -> Topologist
makeTopologist :: (String -> Bool) -> ([String] -> String)
topologist          = makeTopologist (\_ -> True)
algebraicTopologist = makeTopologist (\joke -> length joke < 200
|| isInfixOf "group" joke)
makeTopologist isFunny = filter isFunny >> shuffle >> head

## Topologists in Haskell

loudTopologist = shuffle >> head >> shout
where
shout str = upcase str ++ "!!!"

-- or, equivalently
loudTopologist = topologist >> shout
filter criteria >> shuffle >> head >> deliver

## Topologists in Haskell

makeTopologist :: SenseOfHumor -> Delivery -> Topologist
makeTopologist isFunny delivery = filter isFunny >> shuffle >> head >> delivery

makeTopologist :: (String -> Bool) -> (String -> String) -> ([String] -> String)
topologist = makeTopologist (\_ -> True) (\j -> j)
= makeTopologist (const True) id

loudAlgebraic = makeTopologist (\j -> length j < 200) (\j -> upcase j ++ "!!!")

## Topologists in Haskell in Ruby

double :: Int -> Int
double x = x * 2


double = ->(x) { x * 2 }

double.call 5 # => 10
addThree :: Int -> Int -> Int -> Int
addThree a b c = a + b + c
addThree = -> a do
-> b do
-> c do
a + b + c
end
end
end

addThree.call(1).call(2).call 3 # => 6
addThree = ->(a,b,c) { a + b + c }.curry

## Topologists in Haskell in Ruby

makeTopologist :: (String -> Bool) -> (String -> String) -> [String] -> String
makeTopologist isFunny delivery = filter isFunny >> shuffle >> head >> delivery
makeTopologist = -> isFunny do
-> delivery do
-> jokes do
delivery.call jokes.select { |j| isFunny.call j }.sample
end
end
end

## Topologists in Haskell in Ruby

makeTopologist :: SenseOfHumor -> Delivery -> Topologist
class Topologist
attr_reader :sense_of_humor, :delivery

def initialize sense_of_humor:, delivery:
@sense_of_humor, @delivery = sense_of_humor, delivery
freeze
end
end

## Topologists in Haskell in Ruby

makeTopologist :: SenseOfHumor -> Delivery -> Topologist
class Topologist
attr_reader :sense_of_humor, :delivery

def initialize sense_of_humor:, delivery:
@sense_of_humor, @delivery = sense_of_humor, delivery
freeze
end

def call jokes
delivery.call jokes.select { |joke| sense_of_humor.call joke }.sample
end
end
topologist = Topologist.new(
sense_of_humor: ->(joke) { true },
delivery:       ->(joke) { joke }
)
loud_topologist = Topologist.new(
sense_of_humor: ->(joke) { true },
delivery:       ->(joke) { joke.upcase + "!!!" }
)

## Topologists in Haskell in Ruby

makeTopologist :: SenseOfHumor -> Delivery -> Topologist
class Topologist
def with overrides={}
self.class.new({
sense_of_humor: sense_of_humor,
delivery:       delivery
}.merge overrides)
end
end
loud_topologist = topologist.with(
delivery: ->(joke) { joke.upcase + "!!!" }
)

## Topologists in Haskell in Ruby

class SenseOfHumor
def initialize word:, max_length:
@word, @max_length = word, max_length
freeze
end

def call joke
joke.length < @max_length || joke.include?(@word)
end
end
algebraic = SenseOfHumor.new max_length: 200, word: "group"

topologist.with      sense_of_humor: algebraic
loud_topologist.with sense_of_humor: algebraic

## Embedded Haskell

Rules Guidelines for functional Ruby

• Objects should have a defined collection of fields. An object's state should be expressible entirely in terms of those fields.
• Fields should be set at initialization and then frozen. "Updates" are performed by creating a new object.
• Objects should respond to call to perform their (primary) responsibility.

## Embedded Haskell

Advantages

In this system, our objects

• have a clearly defined responsibility
• have a predictable interface
• are immutable
• have injectable dependencies
• are easy to configure and re-configure
• are easy to test
• are easy to mock in tests

## In Ruby

you already think about types and composition

def create_post request
post = parse_input request
id   = persist post
success_with_id id
end
parse_input     :: Request -> Post
persist         :: Post -> Int
success_with_id :: Int -> String

create_post = parse_input >> persist_post >> success_with_id

## In Ruby

you already think about duck types and composition

def create_post request
post = parse_input request
id   = persist post
success_with_id id
end

def persist obj
obj.save!
obj.id
end
parse_input     :: Request -> Post
persist         :: Persistable -> Int
success_with_id :: Int -> String

create_post = parse_input >> persist >> success_with_id

## In Ruby

you already think about duck types and composition

def create_post request
post = parse_input request
id   = persist post
success_with_id id
end

def persist obj
if valid? obj
obj.save
obj.id
end
end
parse_input     :: Request -> Post
persist         :: Persistable -> Int?
success_with_id :: Int -> String

create_post = parse_input >> persist >> success_with_id

## Embedded Haskell

Testing

class Multiples
fields :factor

def call count
1.upto(count).map { |i| i * factor }
end
end
m = Multiples.new factor: 7

cases = {
1 => [7],
2 => [7,14],
3 => [7,14,21],
-1 => []
}

cases.each do |input, output|
expect(m.call input).to eq output
end

## Embedded Haskell

Testing

class Bot
fields :handlers,    # :: [Handler]
:dispatcher,  # :: [Handler] -> Message -> Handler
:http         # :: Request -> Nil

def call request; ...; end
end

let(:bot)     { Bot.new ... } # or a "live" production Bot configuration
let(:message) { Message.new text: "hello", user: ... }

## Embedded Haskell

Testing

Possible considerations

• Given an input, expect an output
• Need a dependency to produce a particular value
• Need a dependency to receive a particular value

## Embedded Haskell

Testing

# 1) Input => Output
response = bot.with(
http: ->(req) { }
).call message

# 2) Dependecy returns a particular value
response = bot.with(
dispatcher: ->(reqs, msg) { TestHandler.new }
).call message

# 3) Dependency receives a particular value
http = double "HTTPClient"
expect(http).to receive(...)

response = bot.with(http: http).call message

expect(response.text).to eq "..."

## Example - Medlink

class SMS::OrderPlacer
def initialize sms
@sms = sms
end

def run!
parse_message
build_order
validate_order
record
send_response
end

private

# ...
end

## Example - Medlink

class SMS::OrderPlacer
def initialize sms
@sms = sms
end

def run!
parse_message
build_order
validate_order
record
send_response
end

private

def parse_message
@user = something_involving @sms
@text = something_else_involving @sms
end

def send_response
Medlink.notifier.notify "#{@user} has ordered #{@order.supplies.to_sentence}"
end

# ...
end

## When composition is implicit, so are boundaries

Prefer explicit boundaries with explicit values

parse_message  :: SMS -> ParseResult
build_order    :: ParseResult -> Order
validate_order :: Order -> Order -- ??
record         :: Order -> Order -- ??
send_response  :: Order -> String

parse_message >> build_order >> validate_order >> record >> send_response

## Example - Composed

class SMS::OrderPlacer
fields :notifier, :db

def call sms
send_response( record( validate_order( build_order( parse_message( sms )))))
end

private

def parse_message sms
...
end

## Example - Composed

class SMS::OrderPlacer
fields :notifier, :db

def call sms
compose(
:parse_message,
:build_order,
:validate_order,
:record,
:send_response
).call sms
end

def compose *methods
->(val) do
result = val
methods.each do |name|
result = method(name).call result
end
result
end
end
end

## Example - Composed

class SMS::OrderPlacer
fields :notifier, :db

def call sms
compose(
:parse_message,
:build_order,
:validate_order,
:record,
:send_response
).call sms
end

def compose *methods
->(val) do
methods.reduce(val) do |result, name|
method(name).call result
end
end
end
end

## Example - Composed

class SMS::OrderPlacer
fields :notifier, :db

def call sms
compose(:parse_message, :build_order, :validate_order,
:record, :send_response).call sms
end

def parse_message sms
ParseResult.new user: ..., text: ...
end

def record order
db.save_order order
order
end

def send_response order
notifier.call "#{order.user} has ordered #{order.supplies.to_sentence}"
"Got it! Your #{order.supplies.to_sentence} are on the way"
end

# ...
end

## Example - Composed

placer = Medlink.container.sms_order_placer.with(
db:       instance_double(Medlink::Repository),
notifier: ->(_) { nil }
)

expect(placer.db).to receive(:save_order)

message = Message.new(
text: "Order bandages, mosquito netting - please and thank you!",
user: ...
)
response = placer.call message
expect(response).to eq \
"Got it! Your bandages and mosquito netting are on the way!"

## Example - Extracted

class SMS::OrderPlacer
fields :notifier, :db, :order_placer

def call sms
compose(:parse_message, :build_order, order_placer).call sms
end

def compose *methods
->(val) do
methods.reduce(val) do |result, m|
func = m.respond_to?(:call) ? m : method(m)
func.call result
end
end
end
end
let(:placer) { Medlink.container.sms_order_placer.with order_placer: id }

order = placer.call Message.new(
text: "Order bandages, mosquito netting",
user: ...
)
expect(order.supplies).to eq ["bandages", "mosquito netting"]

## Example - Extracted

class OrderPlacer
fields :notifier, :db

def call order
compose(:validate_order, :record, :send_response).call sms
end

private

# other methods extracted from SMS::OrderPlacer ...
end

Methods are a service object in waiting

SMS

parse_message  :: SMS         -> ParseResult
build_order    :: ParseResult -> Order
validate_order :: Order       -> Order
record         :: Order       -> Order
send_response  :: Order       -> String

sms_order_placer :: SMS -> String

ParseResult

Order

String

Order

Order

## Example - Maybe

SMS

-- Maybe Type ~> Type | nil
parse_message  :: SMS         -> Maybe ParseResult
build_order    :: ParseResult -> Maybe Order
validate_order :: Order       -> Maybe Order
record         :: Order       -> Maybe Order
send_response  :: Order       -> Maybe String

sms_order_placer :: SMS -> Maybe String

ParseResult

Order

Maybe ParseResult

Maybe Order

Order

Order

Maybe Order

Maybe Order

Maybe String

## Example - Maybe

class SMS::OrderPlacer
def call sms
compose(:parse_message, :build_order, :validate_order,
:record, :send_response).call sms
end

def compose *methods
->(val) do
methods.reduce(val) do |result, me|
# call me, maybe
if result
func = me.respond_to?(:call) ? me : method(me)
func.call result
end
end
end
end
end
-- Maybe Type ~> Type | nil
parse_message  :: SMS         -> Maybe ParseResult
build_order    :: ParseResult -> Maybe Order
validate_order :: Order       -> Maybe Order
record         :: Order       -> Maybe Order
send_response  :: Order       -> Maybe String

sms_order_placer :: SMS -> Maybe String

## Example - Maybe

class SMS::OrderPlacer
def validate order
return if order.supplies.none?
return if ...
end
end
db = instance_double Medlink::Repository
expect(db).not_to receive(:save_order)

response = sms_order_placer.call Message.new(text: "Order invalid", user: ...)
expect(response).to eq nil
-- Maybe Type ~> Type | nil
parse_message  :: SMS         -> Maybe ParseResult
build_order    :: ParseResult -> Maybe Order
validate_order :: Order       -> Maybe Order
record         :: Order       -> Maybe Order
send_response  :: Order       -> Maybe String

sms_order_placer :: SMS -> Maybe String

SMS

ParseResult

Order

E[ParseResult]

E[Order]

Order

Order

E[Order]

E[Order]

E[String]

## Example - Either

-- E[Type] = Either Type String
parse_message  :: SMS         -> E[ParseResult]
build_order    :: ParseResult -> E[Order]
validate_order :: Order       -> E[Order]
record         :: Order       -> E[Order]
send_response  :: Order       -> E[String]

sms_order_placer :: SMS -> E[String]

## Example - Either

-- E[Type] = Either Type String
parse_message  :: SMS         -> E[ParseResult]
build_order    :: ParseResult -> E[Order]
validate_order :: Order       -> E[Order]
record         :: Order       -> E[Order]
send_response  :: Order       -> E[String]

sms_order_placer :: SMS -> E[String]
Either = Struct.new :value, :error

class SMS::OrderPlacer
def success value; Either.new value, nil; end
def error   err;   Either.new nil, err  ; end

# N.B. This just uses regular old return values
def validate order
return error("Can't order none many supplies") if order.supplies.none?
return error("Something else") if ...
success order
end
end

## Example - Either

-- E[Type] = Either Type String
parse_message  :: SMS         -> E[ParseResult]
build_order    :: ParseResult -> E[Order]
validate_order :: Order       -> E[Order]
record         :: Order       -> E[Order]
send_response  :: Order       -> E[String]

sms_order_placer :: SMS -> E[String]
class SMS::OrderPlacer
def compose *methods
->(val) do
methods.reduce(val) do |result, m|
if result.error
result
else
func = m.respond_to?(:call) ? m : method(m)
func.call result.value
end
end
end
end
end

## Example - Logging

class SMS::OrderPlacer
fields :notifier, :db, :logger

# call :: SMS -> Either Order String
def call sms; ...; end
end
class SMS::OrderPlacer
fields :notifier, :db

# call :: SMS -> (Result, Logs)
def call sms; ...; end
end
response, logs = sms_order_placer.call message

expect(logs.count).to be > 2
expect(logs.first).to eq "Received new order from ..."

SMS

ParseResult

Order

LE[ParseResult]

LE[Order]

Order

Order

LE[Order]

LE[Order]

LE[String]

## Example - Logging

-- LE[Type] = (Either Type String, [String])
parse_message  :: SMS         -> LE[ParseResult]
build_order    :: ParseResult -> LE[Order]
validate_order :: Order       -> LE[Order]
record         :: Order       -> LE[Order]
send_response  :: Order       -> LE[String]

sms_order_placer :: SMS -> LE[String]

## Example - Logging

class SMS::OrderPlacer
def success value, *messages
[Either.new(value, nil), messages]
end

# record :: Order -> [Either Order String, [String]]
def record order
if db.save_order(order)
success order, "Saved order with #{order.supplies.count} supplies"
else
error ...
end
end
end

## Example - Logging

class SMS::OrderPlacer
def compose *methods
->(val) do
methods.reduce([val, []]) do |result, m|
either, logs = result[0], result[1]
if either.error
result
else
func = m.respond_to?(:call) ? m : method(m)
new_value, new_logs = func.call either.value

[new_value, logs + new_logs]
end
end
end
end
end
• We can build error handling, logging, and more using pure functions and extended composition strategies
• None of the compose implementations used instance methods * ; each is a generic and reusable strategy
• Maybe.compose, Either.compose, ...

## Extending Composition

A

B

C

D

E

M[A]

M[B]

M[C]

M[D]

M[E]

• an A or an error object
• a list of A's
• a promise returning an A
• a probability distribution of A's
• writing to a stream and returning an A
• reading from a repo and returning an A

## Extending Composition

A

B

C

D

E

M[A]

M[B]

M[C]

M[D]

M[E]

• M is a functor
• "Moving up" is lift or map
• Exercise: convince yourself that List is a functor (and map is map)

## Extending Composition

A

B

C

D

E

M[A]

M[B]

M[C]

M[D]

M[E]

• Vertical lines are return or pure
• Diagram is commutative

## Extending Composition

A

B

C

D

E

M[A]

M[B]

M[C]

M[D]

M[E]

• M is a monad
• "Moving up" is bind or >>=
• Exercise: List is a monad (what is bind?)

A monad is just a monoid in the category of endofunctors, what's the problem?

A monad is like an assembly line of snails eating burritos

A monad is a way to extend a computation composably

## Parting Thoughts

• Sorry for writing another monad tutorial
• If you're interested in trying this style out on e.g. your service layer check out pzol/deterministic and dry-rb/dry-container
• Blog post and references to follow

How you compose is as important as what you're composing

Be explicit and intensional and it will be extensible

### James Dabbs

@jamesdabbs

github/jamesdabbs

By James Dabbs

# Composition

RubyConf 2016 talk on OO and FP and the nature of composition

• 2,752