Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions src/main/html/Part1Ex1.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

TODO Head over to https://www.tiny.cloud/ and sign up for an API key. Replace the 'no-api-key' with your api key in the below script tag.
-->
<script src="https://cdn.tiny.cloud/1/no-api-key/tinymce/5/tinymce.min.js" referrerpolicy="origin"></script>
<script src="https://cdn.tiny.cloud/1/b6r7yu1n19ibkha4z0s4gdvt0l7z2mc3dzlnc780ep0uuzks/tinymce/5/tinymce.min.js" referrerpolicy="origin"></script>
</head>
<body>

Expand All @@ -30,9 +30,12 @@
Since a selector can match multiple elements, you can use tinymce.init to create multiple editors.
TODO In the below script tag, instantiate TinyMCE editors in the below
-->
<textarea class="editorSet"></textarea>
<textarea class="editorSet"></textarea>
<script></script>
<textarea id="editor2" class="editorSet"></textarea>
<textarea id="editor3" class="editorSet"></textarea>
<script>
tinymce.init({ selector: '#editor2' });
tinymce.init({ selector: '#editor3' });
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could just be simplified to this for what it's worth 🤷

Suggested change
tinymce.init({ selector: '#editor2' });
tinymce.init({ selector: '#editor3' });
tinymce.init({ selector: '.editorSet' });

</script>


<!--
Expand All @@ -54,7 +57,9 @@

Note: you'll need to add some plugins for these to work.
-->

<h1>Custom Editor</h1>
<textarea id="customEditor"></textarea>
<script>tinymce.init({selector: "#customEditor", menubar: false, status: false, plugins: "fullscreen table code", toolbar: "bold table fullscreen code"})</script>

</body>

Expand Down
11 changes: 7 additions & 4 deletions src/main/ts/Part2Ex1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ Let's model the x,y of the top-left and bottom-right corners.
*/
export interface Boundz {
// TODO: add fields: x1, y1, x2, y2
x1: number;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this it's fine, but just as a heads up you'll see we normally mark most properties readonly since that helps to ensure we're not mutating data.

y1: number;
x2: number;
y2: number;
}

/*
Expand All @@ -27,10 +31,7 @@ We tell tsc to transpile to ES5, so IE works.
Notice also that we have an explicit return type. This lets the compiler check that our
code matches the type signature.
*/
export const width = (b: Boundz): number =>
/* TODO */ -1;

// TODO implement height function
export const width = (b: Boundz): number => b.x2 - b.x1;

/*
3. Compiling.
Expand All @@ -47,3 +48,5 @@ Ok, so we started off pretty easy.
Now, code is useless without tests, so let's head over to Exercise1CodeStyleTest.ts
and write some tests.
*/

export const height = (b: Boundz): number => b.y2 - b.y1;
18 changes: 8 additions & 10 deletions src/main/ts/Part2Ex2ArrayFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ We don't write loops if we can help it. Instead, we go up a level, and call func
The simplest of these is 'each' which just iterates.

TODO: Run the following code using this command:
yarn bedrock-auto -b chrome-headless -f src/test/ts/Exercise2ArrayFunctionsTest.ts
yarn bedrock-auto -b chrome-headless -f src/test/ts/part2/Exercise2ArrayFunctionsTest.ts
*/

