The goal is to implement the type Option<T>
. It represents a a value of type T
that is "optional" (might not exist).
Option
has two variants.
Some
for the case the value exist. Containing "some" value of typeT
.None
for the case the value doesn't exist.
Your solution should go here. There are also unit tests and sample solutions.
Create two free functions
Option.some
taking any value and returning anOption
instance holding that value. The returned value should have avalue
field containing the passed value and ahasValue
field set totrue
.Option.none
taking no argument and returning anOption
instance holding no value. The returned value should have ahasValue
field set tofalse
.
Hint 1
Define two different classes, one for each variant.
Hint 2
Define the Option
type as the union of both variants.
Hint 3
Build the variants in such a way that Option
becomes a discriminated union.
Hint 4
Ensure that the field hasValue
has the type true
in the Some
class and the type false
in the None
class.
Hint 5
some<T>(t: T): Option<T>
none<T>(): Option<T>
Challenge: implement the None
variant as a singleton without casting i.e. always the same instance is returned, regardless of the type the wrapped value has.
Challenge hint 1
Take a look at the never
type in typescript.
Write a free function that takes a value that might be null
or undefined
. If the value was null | undefined
than None
is returned, otherwise the value is wrapped in a Some
.
The return type should reflect that the Option
can't contain null | undefined
.
Hint 1
Typescript has a built in utility type NonNullable
.
Hint 2
fromNullable<T>(nullishValue: T): Option<NonNullable<T>>
Write a method unwrapOr
that takes a default value that is returned if option
is None
. If option
is Some
the wrapped is value simply returned.
unwrapOr
should allow for widening the type. Meaning the type of the default value can differ from the wrapped type.
Hint 1
unwrapOr
must introduce e new type for the Default value.
Hint 2
The return type is the union of the wrapped type and the type of the default value.
Hint 3
unwrapOr<U>(defaultValue: U): T | U
Write a method map
that takes a function mapper
and uses it to transform the wrapped value of the Option
.
Hint 1
If the Option
is None
then map
does nothing.
Hint 2
If the Option
is Some
then map
calls the mapper
function with the value of the option and returns the resulting value wrapped in a new Some
.
Hint 3
map
must introduce a new type. The mapper
function takes a value of the wrapped type of the Option
and returns the new type.
Hint 4
map<U>(mapper: (value:T) => U): Option<U>
Write a method flatMap
that takes a function mapper
and uses it to transform the wrapped value. The mapper
function should return an Option
instance but the returned value of flatMap
shall be flat i.e. not be a nested Option
.
Hint 1
If the Option
is None
then flatMap
does nothing.
Hint 2
If the Option
is Some
then flatMap
calls the mapper
function with the value of the option and returns the result.
Hint 3
flatMap
must introduce a new type. The mapper
function takes a value of the wrapped type of the Option
and returns an Option
of the new type.
Hint 4
flatMap<U>(mapper: (value:T) => Option<U>): Option<U>
Write a free function flatten
that takes an Option
containing an Option
and flattens it i.e. returns the nested Option
.
Hint 1
If the Option
is None
then Option.flatten
does nothing.
Hint 2
If the Option
is Some
then Option.flatten
simply returns the wrapped value.
Hint 3
Option.flatten
must introduce a new type that describes the value wrapped by two Options
.
Hint 4
flatten<T>(option: Option<Option<T>>): Option<T>
Challenge 1: Implement flatten
only using flatMap
.
Challenge hint 1.1
flatMap
already has a "flattening" behavior. Let the flatMap
do nothing.
Challenge 2: Also implement a method named flatten
that does the same. option.flatten
should only be callable if option
is known to contain another Option
.
Challenge hint 2.1
Take a look at this parameters. They allow you to specify in which context a method is allowed to be called.