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
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());
}Predicate<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:
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. 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
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
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]]
# 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