export const runEach1 = (): void => {
Expand Down Expand Up @@ -41,6 +41,7 @@ export const myFrogs: Frog[] = [

export const runEach2 = (): void => {
// TODO: Use Arr.each and console.log to print the name of each frog
Arr.each(myFrogs, (f: Frog) => console.log(f.name))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Arr.each(myFrogs, (f: Frog) => console.log(f.name))
Arr.each(myFrogs, (f) => console.log(f.name))

You don't need to declare the type here as it'll be inferred. We normally try to avoid re-declaring redundant types as if you have to change it later it just makes more work 🤷

};

/*
Expand All @@ -65,8 +66,7 @@ export const runMap2 = (xs: number[]): string[] =>

// TODO: Return the frog's names and check it by running
// yarn bedrock-auto -b chrome-headless -f src/test/ts/Exercise2ArrayFunctionsTest.ts
export const frogNames = (fs: Frog[]): string[] =>
[];
export const frogNames = (fs: Frog[]): string[] => Arr.map(fs, (f: Frog) => f.name);

// TODO: Return the frog's ages
// TODO: Write a test for this in Exercise2ArrayFunctionsTest
Expand All @@ -83,12 +83,10 @@ export const evens = (xs: number[]): number[] =>

// TODO: Write a function that returns all the frogs that ribbit
// TODO: Run the provided test to check your answer.
export const ribbitting = (frogs: Frog[]): Frog[] =>
[];
export const ribbitting = (frogs: Frog[]): Frog[] => Arr.filter(frogs, (f: Frog) => f.ribbits === true)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const ribbitting = (frogs: Frog[]): Frog[] => Arr.filter(frogs, (f: Frog) => f.ribbits === true)
export const ribbitting = (frogs: Frog[]): Frog[] => Arr.filter(frogs, (f: Frog) => f.ribbits)

The === true here is redundant since it's a non-optional boolean, so we're normally just do something like the above instead.


// TODO: Write a function that returns all frogs aged 8 or older
export const olderFrogs = (frogs: Frog[]): Frog[] =>
[];
export const olderFrogs = (frogs: Frog[]): Frog[] => Arr.filter(frogs, (f: Frog) => f.age >= 8)

/*
5. Arr.exists
Expand All @@ -97,8 +95,9 @@ Arr.exists returns true if there is one or more element that matches a predicate
*/

// TODO: Write a function that returns true if there's one or more ribbiting frogs

export const oneOrMoreRibbitingFrogs = (frogs: Frog[]) => Arr.exists(frogs, (f: Frog) => f.ribbits === true);
// TODO: Write a function that takes an array of numbers, and returns true if there are any negative numbers
export const hasNegativeNumber = (items: number[]) => Arr.exists(items, (i: number) => i < 0);

/*
6. Arr.bind
Expand All @@ -110,8 +109,7 @@ This behaviour of running map then flatten is why this function is sometimes cal

TODO: Write a function that takes a list of strings, each string containing a comma-separated list of values, and returns all of the values as an array.
*/
export const splitCsvs = (csvs: string[]): string[] =>
[];
export const splitCsvs = (csvs: string[]): string[] => Arr.bind(csvs, (s: string) => s.split(','));

/*
7. Arr.find
Expand Down
33 changes: 25 additions & 8 deletions src/main/ts/Part2Ex3Optional.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Optional } from '@ephox/katamari';
import { search } from '@ephox/sugar/lib/main/ts/ephox/sugar/api/dom/Focus';
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Something went wrong with an auto import here I'm guessing... you'll find you'll get a lint error in any of our regular code for this fwiw. We also prefer used named imports as it makes the code more readable, e.g:

import { Focus } from `@ephox/katamari';`


/*
Optional
Expand Down Expand Up @@ -35,13 +36,19 @@ export const toPositiveInteger = (n: number): Optional<number> =>
n > 0 ? Optional.some(n) : Optional.none();

// TODO: create a function which takes a string and returns some if the string is non-empty
export const isString = (s: string): Optional<string> => s ? Optional.some(s) : Optional.none();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will work fine, but just as a style hint we don't allow falsy checks for non-boolean values like this most times, so it should probably be something like this instead:

Suggested change
export const isString = (s: string): Optional<string> => s ? Optional.some(s) : Optional.none();
export const isString = (s: string): Optional<string> => s.length > 0 ? Optional.some(s) : Optional.none();


// TODO: create a function which takes a url as a string and returns the protocol part as an Optional.
// The string may or may not actually have a protocol. For the protocol to be valid, it needs to be all alpha characters.
// You can use a regex.
// Have a look at Exercise3OptionTest.ts for example input. Make sure the tests pass.
export const getProtocol = (url: string): Optional<string> => {
throw new Error("TODO");
let result = null;
if (url) {
result = url.match(/^http(s)?/);
}

return result ? Optional.some(result[0]) : Optional.none();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would return the wrong value if I had a string such as httpsimple.com, can you have another go at this making sure to take that into account and avoiding the falsy checks please?

};

/*
Expand All @@ -57,10 +64,13 @@ TODO: use Optional.from to implement the following DOM function
*/

export const getNextSibling = (e: Element): Optional<ChildNode> => {
throw new Error("TODO");
return Optional.from(e ? e.nextSibling : null);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you have a truthy check here as based on the types e cannot be nullable? As such, this could just be this:

Suggested change
return Optional.from(e ? e.nextSibling : null);
return Optional.from(e.nextSibling);

};

// TODO: use Optional.from to implement a similar wrapper for Element.getAttributeNode(string)
export const getElementAttribute = (e: Element, attributeName: string): Optional<Attr> => {
return Optional.from(e && attributeName ? e.getAttributeNode(attributeName) : null)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my previous comment this could be this instead:

Suggested change
return Optional.from(e && attributeName ? e.getAttributeNode(attributeName) : null)
return Optional.from(e.getAttributeNode(attributeName));

}

/*
How do we get data out of an Optional? Well, that's a bit tricky since there isn't always
Expand All @@ -80,11 +90,10 @@ export const message = (e: Optional<string>): string =>
);

// TODO: Implement a function using fold, that takes an Optional<number>. If it's some, double it. If it's none, return 0;
export const doubleNumber = (n: Optional<number>): number => n.fold(() => 0, (n: number) => n * 2);

// TODO: Implement a function that takes an Optional<T> for any type T. Return true if it's some, and false if it's none.
const trueIfSome = <T> (x: Optional<T>): boolean => {
throw new Error("TODO");
};
const trueIfSome = <T> (x: Optional<T>): boolean => x.fold(() => false, () => true);

/*
The last function you implemented is already part of the Optional type, and is called isSome().
Expand All @@ -107,8 +116,10 @@ You can do this with fold, but getOr is a shortcut.
*/

// TODO: Using getOr, take an Optional<{age: number}> and turn it into an {age: number}, using a default value of 0.
export const toValueOr = (input: Optional<{age: number}>): {age: number} => input.getOr({age: 0});

// TODO: Write the same function using fold
export const toValueOrWithFold = (input: Optional<{age: number}>): {age: number} => input.fold(() => {return {age: 0};}, (input: {age: number}) => input );
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const toValueOrWithFold = (input: Optional<{age: number}>): {age: number} => input.fold(() => {return {age: 0};}, (input: {age: number}) => input );
export const toValueOrWithFold = (input: Optional<{age: number}>): {age: number} => input.fold(() => ({age: 0}), (input: {age: number}) => input);

As you're using newer ECMAScript syntax here you don't need the return statement.



/*
Expand All @@ -118,9 +129,13 @@ Let's explore this by converting Optionals to and from Arrays.
*/

// TODO: Write a function that converts an Optional<A> to an A[] for any type A.

export const optionalToArray = <A>(input: Optional<A>): A[] => {
return input.isSome() ? input.toArray() : [];
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just as a heads up we try to avoid using isSome as much as possible. So in this case, given what had been introduced, we probably should have done something like this instead:

  return input.fold(() => [], (a) => [a]);

Worth noting if using toArray you don't need the isSome check at all and it could instead have just been this:

  return input.toArray();

}
// TODO: Write a function that converts an A[] to an Optional<A>. If the array has more than one element, only consider the first element.

export const arrayToOptional = <A>(input: A[]): Optional<A> => {
return Array.isArray(input) && input.length > 0 ? Optional.some(input[0]) : Optional.none();
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We already know this is an array, so you don't need the Array.isArray check. We could also use the fact that accessing an index larger than the array returns undefined so this could have been this:

  return Optional.from(input[0]);

}

/*
One of the most useful functions on Optional is "map". We say this function "maps a function over the Optional".
Expand All @@ -139,12 +154,14 @@ const x: Optional<string> = Optional.some(3).map((x) => String(x)); // returns O
const y: Optional<string> = Optional.none<number>().map((x) => String(x)); // returns Optional.none<string>()

// TODO: Write a function that takes an Optional<number> and adds 3 to the number
export const add3 = (number: Optional<number>) => number.map((x: number) => x + 3);

// TODO: Write a function that takes an Optional<string> and prefixes the string with "hello"
export const prefixHello = (input: Optional<string>) => input.map((i: string) => "hello " + i);

/*
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?
Answer: ...
Answer: return Optional.none() since there is nothing to runs the function over
*/
const willItKersplode = (): Optional<string> => {
const z = Optional.none<string>();
Expand Down
24 changes: 17 additions & 7 deletions src/main/ts/Part2Ex4FP.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,15 @@ TODO: Extract a pure function for the logic hiding in this (impure) function

type Mode = 'code' | 'design' | 'markdown';

const hasMode = (m: Mode): boolean => {
return m === 'code' || m === 'design' || m === 'markdown' ? true : false;
}

const switchMode = (m: Mode): void => {
const valid = hasMode(m);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't really do anything meaningful unless you're accepting content from outside of TypeScript, as you can't pass a mode that isn't defined in the Mode type and if you're checking this it's somewhat defeating the point of using types. That said, you've still made a pure function here so its fine as that was the point of this exercise.

if (valid) {
// do side effect
}
// pretend that something useful happens here that causes a side effect
};

Expand Down Expand Up @@ -120,12 +128,12 @@ const getOrElse1 = <A> (oa: Optional<A>, other: A): A =>
// Hang on - that looks familiar. The function we pass as the "some" case is the identity function.

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

export const getOrElse2 = <A> (oa: Optional<A>, other: A): A => oa.fold(() => other, Fun.identity);
// TODO: What happens if you map the identity function over an Optional?
// Answer: ...
// Answer: Get the value of the Optional

// TODO: What happens if you map the identity function over an Array?
// Answer: ...
// Answer: Get the value of each element in the array

/*
In FP, we use a lot of little functions like identity, that seem insignificant on their own, but they come in handy
Expand All @@ -149,17 +157,19 @@ Again, this looks familiar from our getOrElse1 function above.

TODO: rewrite getOrElse1 using both Fun.identity and the "constant" function defined above.
*/

export const getOrElse3 = <A> (oa: Optional<A>, other: A): A => oa.fold(constant(other), Fun.identity)

/*
TODO: use katamari's Fun.constant in your getOrElse and see if it compiles.
*/
export const getOrElse4 = <A> (oa: Optional<A>, other: A): A => oa.fold(Fun.constant(other), Fun.identity)

// TODO: Write a function that takes an array of numbers and replaces each value with 9.
export const replaceElementWith9 = (items: number[]) => Optional.from(items).fold(Fun.constant([]), (items) => items.map(Fun.constant(9)));
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is overly complicated, as again items shouldn't be nullable so you don't need the Optional.from logic here. Additionally as a heads up we generally avoid the native Array.map function and instead use the helper functions defined in Arr. So instead this could have been something like this:

Suggested change
export const replaceElementWith9 = (items: number[]) => Optional.from(items).fold(Fun.constant([]), (items) => items.map(Fun.constant(9)));
export const replaceElementWith9 = (items: number[]) => Arr.map(items, Fun.constant(9));



// TODO: In the previous question, what's the *same* between the input and output values
// Answer:
// Answer: They are the same type (number) and same number of elements??


/*
Expand Down Expand Up @@ -210,7 +220,7 @@ signature and handling for n-ary functions. Your rule-of-thumb is to use Fun.com
*/

// TODO: use Fun.compose1 to write a function that doubles a number twice
export const dblX2: (x: number) => number = Fun.compose1(dbl, dbl);

// TODO: Rewrite this function to use a single map call and function composition
const dblOs = (oa: Optional<number>): Optional<string> =>
oa.map(dbl).map(String);
export const dblOs = (oa: Optional<number>): Optional<string> => oa.map(Fun.compose1(String, dbl));
21 changes: 17 additions & 4 deletions src/main/ts/Part2Ex5Sugar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,16 @@ as the main document.

TODO: Use SugarElement's fromHtml and fromText functions to create a few elements.
*/
const divWithText = SugarElement.fromHtml("<div></div>");
const divText = SugarElement.fromText("this is a div");
// maybe not relevant to this question
divWithText.dom.appendChild(divText.dom);

const paragraph = SugarElement.fromHtml("<p></p>", document);
const paragraphText = SugarElement.fromText("this is a paragraph");
// maybe not relevant to this question
paragraph.dom.appendChild(paragraphText.dom);



/*
Expand All @@ -52,9 +62,8 @@ We often have to traverse from an element to its relatives. The Traverse module
Insert.append(parent, kid);

const parent2 = Traverse.parent(kid);

// TODO: inspect the type of Traverse.parent and explain why that type was used.
// Answer:
// TODO: inspect the type of Traverse.parent and explain why that type was used.
// Answer: It is safer to return an Optional type in case the targeted element doesn't have a parent
};


Expand All @@ -67,11 +76,15 @@ We often have to traverse from an element to its relatives. The Traverse module
Insert.append(parent, kid2);

// TODO: starting at kid1, find kid2
const foundKid2 = Traverse.nextSibling(kid1);

// TODO: starting at kid2, find kid1

const foundKid1 = Traverse.prevSibling(kid2);
// TODO: starting at parent, find both kids
const children = Traverse.children(parent);

// TODO: kid2 grew up - give it its own child node
const childOfKid2 = SugarElement.fromTag("div");
Insert.append(kid2, childOfKid2);
};

Loading