Double calculateTotalCashback(Order order){
Double totalCashback = 0.0;
for (OrderItem orderItem : order.getOrderItems()) {
if(orderItem.hasOffer() &&
orderItem.getOffer().getCashback() > 0.0) {
totalCashback += orderItem.getOffer().getCashback();
}
}
return totalCashback;
}Mutable State
Reasoning about code - How vs What
Lock based concurrency
class Book < ApplicationRecord
attr_reader :isbn, :title, :author
after_create :index_by_isbn
end
puts book.author
puts chapter.authorType safety
Writing tests for compiler checks
Quick Sort in Haskell
qsort [] = []
qsort (x:xs) = qsort small ++ [x] ++ qsort large
where small = [y | y <- xs, y <= x]
large = [y | y <- xs, y > x]No side effects (or) state changes
Type safety and Type inference
Immutable data - Multicore concurrency
Pivot
Larger than Pivot
Smaller than Pivot
Infinite Fibonacci in Lisp
Lazy evaluation
Functions are composable
(define (fib a b)
(cons-stream a (fib b (+ a b))))
(take 10 (filter isEven fib))Infinite recursion
Higher order functions
Why functional programming matters - John Hughes
Twitter uses Scala for backend services
Haskell at Standard Chartered
Ocaml at Jane Street
Wall street's secret sauce
WhatsApp: Scaling to Billions of messages in Erlang
Facebook: Fighting Spam with Haskell
Java 8: Functional features
No matter what language you work in, programming in a functional style provides benefits. You should do it whenever it is convenient, and you should think hard about the decision when it isn't convenient"
- John Carmack (Creator of Quake, Doom, Wolf)
Imperative / OOP
Functional
Math
Java
Python/Ruby
Javascript
Scala
Lisp
Haskell
Lambda Calculus
Category Theory
A language that doesn't affect the way you think about programming, is not worth knowing."
- Alan J. Perlis
App
Desktop
Exchange
| Campaign | Impressions | Clicks |
|---|---|---|
| 101 | 100,000 | 1000 |
| 102 | 200,000 | 2000 |
Functional concept
# foo
foo :: String -> String
foo x = "Hello " ++ xpublic String foo(String name){
return "Hello " + name;
}Haskell code example
Java code example
# Type
# Type alias
type Price = Double
type CTR = Double
data PriceType = CPM | CPV | CPC
type Id = Int
type Score = Double
data Advertiser = Advertiser Id
data Campaign = Campaign Id Advertiser PriceType Price
data Banner = Banner Id Campaigndata Slot = Slot Id
data User = UnknownUser | User Id
data SlotGroup = SlotGroup Id [Slot]
Campaign, Banner attrs
Advertiser, Campaign, Banner
Slot, User
constructors
1. Active
2. Gender targeting
relevantCampaigns :: Context -> Map[Slot, [Campaign]]
1. Active
2. Gender targeting
isActive = \campaign -> isActive campaignisGenderTargeted = \campaign, context ->
case (getGender campaign) of
Nothing -> True
Just (gender) -> gender == getGender (getUser context)# Type signature
# Lambdas
Predicate<Campaign> isActive = campaign -> campaign.isActive();
Predicate<Campaign> isGenderTargeted(Context context) {
return campaign -> context.getUser()
.map(gender -> gender.equals(campaign.getGender()))
.orElse(true);
}# Predicate
List<Campaign> getRelevantCampaigns(Context context){
return campaignStore.getAll().stream()
.filter(isActive())
.filter(isGenderTargeted(context))
.collect(toList());
}# Streams
# Filter
# Function
Predicate<Campaign> getRelevanceFilters(Context context){
return isActive()
.and(isGenderTargeted(context))
.and(isFrequencyCapped(context));
}
Adding a new relevance rule is
as simple as adding a predicate
composition
1. Matches template
2. Matches width and height
relevantBanners :: Map[Slot, [Campaign]] -> Map[Slot, [Banner]]
1. Matches template
Predicate<Banner> forTemplate(Context context){
return banner -> banner.getTemplate().equals(context.getTemplate());
}2. Matches width and height
Predicate<Banner> forDimension(Context context){
return banner -> banner.getWidth().equals(context.getWidth()) &&
banner.getHeight().equals(context.getHeight());
}forTemplate = \banner, context ->
getTemplate banner == getTemplate contextforDimension = \banner, context ->
getWidth banner == getWidth context &&
getHeight banner == getHeight contextPredicate<Banner> getFilters(Context context){
return forTemplate(context)
.or(forDimension(context));
}
List<Banner> getRelevantBanners(Context context, List<Campaign> campaigns){
return bannerStore.getAll(campaigns).stream()
.filter(getFilters(context))
.collect(toList());
}
[1,2,3] ++ [] == [1,2,3]
[1,2] ++ ([3,4] ++ [5,6]) == ([1,2] ++ [3,4]) ++ [5,6]
[] ++ [1,2,3] == [1,2,3]
List
Sum
5 + 0 == 5
(1 + 2) + 3 == 1 + (2 + 3)
0 + 5 == 5
Product
5 * 1 == 5
(1 * 2) * 3 == 1 * (2 * 3)
1 * 5 == 5
False && True == False
(True && True) && False == True && (True && False)
True && False == False
Boolean AND
True || False == True
(True || True) || False == True || (True || False)
False || True == True
Boolean OR
All of these are binary operations with Identity and Associativity
Monoid is an abstraction of this common pattern
# Monoid
Any
mconcat [Any False, Any False] == Any False
mconcat [Any False, Any True] == Any TrueAll
mconcat [All True, All False] == All False
mconcat [All True, All True] == All TrueMonoid: if you define identity and append, you get concat for free
isRelevant campaign = mconcat (map (\f -> f campaign) filters)
isRelevant banner = mconcat (map (\f -> f banner) filters)With these abstractions, Relevance simplifies to:
A/B test to find effectiveness of Banner
| A (control) | B | |
|---|---|---|
| Conversion | 0.5% | 1.5% |
splitBanners :: Map[Slot, [Banner]] -> Map[Slot, [Banner]]
allBanners = slotBanners.values().stream()
.flatMap(bs -> bs.stream())
.collect(toList());
abService.getControl(context.getUser(), allBanners)
abService.getTreatment(context.getUser(), allBanners)
# flatMap
flatMap on Stream
1. Score banners by criteria
2. Sort banners by score
1. Samsung (score: 0.85)
2. Sony (score: 0.75)
3. BPL (score: 0.65)
rankBanners :: Map[Slot, [Banner]] -> Map[Slot, [BannerScore]]
1. Score banners by criteria
2. Sort banners by score
What is the common pattern here?
relevantBanners :: Map[Slot, [Campaign]] -> Map[Slot, [Banner]]
rankBanners :: Map[Slot, [Banner]] -> Map[Slot, [BannerScore]]
Map<K, B> mapValues(Map<K, A> input, Function<A, B> transformer) {
return input.keySet().stream()
.collect(toMap(Function.identity(), transformer));
}
# Higher order
functions
mapValues(relevantBanners, bannerScorer())
public Map<Slot, List<BannerScore>> rankBanners(Map[Slot, List[Banner]] relevantBanners){
return mapValues(relevantBanners, scorer());
}
Function<Slot, List<BannerScore>> bannerScorer(){
return slot -> {
List<Price> prices = getPrices(banners);
List<CTR> ctrs = getCTRs(banners);
return StreamUtils.zip(
prices.stream(),
ctrs.stream(),
(price, ctr) -> calculate(price, ctr))
.collect(toList());
}
}
mapValues takes function as argument
bannerScorer returns function as output
# First class
functions
# zip
1. tryerlang.org (score: 0.90)
2. tryhaskell.org (score: 0.90)
3. tryclj.com (score: 0.90)
What if the scores are equal?
How can we rotate equally?
compare(Banner b1, Banner b2){
if(b1.score().compare(b2.score()) != 0){
return b1.score().compare(b2.score())
}
return b1.random().compare(b2.random())
}instance Monoid Ordering where
mempty = EQ
GT `mappend` _ = GT
LT `mappend` _ = LT
EQ `mappend` y = y
score1 compare score2 <>
random1 compare random2Handling equal scores
Optional
1. CTR: Click Through Rate
2. Logistic Regression Model
3. Input: Supply, Demand, User features
4. Output: Predicted CTR
n features
θi - Model coefficients
Xi - Boolean value of features
Features
1. Slot : slot:123 (0.001) or slot:default (-0.005)
2. Campaign : campaign:123 (0.002) or campaign:default (-0.005)
3. Gender : gender:male (0.003) or gender:female (0.003) or gender:default (-0.005)
4. Stores : store:books (0.004), store:mobiles (0.005), store:default (-0.005)
z = - 5.0 + 0.001 + 0.002 + 0.003 - 0.005
pCTR = 0.7%
# Optional
# Null safety
Feature genderFeature = context.getUser().getGender()
.map(value -> new Feature("gender:" + value))
.orElse(new Feature("gender:default"))Single valued features
Optional [Gender]
1. user.gender is present - Just [Gender] - map
2. user.gender is null - Nothing - orElse
storeFeature :: Context -> [Feature]
storeFeature context = case (getStores (getUser context)) of
[] -> [Feature "store:default"]
values -> map (\value -> Feature "store:" ++ value) valuesMulti valued features
List<Feature> storeFeatures = context.getStores().isEmpty() ?
asList(new Feature("store:default")) :
context.getStores().stream().map(value -> new Feature("store:" + value))genderFeature :: Context -> Feature
genderFeature context = case (getGender (getUser context)) of
Just(value) -> Feature "gender:" ++ value
Nothing -> Feature "gender:default"Single valued features
Functors apply a function to a value in a box
*
# Functors
value -> new Feature("gender:" + value)
Gender
Feature
getGender() :: Maybe[Gender]
Optional[Gender]
"gender:female"
Feature
map
Java 8 Optional is a functor
*
N-armed bandit in a row of Slot machines
Arm - Options available
Reward - Success metrics
Exploitation - Choose highest reward
Exploration - Choose random reward
Explore/Exploit dilemma
Optional
# map
if (shouldExplore()){
return bannerExplorer(banners);
}
return bannerScorer(banners);public Function<Slot, List<BannerScore>> bannerExplorer(List<Banner> banners){
return slot -> banners.stream()
.map(b -> new BannerScore(b, random.nextDouble()))
.collect(toList());
}
Optional
Feature genderFeature = context.getUser()
.flatMap(user -> user.getGender())
.map(value -> new Feature("gender:" + value))
.orElse(new Feature("gender:default"))1. Optional [User]
user is present: flatMap
user is null: orElse
2. Optional [Gender]
user.gender is present: map
user.gender is null: orElse
# flatMap
# Monads
*
Monads apply a value in a box to a function that returns a value in a box
half()
map on Optional
flatMap on Optional
value -> new Feature("gender:" + value)
Gender
Feature
getGender() :: Maybe[Gender]
Optional[Gender]
"gender:female"
Feature
map
user -> user.getGender()
User
Optional[Gender]
getUser() :: Maybe[User]
Optional[User]
Optional[Gender]
flatMap
Java 8 Optional is a monad
1. Serve unique ads across multiple slots
2. Uniqueness can be by advertiser or campaign or banner
Dedup options:
1. None 2. By Advertiser 3. By Campaign 4. By Banner
dedupCriteria = Nothing
dedupCriteria = Just (\banner -> (advertiser (campaign banner)))
dedupCriteria = Just (\banner -> (campaign banner))
dedupCriteria = Just (\banner -> banner)dedupBanners :: Map[Slot, [BannerScore]] -> Map[Slot, Maybe[Banner]]
Dedup options:
1. None 2. By Advertiser 3. By Campaign 4. By Banner
dedupCriteria = Nothing
dedupCriteria = Just (\banner -> (advertiser (campaign banner)))
dedupCriteria = Just (\banner -> (campaign banner))
dedupCriteria = Just (\banner -> banner)# Applicative
Applicatives apply function in a box
to a value in a box.
*
# Function composition
Workflow is just function composition
compose :: (b -> c) -> (a -> b) -> a -> crelevantCampaigns :: Context -> [Campaign]
relevantBanners :: [Campaign] -> [Banner]
rankBanners :: [Banner] -> [BannerScore]
dedupBanners :: [BannerScore] -> Maybe[Banner]selectAds :: Context -> Maybe[Banner]
selectAds = dedupBanners . rankBanners . relevantBanners . relevantCampaigns