Skip to content

Commit b5b5bfa

Browse files
author
Dylan Just
committed
Progress.
1 parent b52abfc commit b5b5bfa

2 files changed

Lines changed: 165 additions & 24 deletions

File tree

src/main/ts/Exercise3Optional.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,10 @@ const y: Optional<string> = Optional.none<number>().map((x) => String(x)); // re
152152

153153
// TODO: Write a function that takes an Optional<string> and prefixes the string with "hello"
154154

155-
// TODO: If the below function is called, does it return a value or throw an exception? Write your answer in a comment, and explain why you think it behaves this way?
155+
/*
156+
TODO: If the below function is called, does it return a value or throw an exception? Why should it behave one way or the other?
157+
Answer: ...
158+
*/
156159
const willItKersplode = (): Optional<string> => {
157160
const z = Optional.none<string>();
158161
return z.map<string>((s) => {

src/main/ts/Exercise4FP.ts

Lines changed: 161 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Option as Optional } from "@ephox/katamari";
1+
import { Fun, Option as Optional } from "@ephox/katamari";
22

33
/*
44
Functional programming is about programming in functions. Functions in the mathematical sense. "Pure" functions.
@@ -29,13 +29,75 @@ What are the benefits we're trying to get?
2929
the mathematical properties of functions. It turns out that math has ... to put it mildly ... rather a lot to say about functions.
3030
3131
Now, for the sake of time, I'm not going to do any more hard-sell on why it's important - lots has already been written
32-
on this topic. For the rest of this exercise, we're just going to look at some of the basic techniques and common
33-
functions we use.
32+
on this topic and can be found by searching the web. For the rest of this exercise, we're just going to look at some of the
33+
basic techniques and common functions we use.
3434
*/
3535

3636
/*
37-
Let's start with the simplest function there is: the identity function.
38-
It takes an argument and returns it.
37+
38+
Extracting side-effects.
39+
40+
In a lot of code, side-effects are mixed up with logic. The more we separate them, the more testable the actual logic is.
41+
42+
In the below (impure) function, we're printing some text for each branch of the optional.
43+
*/
44+
export const printMessage1 = (e: Optional<string>): void =>
45+
e.fold(
46+
() => {
47+
console.log("oop");
48+
},
49+
(s) => {
50+
console.log("The value was " + s);
51+
}
52+
);
53+
54+
/*
55+
The only way to test this would be to fake out the console somehow and capture its output, which can get pretty nasty.
56+
57+
Really, this function is doing two things:
58+
59+
1. calculating a string
60+
2. printing it
61+
62+
Calculating the string is a pure function! Let's extract it:
63+
*/
64+
65+
export const getMessage = (e: Optional<string>): string =>
66+
e.fold(
67+
() => "oop",
68+
(s) => "The value was " + s
69+
);
70+
71+
export const printMessage2 = (e: Optional<string>): void =>
72+
console.log(getMessage(e));
73+
74+
/*
75+
Now, printMessage2 is still tricky to test, but getMessage is very easy to test. We've improved the testability of our code.
76+
77+
TODO: Extract a pure function for the logic hiding in this (impure) function
78+
*/
79+
80+
type Mode = 'code' | 'design' | 'markdown';
81+
82+
const switchMode = (m: Mode): void => {
83+
// pretend that something useful happens here
84+
}
85+
86+
const nextMode = (m: Mode): void => {
87+
if (m === 'code') {
88+
switchMode('design');
89+
} else if (m === 'design') {
90+
switchMode('markdown');
91+
} else {
92+
switchMode('code');
93+
}
94+
};
95+
96+
97+
/*
98+
The identity function.
99+
100+
This is a very simple function that takes an argument and returns it.
39101
*/
40102

41103
const identity = <A> (a: A): A => a;
@@ -59,29 +121,105 @@ const getOrElse1 = <A> (oa: Optional<A>, other: A): A =>
59121

60122
// TODO: write a version of getOrElse1 using Fun.identity.
61123

124+
// TODO: What happens if you map the identity function over an Optional?
125+
// Answer: ...
62126

127+
// TODO: What happens if you map the identity function over an Array?
128+
// Answer: ...
63129

130+
/*
131+
In FP, we use a lot of little functions like identity, that seem insignificant on their own, but they come in handy
132+
and form a little toolkit for bashing the data you have into the shape it needs to be in.
64133
65-
// You can also use fold to do some kind of "side-effect" on each branch, as below.
134+
Next up is "constant". If you pass a value to this function, it gives you a function that always returns the same thing.
66135
136+
You can find this as Fun.constant in katamari.
67137
68-
export const message2 = (e: Optional<string>): void =>
69-
e.fold(
70-
() => {
71-
console.log("oop");
72-
},
73-
(s) => {
74-
console.log("The value was " + s);
75-
}
76-
);
138+
One way of writing it is below:
139+
*/
77140

78-
// We normally try to isolate side-effects, though, so we'd normally separate the calculation from the side effect
79-
export const message3 = (e: Optional<string>): void => {
80-
const m = e.fold(
81-
() => "no value",
82-
(s) => "The value was " + s
83-
);
84-
console.log(m);
85-
}
141+
const constant = <A, B> (a: A) => (b: B) => a;
142+
143+
const always3 = constant(3);
144+
145+
/*
146+
So, constant ignores whatever is passed for the B parameter, and just returns the A.
147+
148+
Again, this looks familiar from our getOrElse1 function above.
149+
150+
TODO: rewrite getOrElse1 using both Fun.identity and the "constant" function defined above.
151+
*/
152+
153+
154+
/*
155+
Now, katamari's Fun.constant is slightly different to ours above:
156+
157+
const constant = <A, B> (a: A) => (b: B) => a; // ours
158+
const constant = <A> (a: A) => () => a; // katamari's
159+
160+
In most languages, this would be make the two incompatible. However, TypeScript is a bit more lenient and lets us
161+
use the second form everywhere the first form is expected.
162+
163+
TODO: don't just take my word for it - use katamari's Fun.constant in you getOrElse and see if it compiles.
164+
*/
165+
166+
// TODO: Write a function that takes an array of numbers and replaces each value with 9.
167+
168+
169+
// TODO: In the previous question, what's the *same* between the input and output values
170+
// Answer:
171+
172+
173+
/*
174+
Function composition
175+
176+
Functions take an input and return an output. Let's think of them as little machines with an input and output slots.
177+
Let's use terrible ASCII art to demonstrate, and generic letters A, B and C to represent their input and output types.
178+
179+
Here's one function:
180+
181+
]===>===[
182+
183+
we stick in an A and it spits out a B:
184+
185+
A ]===>===[ B
186+
187+
Here's another function. This one takes a B and spits out a C.
188+
189+
B }---*>---{ C
190+
191+
So what happens if I plug these two together? Well, I should be able to make a machine that takes an A and spits out a C:
192+
193+
A ]===>===[}---*>---{ C
194+
195+
This is function composition.
196+
197+
In TypeScript, it looks a bit like this:
198+
*/
199+
200+
const compose = <A, B, C> (f: (a: B) => C, g: (a: A) => B) => (a: A): C => f(g(a));
201+
202+
/*
203+
The below function "dblS" doubles a number then converts it to a string.
204+
*/
205+
206+
const dbl = (x: number): number => x * 2;
207+
208+
const dblS: (s: number) => string =
209+
compose(String, dbl);
210+
211+
/*
212+
It can read a bit funny, since it does the dbl, then the String. But, the order comes from the fact, e.g.
213+
214+
compose(String, dbl) === (a) => String(dbl(x))
215+
216+
Now, katamari has a Fun.compose1, which is like our compose here. It also has a Fun.compose, which has a gnarlier
217+
signature and handling for n-ary functions. Your rule-of-thumb is to use Fun.compose1 unless you really need Fun.compose.
218+
*/
219+
220+
// TODO: use Fun.compose1 to write a function that doubles a number twice
86221

222+
// TODO: Rewrite this function to use a single map call and function composition
223+
const dblOs = (oa: Optional<number>): Optional<string> =>
224+
oa.map(dbl).map(String);
87225

0 commit comments

Comments
 (0)