Til hovedinnhold

Funksjonell programmering i Enonic XP #3 - Option

Publisert: 13. desember 2019 av Tom Arild Jakobsen

I stedet for å returnere null-verdier kan vi returnere en Option, som tvinger utvikleren til å håndtere tilfellet der verdien mangler.

Artikkelnummer Andre artikler i denne serien
1 Funksjonell programmering i Enonic XP #1 - Intro
2 Funksjonell programmering i Enonic XP #2 - TypeScript
3 Funksjonell programmering i Enonic XP #3 - Option
4 Funksjonell programmering i Enonic XP #4 - Either
5 Funksjonell programmering i Enonic XP #5 - IO
6 Funksjonell programmering i Enonic XP #6 - Eksempler

Håndtere null

Den neste sideeffekten vi må håndtere er at contentLib.get kan returnere null, dersom verdien ikke eksisterer.

TypeScript-kompilatoren tvinger utvikleren til å ta høyde for null-verdier, så problemet med "runtime errors" når en verdi enten er null eller undefined, er mindre i TypeScript enn i JavaScript.

I funksjonell programmerings verden har vi et eget konsept for å håndtere manglende verdier, og det er Option<A>. En Option<A> kan være én av to typer. Enten en Some<A>, som betyr at det finnes en verdi. Eller en None, som betyr at det ikke finnes en verdi.

Eksempel:

Vi kan pakke inn resultatet fra en content.get() i en Option, for å representere muligheten for at verdien for den nøkkelen ikke finnes i databasen.

I dette eksempelet lager vi en funksjon getEmployee(), som returnerer Option<Content<Employee>>. Det er også en funksjon get, som bruker en fold() for å håndtere resultatet av getEmployee() for å lage en Response.

Det er en god tommelfingerregel at man kun lager Response i get, post etc. funksjoner i controllere. Og den vanligste måten å gjøre det er å gjøre en fold() over noe data man har hentet fra databasen.

Merk deg hvordan utvikleren her blir tvunget til å håndtere null-tilfellet, når de vil "pakke ut" verdien de får returnert som en Option. Funksjonell programmering tvinger oss til å håndtere det som ikke er "happy-pathen", og det er en veldig bra ting.

import { fold, none, Option, some } from 'fp-ts/Option';
import { Employee } from '../../content-types/employee/employee'
import { Content, ContentLibrary } from "enonic-types/content";
import { Request, Response } from "enonic-types/controller";

const contentLib: ContentLibrary = __non_webpack_require__('/lib/content');

/**
 * Returns employee content, if its found
 */
function getEmployee(key: string):  Option<Content<Employee>> {
  const employee = contentLib.get<Employee>({ key });

  return (employee !== null)
    ? some(employee)
    : none;
}

/**
 * Controllers get function
 */
export function get(req: Request): Response {
  const optionEmployee = getEmployee(req.params.key!);

  return fold(
    () => (
      {
        status: 404
      }
    ),
    (employeeContent) => (
      {
        status: 200,
        body: employeeContent
      }
    )
  )(optionEmployee);
}

Hjelpefunksjoner

Eksempel:

Det fins også en hjelpefunksjon som man kan bruke for å gjøre en "nullable" verdi om til en Option, og det er fromNullable().

Du kan se at getEmployee() i det neste eksempel fungerer på akkurat samme måte som den gjorde i forrige eksempel, men med litt enklere kode.

import { fromNullable, Option } from 'fp-ts/lib/Option';
import { Employee } from '../../content-types/employee/employee'
import { Content, ContentLibrary } from "enonic-types/lib/content";

const contentLib: ContentLibrary = __non_webpack_require__('/lib/content');

/**
 * Returns employee content, if its found
 */
function getEmployee(key: string):  Option<Content<Employee>> {
  return fromNullable(contentLib.get<Employee>({ key }));
}

// ...resten av koden er det samme som over

Veien videre

I neste kapittel skal vi se på håndtering av Exceptions med Either<E, A>.