Skip to content

Commit fe1b41d

Browse files
committed
Fixed challenge questions.
1 parent 66ed32d commit fe1b41d

File tree

5 files changed

+222
-36
lines changed

5 files changed

+222
-36
lines changed

README.md

+2-9
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
This is the base project for the workshop.
55

6-
__note__: please test your environment as soon as possible, but remember to grab the
7-
latest copy of this repository the day before the workshop with a `git pull origin master`.
6+
__note__: please test your environment before you arrive so we can get
7+
started quickly on the day.
88

99
### Core
1010

@@ -25,13 +25,6 @@ and scala syntax.
2525

2626
Implement the missing methods from the Result data type.
2727

28-
Result is a straight forward sum-type with three cases:
29-
- An explosion (i.e. an exception from somewhere), or
30-
- A failure message, or
31-
- A success value
32-
33-
It could be thought of as a newtype for: Exception \/ String \/ A.
34-
3528

3629
### Challenge 2
3730

src/main/scala/challenge1/Result.scala

+53-22
Original file line numberDiff line numberDiff line change
@@ -154,36 +154,67 @@ object Result {
154154

155155

156156
/*
157-
* *Challenge* Exercise 1.5:
157+
* *Challenge* Exercise 1.5: The worlds most trivial HTTP calculator.
158158
*
159-
* Given three methods which may succeed or fail:
160-
* - data
161-
* - count
162-
* - user
159+
* We are implementing a way to compute a number via a HTTP like
160+
* API.
161+
* - We will send a method which is one of GET|POST|PUT|DELETE.
162+
* - We want to send an integer as a request body.
163+
* - We will send a path of what calculation to use.
163164
*
164-
* If the user is valid:
165-
* - produce the string representing the user.
166-
* Otherwise, if the data and count are ok:
167-
* - produce a string of the data and count concatenated together
168-
* Otherwise:
169-
* - produce the string "bogus"
165+
* Complete the implementation, some of the methods are provided
166+
* with type signatures to get started.
170167
*/
171168
object Example {
172169

173-
case class Input(path: String, data: String, count: Int, user: String, auth: Boolean)
170+
/** Simplified method data type. */
171+
sealed trait Method
172+
case object Get extends Method
173+
case object Post extends Method
174+
case object Put extends Method
175+
case object Delete extends Method
174176

175-
/* Extract data if path is valid */
176-
def data(input: Input): Result[String] =
177-
if (input.path == "/valid") Result.ok(input.data) else Result.fail(NotFound)
177+
/*
178+
* Parse the method if it is valid, otherwise fail with InvalidRequest.
179+
*
180+
* Hint: Scala defines String#toInt, but warning it throws exceptions if it is not a valid Int :|
181+
*/
182+
def request(body: String): Result[Int] =
183+
???
178184

179-
/* Extract count iff we have greater than 0 */
180-
def count(input: Input): Result[Int] =
181-
if (input.count > 10) Result.ok(input.count) else Result.fail(InvalidRequest)
185+
/* Parse the method if it is valid, otherwise fail with InvalidMethod. */
186+
def method(method: String): Result[Method] =
187+
???
188+
189+
/*
190+
* Route method and path to an implementation.
191+
*
192+
* A minimal implementation is:
193+
* GET /single -> n * 1
194+
* GET /double -> n * 2
195+
* GET /triple -> n * 3
196+
* PUT * -> Unauthorized
197+
* POST * -> Unauthorized
198+
* DELETE * -> Unauthorized
199+
* * -> NotFound
200+
*/
201+
def route(method: Method, path: String): Result[Int => Int] =
202+
???
182203

183-
/* Extract user if it authorized */
184-
def user(input: Input): Result[String] =
185-
if (input.auth) Result.ok(input.user) else Result.fail(Unauthorized)
204+
/*
205+
* Attempt to compute an `answer`, by:
206+
* - determining method
207+
* - selecting implementation
208+
* - determing request value
209+
* - using the implementation and request value to compute an answer.
210+
*/
211+
def service(path: String, methodx: String, body: String): Result[Int] =
212+
???
186213

187-
def answer(input: Input) =
214+
/*
215+
* Sometimes we always an `answer`, so default to 0 if
216+
* our request failed in any way.
217+
*/
218+
def run(path: String, method: String, body: String): Int =
188219
???
189220
}

src/main/scala/challenge2/Reader.scala

+52-2
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,20 @@ object Reader {
7474
/*
7575
* Exercise 2.6:
7676
*
77-
* Implement monoid type class for Reader[R, A], given a monoid for A.
77+
* Sequence, a list of Readers, to a Reader of Lists.
7878
*/
79-
implicit def ReaderMonoid[R, A: Monoid]: Monoid[Reader[R, A]] =
79+
def sequence[R, A](readers: List[Reader[R, A]]): Reader[R, List[A]] =
8080
???
8181

82+
implicit def ReaderMonoid[R, A: Monoid]: Monoid[Reader[R, A]] =
83+
new Monoid[Reader[R, A]] {
84+
def zero: Reader[R, A] =
85+
value[R, A](Monoid[A].zero)
86+
87+
def append(a: Reader[R, A], b: => Reader[R, A]) =
88+
for { aa <- a; bb <- b } yield Monoid[A].append(aa, bb)
89+
}
90+
8291

8392
class Reader_[R] {
8493
type l[a] = Reader[R, a]
@@ -93,3 +102,44 @@ object Reader {
93102
r flatMap f
94103
}
95104
}
105+
106+
107+
/*
108+
* *Challenge* Exercise 2.7: Indirection.
109+
*
110+
* Lookup a specified config value, and then use its values
111+
* as keys to look up a subsequent set of values.
112+
*
113+
* Complete the implementation, some of the methods are provided
114+
* fill in the remainder, to complete the spec.
115+
*/
116+
object Example {
117+
case class ConfigEntry(name: String, values: List[String])
118+
case class Config(data: List[ConfigEntry])
119+
120+
/*
121+
* For a single name, lookup all of the direct values for that name.
122+
*
123+
* Libraries available:
124+
* - The Reader.* libraries
125+
* - List[A] has `find` method that will provide a Option[A]
126+
* - Option[A] has a `getOrElse` method similar to challenge1.Result
127+
*
128+
* Hint: Starting with Reader.ask will help.
129+
*/
130+
def direct(name: String): Reader[Config, List[String]] =
131+
???
132+
133+
/*
134+
* For a single name, lookup all of the indirect values, that
135+
* is those values whose key is a one of the direct values of
136+
* the specified name.
137+
*
138+
* Libraries available:
139+
* - List[List[A]].flatten will produce a List[A].
140+
*
141+
* Hint: Starting with Reader.sequence will be important.
142+
*/
143+
def indirect(name: String): Reader[Config, List[String]] =
144+
???
145+
}

src/main/scala/challenge3/Writer.scala

+85-3
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ import core._, Syntax._
88
*/
99
case class Writer[W, A](log: W, value: A) {
1010

11+
def run: (W, A) =
12+
(log, value)
13+
1114
/*
1215
* Exercise 3.1:
1316
*
@@ -51,15 +54,20 @@ object Writer {
5154
*
5255
* Implement tell.
5356
*
54-
* Tell produces the writer content w and produces no value.
57+
* Tell appends the writer content w and produces no value.
5558
*
5659
* Hint: Try using Writer constructor.
5760
*/
5861
def tell[W](w: W): Writer[W, Unit] =
5962
???
6063

61-
def writer[W, A](a: A)(w: W): Writer[W, A] =
62-
Writer(w, a)
64+
/*
65+
* Exercise 3.5:
66+
*
67+
* Sequence, a list of Readers, to a Reader of Lists.
68+
*/
69+
def sequence[W: Monoid, A](writers: List[Writer[W, A]]): Writer[W, List[A]] =
70+
???
6371

6472
class Writer_[W] {
6573
type l[a] = Writer[W, a]
@@ -81,3 +89,77 @@ object Writer {
8189
Writer(Monoid[W].append(l.log, r.log), Monoid[A].append(l.value, r.value))
8290
}
8391
}
92+
93+
/*
94+
* *Challenge* Exercise 3.6: Stocks + Stats.
95+
*
96+
* We have some stock prices over time, and we make a simple
97+
* adjustment:
98+
* Map across each ticker price and do an adjustment by adding
99+
* 1000 cents to every value under 10000 and 10 cents to every
100+
* value equal to or over 10000.
101+
*
102+
* However, while we compute this answer we also want to caculate
103+
* summary statistics for our data, specifically, min, max, total,
104+
* and count.
105+
*
106+
* Use the Writer data type to compute stats whilst we calculate
107+
* our adjustments.
108+
*
109+
* Complete the implementation, some of the methods are provided
110+
* fill in the remainder, to complete the spec.
111+
*/
112+
object Example {
113+
case class Stats(min: Int, max: Int, total: Int, count: Int)
114+
case class Stock(ticker: String, date: String, cents: Int)
115+
116+
/**
117+
* Implement our algorthim.
118+
*
119+
* Hint: Writer(W, A) and Writer.sequence will be useful here.
120+
*/
121+
def stocks(data: List[Stock]): (Stats, List[Stock]) =
122+
???
123+
124+
/**
125+
* A monoid for Stats.
126+
*/
127+
implicit def StatsMonoid: Monoid[Stats] =
128+
new Monoid[Stats] {
129+
def zero =
130+
???
131+
def append(l: Stats, r: => Stats) =
132+
???
133+
}
134+
135+
def exampledata = List(
136+
Stock("FAKE", "2012-01-01", 10000)
137+
, Stock("FAKE", "2012-01-02", 10020)
138+
, Stock("FAKE", "2012-01-03", 10022)
139+
, Stock("FAKE", "2012-01-04", 10005)
140+
, Stock("FAKE", "2012-01-05", 9911)
141+
, Stock("FAKE", "2012-01-06", 6023)
142+
, Stock("FAKE", "2012-01-07", 7019)
143+
, Stock("CAKE", "2012-01-01", 1)
144+
, Stock("CAKE", "2012-01-02", 2)
145+
, Stock("CAKE", "2012-01-03", 3)
146+
, Stock("CAKE", "2012-01-04", 4)
147+
, Stock("CAKE", "2012-01-05", 5)
148+
, Stock("CAKE", "2012-01-06", 6)
149+
, Stock("CAKE", "2012-01-07", 7)
150+
, Stock("BAKE", "2012-01-01", 99999)
151+
, Stock("BAKE", "2012-01-02", 99999)
152+
, Stock("BAKE", "2012-01-03", 99999)
153+
, Stock("BAKE", "2012-01-04", 99999)
154+
, Stock("BAKE", "2012-01-05", 99999)
155+
, Stock("BAKE", "2012-01-06", 99999)
156+
, Stock("BAKE", "2012-01-07", 99999)
157+
, Stock("LAKE", "2012-01-01", 10012)
158+
, Stock("LAKE", "2012-01-02", 7000)
159+
, Stock("LAKE", "2012-01-03", 1234)
160+
, Stock("LAKE", "2012-01-04", 10)
161+
, Stock("LAKE", "2012-01-05", 6000)
162+
, Stock("LAKE", "2012-01-06", 6099)
163+
, Stock("LAKE", "2012-01-07", 5999)
164+
)
165+
}

src/test/scala/challenge1/Challenge1Spec.scala

+30
Original file line numberDiff line numberDiff line change
@@ -35,4 +35,34 @@ object Challenge1Spec extends test.Spec {
3535
"fold with ok" ! prop((i: Int) =>
3636
Result.ok[Int](i).fold(_ => false, _ === i))
3737
}
38+
39+
"Example" should {
40+
"single" ! {
41+
Example.service("/single", "GET", "3") must_== Result.ok(3)
42+
}
43+
"double" ! {
44+
Example.service("/double", "GET", "5") must_== Result.ok(10)
45+
}
46+
"triple" ! {
47+
Example.service("/triple", "GET", "9") must_== Result.ok(27)
48+
}
49+
"handle invalid method" ! {
50+
Example.service("/triple", "INVALID", "9") must_== Result.fail(InvalidMethod)
51+
}
52+
"handle invalid path" ! {
53+
Example.service("/oops", "GET", "9") must_== Result.fail(NotFound)
54+
}
55+
"handle invalid request" ! {
56+
Example.service("/triple", "GET", "huh?") must_== Result.fail(InvalidRequest)
57+
}
58+
"handle unathorized method" ! {
59+
Example.service("/single", "POST", "8") must_== Result.fail(Unauthorized)
60+
}
61+
"default to 0 in failure" ! {
62+
Example.run("oops", "GET", "1") must_== 0
63+
}
64+
"not default to 0 in success" ! {
65+
Example.run("/double", "GET", "3") must_== 6
66+
}
67+
}
3868
}

0 commit comments

Comments
 (0)