We can't find the internet
Attempting to reconnect
Something went wrong!
Hang in there while we get back on track
Act on It
Patron expert wants to set order based on reward points. Implement that.
### Patron.ex
def get_reward_points(%__MODULE__{reward_points: reward_points}),
do: reward_points
def ord_by_reward_points do
Ord.Utils.contramap(&get_reward_points/1)
end
Now let’s test it
iex(26)> ord_points = FunPark.Patron.ord_by_reward_points()
%{
lt?: #Function<1.94864313/2 in FunPark.Ord.Utils.contramap/2>,
ge?: #Function<4.94864313/2 in FunPark.Ord.Utils.contramap/2>,
gt?: #Function<3.94864313/2 in FunPark.Ord.Utils.contramap/2>,
le?: #Function<2.94864313/2 in FunPark.Ord.Utils.contramap/2>
}
iex(27)> guest_1 = FunPark.Patron.make("jim", 13, 70)
%FunPark.Patron{
id: 3970,
name: "jim",
age: 13,
height: 70,
ticket_tier: :basic,
fast_passes: [],
reward_points: 0,
likes: [],
dislikes: []
}
iex(28)> guest_1 = FunPark.Patron.change(guest_1, %{reward_points: 200})
%FunPark.Patron{
id: 3970,
name: "jim",
age: 13,
height: 70,
ticket_tier: :basic,
fast_passes: [],
reward_points: 200,
likes: [],
dislikes: []
}
iex(29)> guest_2 = FunPark.Patron.make("sal", 20, 50)
%FunPark.Patron{
id: 4034,
name: "sal",
age: 20,
height: 50,
ticket_tier: :basic,
fast_passes: [],
reward_points: 0,
likes: [],
dislikes: []
}
iex(30)> guest_2 = FunPark.Patron.change(guest_2, %{reward_points: 300})
%FunPark.Patron{
id: 4034,
name: "sal",
age: 20,
height: 50,
ticket_tier: :basic,
fast_passes: [],
reward_points: 300,
likes: [],
dislikes: []
}
iex(33)> ord_points.gt?.(guest_1, guest_2)
false
iex(34)> ord_points.lt?.(guest_1, guest_2)
true
You can apply the same pattern to any field on the struct. the contramap/1 call simply
wraps whatever extractor you give it and returns a map of comparison functions. Try
swapping the accessor to see different orderings:
### more helpers in Patron.ex
def get_name(%__MODULE__{name: name}), do: name
def get_age(%__MODULE__{age: age}), do: age
def ord_by_name do
Ord.Utils.contramap(&get_name/1)
end
def ord_by_age do
Ord.Utils.contramap(&get_age/1)
end
Now experiment in iex:
iex> alice = FunPark.Patron.make("Alice", 15, 50, ticket_tier: :premium)
iex> beth = FunPark.Patron.make("Beth", 16, 53)
iex> name_ord = FunPark.Patron.ord_by_name()
iex> age_ord = FunPark.Patron.ord_by_age()
iex> points_ord = FunPark.Patron.ord_by_reward_points()
iex> name_ord.lt?.(alice, beth) # alphabetical: "Alice" < "Beth"
true
iex> age_ord.lt?.(alice, beth) # 15 < 16
true
iex> points_ord.lt?.(alice, beth) # 0 < 0 (initially equal)
false
iex> beth = FunPark.Patron.change(beth, %{reward_points: 100})
iex> points_ord.lt?.(alice, beth) # now 0 < 100
true
Swapping the extractor function (get_name/1, get_age/1, get_reward_points/1,
or any other field) gives you a completely different ordering without touching the
core protocol at all. That’s the power of contramap/1!
Ride expert want to set an order for rides that will be based off of wait time.
def get_wait_time(%__MODULE__{wait_time: wait_time}), do: wait_time
def ord_by_wait_time do
Ord.Utils.contramap(&get_wait_time/1)
end
That is the same thing we just did now we can leverage that to test. So long as we implement the right anonymous function.
iex(35)> ord_wait_time = FunPark.Ride.ord_by_wait_time()
%{
lt?: #Function<1.94864313/2 in FunPark.Ord.Utils.contramap/2>,
ge?: #Function<4.94864313/2 in FunPark.Ord.Utils.contramap/2>,
gt?: #Function<3.94864313/2 in FunPark.Ord.Utils.contramap/2>,
le?: #Function<2.94864313/2 in FunPark.Ord.Utils.contramap/2>
}
iex(37)> fast_ride = FunPark.Ride.make("fast ride") |> FunPark.Ride.change(%{wait_time: 30})
%FunPark.Ride{
id: 4290,
name: "fast ride",
min_age: 0,
min_height: 0,
wait_time: 30,
online: true,
tags: []
}
iex(38)> slow_ride = FunPark.Ride.make("slow ride") |> FunPark.Ride.change(%{wait_time: 60})
%FunPark.Ride{
id: 4482,
name: "slow ride",
min_age: 0,
min_height: 0,
wait_time: 60,
online: true,
tags: []
}
iex(39)> ord_wait_time.lt?.(slow_ride, fast_ride)
false
iex(40)> ord_wait_time.gt?.(slow_ride, fast_ride)
true
Using contramap/1 and reverse/1 how would you sort patrons with the most points first?
iex(68)> ord_points = FunPark.Patron.ord_by_reward_points()
%{
lt?: #Function<1.94864313/2 in FunPark.Ord.Utils.contramap/2>,
ge?: #Function<4.94864313/2 in FunPark.Ord.Utils.contramap/2>,
gt?: #Function<3.94864313/2 in FunPark.Ord.Utils.contramap/2>,
le?: #Function<2.94864313/2 in FunPark.Ord.Utils.contramap/2>
}
iex(69)> rev_ord_points = FunPark.Ord.Utils.reverse(ord_points)
%{
lt?: #Function<3.94864313/2 in FunPark.Ord.Utils.contramap/2>,
ge?: #Function<2.94864313/2 in FunPark.Ord.Utils.contramap/2>,
gt?: #Function<1.94864313/2 in FunPark.Ord.Utils.contramap/2>,
le?: #Function<4.94864313/2 in FunPark.Ord.Utils.contramap/2>
}
iex(70)> lowest = FunPark.Patron.make("low", 20, 30) |> FunPark.Patron.change(%{reward_points: 10})
%FunPark.Patron{
id: 462,
name: "low",
age: 20,
height: 30,
ticket_tier: :basic,
fast_passes: [],
reward_points: 10,
likes: [],
dislikes: []
}
iex(71)> highest = FunPark.Patron.make("high", 20, 30) |> FunPark.Patron.change(%{reward_points: 30})
%FunPark.Patron{
id: 590,
name: "high",
age: 20,
height: 30,
ticket_tier: :basic,
fast_passes: [],
reward_points: 30,
likes: [],
dislikes: []
}
iex(72)> FunPark.List.sort([lowest, highest], rev_ord_points)
[
%FunPark.Patron{
id: 590,
name: "high",
age: 20,
height: 30,
ticket_tier: :basic,
fast_passes: [],
reward_points: 30,
likes: [],
dislikes: []
},
%FunPark.Patron{
id: 462,
name: "low",
age: 20,
height: 30,
ticket_tier: :basic,
fast_passes: [],
reward_points: 10,
likes: [],
dislikes: []
}
]
For this I was able to leverage a few things to get this to work. First we needed to set the proper ordering for the key-value pairs and then choose which :atom to look for. Once that was done we used the List module to get the sort.
Implement in Ord min/2 and max/2 to return the values based on the Ord given
# FunPark.Ord.Utils
def max(a, b, ord \\ Ord) do
case compare(a, b, ord) do
:lt -> b
_ -> a
end
end
def min(a, b, ord \\ Ord) do
case compare(a, b, ord) do
:gt -> b
_ -> a
end
end
Now let’s test.
iex(73)> FunPark.Ord.Utils.min(lowest, highest, rev_ord_points)
%FunPark.Patron{
id: 590,
name: "high",
age: 20,
height: 30,
ticket_tier: :basic,
fast_passes: [],
reward_points: 30,
likes: [],
dislikes: []
}
iex(74)> FunPark.Ord.Utils.min(lowest, highest, ord_points)
%FunPark.Patron{
id: 462,
name: "low",
age: 20,
height: 30,
ticket_tier: :basic,
fast_passes: [],
reward_points: 10,
likes: [],
dislikes: []
}
iex(75)> FunPark.Ord.Utils.max(lowest, highest, ord_points)
%FunPark.Patron{
id: 590,
name: "high",
age: 20,
height: 30,
ticket_tier: :basic,
fast_passes: [],
reward_points: 30,
likes: [],
dislikes: []
}
iex(76)> FunPark.Ord.Utils.max(lowest, highest, rev_ord_points)
%FunPark.Patron{
id: 462,
name: "low",
age: 20,
height: 30,
ticket_tier: :basic,
fast_passes: [],
reward_points: 10,
likes: [],
dislikes: []
}
We used the same Patrons as we just made.
Create a macro for Eq
defmacro eq_for(for_struct, field) do
quote do
alias FunPark.Eq
defimpl FunPark.Ord, for: unquote(for_struct) do
def eq?(
%unquote(for_struct){unquote(field) => v1},
%unquote(for_struct){unquote(field) => v2}
),
do: Eq.eq?(v1, v2)
def not_eq?(
%unquote(for_struct){unquote(field) => v1},
%unquote(for_struct){unquote(field) => v2},
),
do: Eq.not_eq?(v1, v2)
end
end
Now you can add this to any module and you can even inject the basic version of the code that you want. Ill give you the the injection and so you can comment out the defimpl within each Context.
# FunPark.Ride
import FunPark.Macros, only: [eq_for: 2, ord_for: 2]
...
ord_for(FunPark.Ride, :name)
eq_for(FunPark.Ride, :id)
# FunPark.Patron
import FunPark.Macros, only: [eq_for: 2, ord_for: 2]
...
ord_for(FunPark.Patron, :name)
eq_for(FunPark.Patron, :id)
# FaunPark.FastPass
import FunPark.Macros, only: [eq_for: 2, ord_for: 2]
...
eq_for(FunPark.FastPass, :id)
ord_for(FunPark.FastPass, :time)
Now we can test to make sure that they work.
iex(79)> FunPark.Eq.eq?(lowest, highest)
false
iex(80)> FunPark.Eq.eq?(lowest, lowest)
true
iex(81)> FunPark.Ord.lt?(lowest, lowest)
false
iex(83)> FunPark.Ord.lt?(highest, lowest)
true
iex(84)> FunPark.Ord.lt?(tea_cup, river_ride)
false
iex(85)> FunPark.Ord.gt?(tea_cup, river_ride)
true
iex(86)> FunPark.Eq.eq?(tea_cup, tea_cup)
true
iex(87)> FunPark.Eq.eq?(tea_cup, river_ride)
false
iex(89)> FunPark.Ord.lt?(fast_pass_1, fast_pass_2)
false
iex(90)> FunPark.Ord.gt?(fast_pass_1, fast_pass_2)
true
iex(91)> FunPark.Eq.eq?(fast_pass_1, fast_pass_2)
false
iex(92)> FunPark.Eq.eq?(fast_pass_1, fast_pass_1)
true