1- import { Option as Optional } from "@ephox/katamari" ;
1+ import { Fun , Option as Optional } from "@ephox/katamari" ;
22
33/*
44Functional 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
3131Now, 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
41103const 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