Skip to content

Instantly share code, notes, and snippets.

@ulve
Last active May 23, 2018 08:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ulve/5831f7ed45a886b44252c400d038797c to your computer and use it in GitHub Desktop.
Save ulve/5831f7ed45a886b44252c400d038797c to your computer and use it in GitHub Desktop.
Användning av contramap i typescript
// Först gör vi en enum som beskriver dom olika sätt saker kan vara sorterade på
// inte så många kanske
enum Ordering {
GreaterThan = 1,
LessThan = -1,
Equal = 0
}
// Sen gör vi ett interface för att beskriva att en typ är möjlig att jämföra
// Skickar vi in två objekt så skall funktionen sen svara om det är större än
// Mindre än eller lika med ganska rakt på sak
interface Orderable<A> {
compare(x: A, y: A): Ordering;
}
// Om vi gör en exempelimplementation för siffror
const ascending: Orderable<number> = {
compare: (x: number, y: number): Ordering =>
x < y ? Ordering.LessThan : x > y ? Ordering.GreaterThan : Ordering.Equal
};
// En testarray att köra på
const testNumbers = [1, 4, 3, 7, 4];
// Det fiffiga med den här är även att den fungerar med array.sort rakt av
const sorted1 = testNumbers.sort(ascending.compare);
// => [1, 3, 4, 4, 7]
// Vi kan göra en för descending också
const descending: Orderable<number> = {
compare: (x: number, y: number): Ordering =>
x < y ? Ordering.GreaterThan : x > y ? Ordering.LessThan : Ordering.Equal
};
const sorted2 = testNumbers.sort(descending.compare);
// => [7, 4, 3, 2, 1]
// Det var ju inte så oväntat. Nått som är ger lite dålig magkänsla är att båda
// är så lika. Kan vi inte göra något åt det?
// Vi tar gör en funktion som vänder på en sorteringsfunktion
const invert = <A>(ord: Orderable<A>): Orderable<A> => ({
compare: (x: A, y: A): Ordering => ord.compare(y, x)
});
// Då kan man göra så här
const descending2: Orderable<number> = invert(ascending);
// Se på tusan. Det fungerade
const sorted3 = testNumbers.sort(descending2.compare);
// => [7, 4, 3, 2, 1]
// Inget hittils är ju särskilt häftigt ändå. Det känns bara onödigt.
// Kan vi inte låtsas att det är viktigt och hitta på något mer man kan göra?
// Vi skulle kunna göra en funktion för att composa sorteringar!
// Vad gör den här då? Jo den tar två sorteringar. Är dom lika så kör vi på sorteringen
// på den andra annars kör vi på den första sorteringen. Detta innebär att vi
// Först sorterar på x sen på y. Detta ger ju som inte så mycket på siffror
// men säg att vi hade objekt {djur: "katt", färg: "vit"} så hade vi kunnat
// sortera en lista med sådana på först djuret sen på färgen.
const composeOrdering = (x: Ordering, y: Ordering): Ordering =>
x === Ordering.Equal ? y : x;
// Men den går ju inte använda rakt av, det är ju bara sorteringsordningen
// Så vi gör en funktion som kan slå ihop två sorteringar.
// Här är det lite closures som stäler till det kanske men det skall nog gå
// följa.
const compose = <A>(x: Orderable<A>, y: Orderable<A>): Orderable<A> => ({
compare: (x1: A, y1: A): Ordering =>
composeOrdering(x.compare(x1, y1), y.compare(x1, y1))
});
// Vi gör ett exempel på det hela så man förstår lättare
interface Animal {
race: string;
colour: string;
}
const testAnimals: Animal[] = [
{ race: "Cat", colour: "black" },
{ race: "Dog", colour: "black" },
{ race: "Cat", colour: "yellow" },
{ race: "Fish", colour: "silver" }
];
// Här gör vi en funktion som sorterar på ras
const race: Orderable<Animal> = {
compare: (x: Animal, y: Animal): Ordering =>
x.race < y.race
? Ordering.LessThan
: x.race > y.race
? Ordering.GreaterThan
: Ordering.Equal
};
// Och en funktion som sorterar på färg
const colour: Orderable<Animal> = {
compare: (x: Animal, y: Animal): Ordering =>
x.colour < y.colour
? Ordering.LessThan
: x.colour > y.colour
? Ordering.GreaterThan
: Ordering.Equal
};
// Nu går dom att slå ihop
const raceThenColour: Orderable<Animal> = compose(race, colour);
// Dra mig på trissor! Det fungerar!
const sorted4 = testAnimals.sort(raceThenColour.compare);
// => [
// { race: "Cat", colour: "black" },
// { race: "Cat", colour: "yellow" },
// { race: "Dog", colour: "black" },
// { race: "Fish", colour: "silver" }
// ];
// Testar för säkerhetsskull att göra en andra vägen
const colourThenRace: Orderable<Animal> = compose(colour, race);
const sorted5 = testAnimals.sort(colourThenRace.compare);
// => [
// { race: "Cat", colour: "black" },
// { race: "Dog", colour: "black" },
// { race: "Fish", colour: "silver" }
// { race: "Cat", colour: "yellow" },
// ];
// Det är ju lite trist att man inte kan göra något återanvändbart där. All
// Strängsortering ser ju ganska lika ut.
// Vi gör en jämförelsefunktion för strängar då. Den borde ju gå återanvända
const stringOrd: Orderable<string> = {
compare: (x: string, y: string) =>
x < y ? Ordering.LessThan : x > y ? Ordering.GreaterThan : Ordering.Equal
};
// Gu så bra. Men det känns fortfarande inte som om jag är klar här. Den går ju inte
// riktigt att använda här va. Den tar ju en sträng och vi har bara Animals.
//
// Det finns något som heter contramap som kan vara bra för oss. Det är en bak
// och fram-map. Den mappar över inputen och inte outputen. I det här fallet
// är det så att vi tar en funktion Animal => string
// Sen så tar vi en Orderable av string och det som trillar ut är i slutändan
// Är en Orderable<Animal>. Det verkar ju som om den här contramap är specialgjord
// för oss! Det hela kan kännas ganska abstrakt men vi kan kolla om det går göra tydligare
const contramap = <A, B>(f: (fx: B) => A, O: Orderable<A>): Orderable<B> => ({
compare: (x: B, y: B): Ordering => O.compare(f(x), f(y))
});
// Okej här tar vi då en funktion som tar ett djur och vi returnerar bara ras/färg
// Ras/Färg går sen att sortera med våra strängSorterare. Men det som kommer ut är
// en sorterare på Animal. Det är ju snyggt. Det blir kompakt och bra
const contraRace: Orderable<Animal> = contramap<string, Animal>(
x => x.race,
stringOrd
);
const contraColour: Orderable<Animal> = contramap<string, Animal>(
x => x.colour,
stringOrd
);
// Compose fungerar fortfarande hoppas jag
const contraRaceThenColor = compose<Animal>(contraRace, contraColour);
// Japp! Vad roligt!
const sorted6 = testAnimals.sort(contraRaceThenColor.compare);
// => [
// { race: "Cat", colour: "black" },
// { race: "Cat", colour: "yellow" },
// { race: "Dog", colour: "black" },
// { race: "Fish", colour: "silver" }
// ];
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment