Skip to content

Commit a39d337

Browse files
committed
Reader
1 parent ac8d137 commit a39d337

File tree

3 files changed

+387
-78
lines changed

3 files changed

+387
-78
lines changed

src/WIP/lesson3-reader.ts

-78
This file was deleted.

src/lesson5-reader.ts

+181
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
export type Reader<R, A> = { type: "Reader"; runReader: (r: R) => A };
2+
3+
// most basic constructor
4+
export const reader = <R, A>(runReader: (r: R) => A): Reader<R, A> => ({
5+
type: "Reader",
6+
runReader: runReader
7+
});
8+
9+
// take A and plop it into the Reader context
10+
// A -> Reader R A
11+
export const pure = <R, A>(a: A): Reader<R, A> => reader(_ => a);
12+
13+
// run the computation, by passing it the environment
14+
export const runReader = <R, A>(environment: R, value: Reader<R, A>): A =>
15+
value.runReader(environment);
16+
17+
// the environment we are going to use
18+
type HorseInformation = {
19+
expectedLegs: number;
20+
expectedTail: boolean;
21+
acceptableNames: string[];
22+
};
23+
24+
export const horseInformation: HorseInformation = {
25+
expectedLegs: 4,
26+
expectedTail: true,
27+
acceptableNames: ["CHAMPION", "HOOVES GALORE", "HAM GAMALAN"]
28+
};
29+
30+
// Simplest Reader example (ignores the environment)
31+
export const read1 = pure("Horses");
32+
33+
// Let's add mapping
34+
// map :: (A -> B) -> Reader R A -> Reader R B
35+
export const map = <R, A, B>(
36+
fn: (a: A) => B,
37+
readA: Reader<R, A>
38+
): Reader<R, B> => reader(r => fn(readA.runReader(r)));
39+
40+
export const read2 = map(a => a.toUpperCase(), pure("Horses"));
41+
42+
// bind :: (A -> Reader R B) -> Reader R A -> Reader R B
43+
export const bind = <R, A, B>(
44+
fn: (a: A) => Reader<R, B>,
45+
readA: Reader<R, A>
46+
): Reader<R, B> => reader(r => fn(readA.runReader(r)).runReader(r));
47+
48+
// let's quickly crack open an Maybe
49+
type Maybe<A> = { type: "Nothing" } | { type: "Just"; value: A };
50+
51+
// just :: A -> Maybe A
52+
const just = <A>(value: A): Maybe<A> => ({ type: "Just", value });
53+
54+
// nothing :: Maybe never
55+
const nothing = (): Maybe<never> => ({ type: "Nothing" });
56+
57+
// and a Horse type
58+
type Horse = { type: "Horse"; name: string; legs: number; tail: boolean };
59+
60+
// and a stable they live in
61+
type Stable = { type: "Stable"; horses: Horse[] };
62+
63+
const makeStableWithHorse = (horse: Horse): Stable => ({
64+
type: "Stable",
65+
horses: [horse]
66+
});
67+
68+
const horseNameExists = (
69+
horseName: string
70+
): Reader<HorseInformation, Maybe<Stable>> =>
71+
reader(horseInfo =>
72+
horseInfo.acceptableNames.indexOf(horseName) !== -1
73+
? just(
74+
makeStableWithHorse({
75+
type: "Horse",
76+
name: horseName,
77+
legs: horseInfo.expectedLegs,
78+
tail: horseInfo.expectedTail
79+
})
80+
)
81+
: nothing()
82+
);
83+
84+
export const read3 = bind(horseNameExists, pure("George"));
85+
86+
const showHorseAcceptability = (value: Maybe<Stable>): string =>
87+
value.type === "Nothing"
88+
? "No good horses here I am afraid"
89+
: value.value.horses
90+
.map(horse => `${horse.name} is an acceptable horse`)
91+
.join(", ");
92+
93+
export const read4 = map(
94+
showHorseAcceptability,
95+
bind(horseNameExists, pure("HOOVES GALORE"))
96+
);
97+
98+
// part 2
99+
100+
// These Reader values are a bit unwieldy, so let's make them easier
101+
// to throw around
102+
103+
// ap :: Reader R (A -> B) -> Reader R A -> Reader R B
104+
export const ap = <R, A, B>(
105+
readF: Reader<R, (a: A) => B>,
106+
readA: Reader<R, A>
107+
): Reader<R, B> =>
108+
reader(r => {
109+
const f = readF.runReader(r);
110+
const a = readA.runReader(r);
111+
return f(a);
112+
});
113+
114+
// curry2 :: (A, B -> C) -> A -> B -> C
115+
const curry2 = <A, B, C>(f: (a: A, b: B) => C) => (a: A) => (b: B) => f(a, b);
116+
117+
// liftA2 :: (A -> B -> C) -> Reader R A -> Reader R B -> Reader R C
118+
export const liftA2 = <R, A, B, C>(
119+
f: (a: A, b: B) => C,
120+
readA: Reader<R, A>,
121+
readB: Reader<R, B>
122+
): Reader<R, C> => ap(map(curry2(f), readA), readB);
123+
124+
type Monoid<A> = {
125+
empty: A;
126+
append: (one: A, two: A) => A;
127+
};
128+
129+
// smash together a list of monoid values
130+
// Monoid A -> A[] -> A
131+
const concat = <A>(monoid: Monoid<A>, values: A[]): A =>
132+
values.reduce(monoid.append, monoid.empty);
133+
134+
// readerMonoid :: Monoid<Reader<R,A>>
135+
const readerMonoid = <R, A>(monoid: Monoid<A>): Monoid<Reader<R, A>> => ({
136+
empty: pure(monoid.empty),
137+
append: (readA, readB) => liftA2(monoid.append, readA, readB)
138+
});
139+
140+
// maybeMonoid :: Monoid<Maybe<A>>
141+
const maybeMonoid = <A>(monoid: Monoid<A>): Monoid<Maybe<A>> => ({
142+
empty: nothing(),
143+
append: (one, two) => {
144+
if (one.type === "Nothing") {
145+
return two;
146+
} else if (two.type == "Nothing") {
147+
return one;
148+
}
149+
return just(monoid.append(one.value, two.value));
150+
}
151+
});
152+
153+
// strongAndStableMonoid :: Monoid<Stable>
154+
const strongAndStableMonoid = (): Monoid<Stable> => ({
155+
empty: { type: "Stable", horses: [] },
156+
append: (one, two) => ({
157+
type: "Stable",
158+
horses: [...one.horses, ...two.horses]
159+
})
160+
});
161+
162+
// take a list of names
163+
// and work out if they are valid horses or not
164+
165+
// bigHorseValidator :: String[] -> Reader HorseInformation (Maybe Stable)
166+
const bigHorseValidator = (
167+
names: string[]
168+
): Reader<HorseInformation, Maybe<Stable>> => {
169+
const monoid = readerMonoid<HorseInformation, Maybe<Stable>>(
170+
maybeMonoid(strongAndStableMonoid())
171+
);
172+
173+
const readerHorses = names.map(horseNameExists);
174+
return concat(monoid, readerHorses);
175+
};
176+
177+
// acceptableHorsesCheck :: String[] -> Reader HorseInformation String
178+
export const acceptableHorsesCheck = (
179+
names: string[]
180+
): Reader<HorseInformation, string> =>
181+
map(showHorseAcceptability, bigHorseValidator(names));

0 commit comments

Comments
 (0)