Sathish Kumar
Flipkart
public List<Foo> getFooList(User user){
return fooList.stream()
.filter(createdBy(user))
.filter(isActive())
.orderBy(createdDate())
.collect(toList());
}
Expectation
Reality
public List<Foo> getFooList(User user){
List<Foo> fooList = new ArrayList<Foo>();
List<Baz> bazList = BazStore.getAll();
for (Baz baz : bazList) {
if(baz.isActive()) {
for (Bar bar : Bar.getAll(baz)) {
if(bar.isCreatedByUser(user)){
for (Foo foo : Foo.getAll(bar)) {
fooList.add(foo);
}
}
}
} else {
fooList.add(Foo.getDefault(baz));
}
}
return fooList;
}
class Book < ApplicationRecord
validates :type, inclusion: { in: %w(paperback hardcover ebook)
after_create :index_by_isbn
end
Expectation
Reality
book.save
nil
puts "#" * 100 ; p book
####################################################################################################
#<Book:0x007fb181077e40 @isbn="123456789", @id=nil>
Expectation
Reality
angular.module('user1', [])
.controller('UserController', function UserController() {
this.username;
this.email;
this.login = function login(email, password) {
return this.login(this.email, this.password, captcha());
};
});
of statically typed functional languages
qsort [] = []
qsort (x:xs) = qsort small ++ [x] ++ qsort large
where small = [y | y <- xs, y <= x]
large = [y | y <- xs, y > x]
QuickSort.hs
of dynamically typed functional languages
(define (fibgen a b)
(cons-stream a (fibgen b (+ a b))))
(define fibs (fibgen 0 1))
(take 10000 fibs)
FibonacciInfinite.scm
Common Arguments for not using a Functional language:
Nobody understands
monads in Haskell
A language that doesn't affect the way you think about programming, is not worth knowing."
- Alan J. Perlis
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
App
Desktop
Exchange
Functional concept introduced for the first time
# foo
foo :: String -> String
foo x = "Hello " ++ x
public String foo(String name){
return "Hello " + name;
}
Haskell code example
Java code example
# Type constructors
# Type aliases
# Data constructors
using type and data constructors
type Id = Int
type Price = Double
data PriceType = CPM | CPV | CPC deriving (Eq, Show)
data Advertiser = Advertiser Id deriving (Eq, Show)
data Campaign = Campaign Id Advertiser PriceType Price deriving (Eq,Show)
data Banner = Banner Id Campaign deriving (Eq, Show)
data Slot = Slot Id
data SlotGroup = SlotGroup Id [Slot]
data User = UnknownUser | User Id
type CTR = Double
type Score = Double
data BannerScore = BannerScore Banner Score
type ECPM = Double
@AllArgsConstructor
@EqualsAndHashCode
@ToString
class Advertiser {
Integer id;
}
@AllArgsConstructor
@EqualsAndHashCode
@ToString
class Campaign {
Integer id;
Advertiser advertiser;
}
@AllArgsConstructor
@EqualsAndHashCode
@ToString
class Banner {
Integer id;
Campaign campaign;
}
1. Active
2. Slot targeting
3. Gender targeting
relevantCampaigns :: Context -> Map[Slot, [Campaign]]
1. Active
2. Slot targeting
3. Gender targeting
isSlotTargeted = \campaign, context ->
getSlot campaign == getSlot context
isActive = \campaign -> getActive campaign
isGenderTargeted = \campaign, context ->
case (getGender campaign) of
Nothing -> True
Just (gender) -> gender == getGender (getUser context)
# Type signature
# Lambdas
# Maybe / Optional
Predicate<Campaign> isActive() {
return campaign -> campaign.isActive();
}
Predicate<Campaign> isSlotTargeted(Context context) {
return campaign -> campaign.getSlot().equals(context.getSlot());
}
Predicate<Campaign> isGenderTargeted(Context context) {
return campaign -> context.getUser()
.map(gender -> gender.equals(campaign.getGender()))
.orElse(true);
}
List<Campaign> getRelevantCampaigns(Context context){
return campaignStore.getAll().stream()
.filter(isActive())
.filter(isSlotTargeted(context))
.filter(isGenderTargeted(context))
.collect(toList());
}
# Streams
# Filter
Predicate<Campaign> getFilters(Context context){
return isActive()
.and(isSlotTargeted(context))
.and(isGenderTargeted(context));
}
List<Campaign> getRelevantCampaigns(Context context){
return campaignStore.getAll().stream()
.filter(getFilters(context))
.collect(toList());
}
Adding a new targeting feature is now as easy as adding another predicate to this list
andAll :: [Campaign -> Bool] -> Campaign -> Bool
andAll predicates campaign = foldr (&&) True (map (\p -> p campaign) predicates)
# Folds / Reduce
1. Matches template
(OR)
2. Matches width and height
relevantBanners :: Map[Slot, [Campaign]] -> Map[Slot, [Banner]]
1. Matches template
(OR)
2. Matches width and height
forTemplate = \banner, context ->
getTemplate banner == getTemplate context
forDimension = \banner, context ->
getWidth banner == getWidth context &&
getHeight banner == getHeight context
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());
}
orAll :: [a -> Bool] -> a -> Bool
orAll predicates x = foldr (||) False (map (\p -> p x) predicates)
[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
What is the common pattern here?
All of these are binary operations which are associative with Identity
Monoid is an abstraction of this common pattern
# Monoid
Any
mconcat [Any False, Any False] == Any False
mconcat [Any False, Any True] == Any True
All
mconcat [All True, All False] == All False
mconcat [All True, All True] == All True
Monoid: if you define identity and append, you get concat for free
class Monoid m where
mempty :: m
mappend :: m -> m -> m
mconcat :: [m] -> m
mconcat = foldr mappend mempty
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. tryerlang.org (score: 0.85)
2. tryhaskell.org (score: 0.75)
3. tryclj.com (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]]
public static <K,A,B> 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, scorer())
public Map<Slot, List<BannerScore>> rankBanners(Map[Slot, List[Banner]] relevantBanners){
return mapValues(relevantBanners, scorer());
}
Function<Slot, List<BannerScore>> scorer(){
return slot -> {
List<Price> prices = getPrices(banners);
List<CTR> ctrs = getCTRs(banners);
return StreamUtils.zip(
prices.stream(),
ctrs.stream(),
(price, ctr) -> new BannerScore(0.5 * price.getValue() + 0.5 * ctr.getValue()))
.collect(toList());
}
}
mapValues takes function as argument
scorer returns function as output
# First class functions
# zip / zipWith
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 randomly?
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
LT `mappend` _ = LT
EQ `mappend` y = y
GT `mappend` _ = GT
score1 compare score2 <>
random1 compare random2
Handling equal scores
1. Logistic Regression Model
2. Input: Supply, Demand, User features
3. 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. Categories : category:books (0.004), category:mobiles (0.005), category:default (-0.005)
z = - 5.0 + 0.001 + 0.002 + 0.003 - 0.005
pCTR = 0.7%
# Optional map
# Optional flatMap
genderFeature :: Context -> Feature
genderFeature context = case (getGender (getUser context)) of
Just(value) -> Feature "gender:" ++ value
Nothing -> Feature "gender:default"
categoryFeature :: Context -> [Feature]
categoryFeature context = case (getCategories (getUser context)) of
[] -> [Feature "cat:default"]
values -> map (\value -> Feature "cat:" ++ value) values
# Optional orElse
# Null safety
Feature genderFeature = context.getUser().getGender()
.map(value -> new Feature("gender:" + value))
.orElse(new Feature("gender:default"))
Feature genderFeature = context.getUser()
.flatMap(user -> user.getGender())
.map(value -> new Feature("gender:" + value))
.orElse(new Feature("gender:default"))
Single valued features
Multi valued features
req1 = Context {slot=1, user=Nothing}
req2 = Context {slot=1, user=Just User{gender="male"}}
generateFeature "gender" (gender (user req2)) -- won't work
generateFeature "gender" (fmap gender (user req1)) -- "gender:default"
generateFeature "gender" (fmap gender (user req2)) -- "gender:male"
generateFeature :: String -> Maybe String -> String
generateFeature fname Nothing = fname ++ ":default"
generateFeature fname (Just fvalue) = fname ++ ":" ++ fvalue
Functors apply a function to a value in a box
*
# Functors
instance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
User::Maybe[User]
Gender::User -> String
*
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
# 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());
}
1. Serve unique ads across multiple slots
2. Uniqueness can be by advertiser or by campaign
Map<Advertiser, List<Banner>> bannersByAdvertiser = banners.values()
.stream()
.collect(groupingBy(b -> b.getCampaign().getAdvertiser()));
boolean isDedupRequired(Map<Slot, Banner> banners){
Map<Advertiser, List<Banner>> bannersByAdvertiser = banners.values()
.stream()
.collect(groupingBy(b -> b.getCampaign().getAdvertiser()));
return bannersByAdvertiser.size() != banners.size();
}
# groupBy
dedupBanners :: Map[Slot, [BannerScore]] -> Map[Slot, Maybe[Banner]]
count(advertisers) != count(banners)
Dedup options:
1. None 2. By Advertiser 3. By Campaign
dedupCriteria = Nothing
dedupCriteria = Just (\banner -> (campaign banner))
dedupCriteria = Just (\banner -> (advertiser (campaign banner)))
# Applicative
instance Applicative Maybe where
Nothing <*> _ = Nothing
(Just f) <*> something = fmap f something
Applicatives apply function in a box
to a value in a box.
*
A/B test to find effectiveness of Banner
A | B |
---|---|
1% | 1.5% |
splitBanners :: Map[Slot, [Banner]] -> Map[Slot, [Banner]]
allBanners = banners.values().stream()
.flatMap(bs -> bs.stream())
.collect(toList());
abService.getControl(context.getUser(), allBanners)
abService.getTreatment(context.getUser(), allBanners)
# flatMap
Feature genderFeature = context.getUser()
.flatMap(user -> user.getGender())
.map(value -> new Feature("gender:" + value))
.orElse(new Feature("gender:default"))
flatMap on Optional
Gender feature
banners.values().stream()
.flatMap(bs -> bs.stream())
.filter(banner -> abService.isA(user, banner))
.collect(toList());
Attribution
flatMap on Stream
What is the common pattern here?
What if we use map instead of flatMap here?
context = Context {slot=1, user=Just User{gender=Just "male"}}
fmap gender (user context)
-- Just (Just "male")
(user context) >>= gender
-- Just "male"
user :: Context -> Maybe User
gender :: User -> Maybe String
-- fmap (User -> Maybe String) (Maybe User) :: Maybe (Maybe User)
# Monads
Gender feature
Monad is an abstraction of this common pattern
(>>=) :: Monad m => ma -> (a -> mb) -> mb
(>>=) :: Maybe a -> (a -> Maybe b) -> Maybe b
(>>=) :: Maybe User -> (User -> Maybe Gender) -> Maybe Gender
*
Monads apply a function that returns a value in a box to a value in a box
user::Context -> Maybe[User]
gender::User -> Maybe[String]
# Function composition
Workflow is nothing but function composition
compose :: (b -> c) -> (a -> b) -> a -> c
relevantCampaigns :: Context -> [Campaign]
relevantBanners :: [Campaign] -> [Banner]
splitBanners :: [Banner] -> [Banner]
rankBanners :: [Banner] -> [BannerScore]
dedupBanners :: [BannerScore] -> Maybe[Banner]
selectAds :: Context -> Maybe[Banner]
selectAds = dedupBanners . rankBanners . splitBanners . relevantBanners . relevantCampaigns
@sathish316
Sathish Kumar
Flipkart