## DEV Community

Giulio Canti

Posted on • Updated on

# Functional design: how to make the `time` combinator more general

In the last article I wrote a `time` combinator which mimics the analogous Unix command: given an action `IO<A>`, we can derive an action `IO<A>` that prints to the console the elapsed time

``````import { IO, io } from 'fp-ts/IO'
import { now } from 'fp-ts/Date'
import { log } from 'fp-ts/Console'

export function time<A>(ma: IO<A>): IO<A> {
return io.chain(now, start =>
io.chain(ma, a => io.chain(now, end => io.map(log(`Elapsed: \${end - start}`), () => a)))
)
}
``````

There are two problems with this combinator though:

• is not flexible, i.e. consumers can't choose what to do with the elapsed time
• works with `IO` only

## Adding flexibility by returning the elapsed time

Instead of always logging, we can return the elapsed time along with the computed value

``````export function time<A>(ma: IO<A>): IO<[A, number]> {
return io.chain(now, start => io.chain(ma, a => io.map(now, end => [a, end - start])))
}
``````

Now a user can choose what to do with the elapsed time by defining its own combinators.

We could still log to the console...

``````export function withLogging<A>(ma: IO<A>): IO<A> {
return io.chain(time(ma), ([a, millis]) =>
io.map(log(`Result: \${a}, Elapsed: \${millis}`), () => a)
)
}
``````

Usage

``````import { randomInt } from 'fp-ts/Random'

function fib(n: number): number {
return n <= 1 ? 1 : fib(n - 1) + fib(n - 2)
}

const program = withLogging(io.map(randomInt(30, 35), fib))

program()
/*
Result: 14930352, Elapsed: 127
*/
``````

...or just ignore the elapsed time...

``````export function ignoreSnd<A>(ma: IO<[A, unknown]>): IO<A> {
return io.map(ma, ([a]) => a)
}
``````

...or, for example, only keep the fastest of a non empty list of actions

``````import { fold, getMeetSemigroup } from 'fp-ts/Semigroup'
import { contramap, ordNumber } from 'fp-ts/Ord'
import { getSemigroup } from 'fp-ts/IO'

export function fastest<A>(head: IO<A>, tail: Array<IO<A>>): IO<A> {
const ordTuple = contramap(([_, elapsed]: [A, number]) => elapsed)(ordNumber)
const semigroupTuple = getMeetSemigroup(ordTuple)
const semigroupIO = getSemigroup(semigroupTuple)
return ignoreSnd(fastest)
}
``````

Usage

``````io.chain(fastest(program, [program, program]), a => log(`Fastest result is: \${a}`))()
/*
Result: 5702887, Elapsed: 49
Result: 2178309, Elapsed: 20
Result: 5702887, Elapsed: 57
Fastest result is: 2178309
*/
``````

In the next article we'll tackle the second problem by introducing a powerful style of programming: tagless final.

## Appendix

The implementation of `fastest` is quite dense, let's see the relevant bits:

1) its signature ensures that we provide a non empty list of actions

``````//  at least one action --v            v--- possibly other actions
function fastest<A>(head: IO<A>, tail: Array<IO<A>>): IO<A>
``````

2) `contramap` is an `Ord` combinator: given an instance of `Ord` for `T` and a function from `U` to `T`, we can derive an instance of `Ord` for `U`.

Here `T = number` and `U = [A, number]`

``````// from `Ord<number>` to `Ord<[A, number]>`
const ordTuple = contramap(([_, elapsed]: [A, number]) => elapsed)(ordNumber)
``````

3) `getMeetSemigroup` transforms an instance of `Ord<T>` into an instance of `Semigroup<T>` which, when combining two values, returns the smaller

``````// from `Ord<[A, number]>` to `Semigroup<[A, number]>`
const semigroupTuple = getMeetSemigroup(ordTuple)
``````

4) `getSemigroup` is a `Semigroup` combinator: given an instance of `Semigroup` for `T`, we can derive an instance of `Semigroup` for `IO<T>`

``````// from `Semigroup<[A, number]>` to `Semigroup<IO<[A, number]>>`
const semigroupIO = getSemigroup(semigroupTuple)
``````

5) `fold` reduces a non empty list of actions using the provided `Semigroup`

``````// from a non empty list of `IO<[A, number]>` to `IO<[A, number]>`
``````// from `IO<[A, number]>` to `IO<A>`