You're looking for an anamorphism, or reverse fold –
// unfold : ((r, state) -> List r, unit -> List r, state) -> List r
const unfold = (f, init) =>
f ( (x, next) => [ x, ...unfold (f, next) ]
, () => []
, init
)
// sampleData : List { id: Int }
const sampleData =
unfold
( (next, done, i) =>
i > 25
? done ()
: next ({ id: i }, i + 1)
, 0
)
console .log (sampleData)
// [ { id: 0 }, { id : 1 }, ... { id: 25 } ]
You can get an intuition for how unfold
works by seeing it used in other common programs –
// unfold : ((r, state) -> List r, unit -> List r, state) -> List r
const unfold = (f, init) =>
f ( (x, next) => [ x, ...unfold (f, next) ]
, () => []
, init
)
// fibseq : Int -> List Int
const fibseq = init =>
unfold
( (next, done, [ n, a, b ]) =>
n === 0
? done ()
: next (a, [ n - 1, b, a + b ])
, [ init, 0, 1 ]
)
console .log (fibseq (10))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
The implementation of unfold
is just one possibility. Get tinkering and implement it in a way of your choosing –
// type Maybe a = Nothing | Just a
// Just : a -> Maybe a
const Just = x =>
({ match: ({ Just: f }) => f (x) })
// Nothing : unit -> Maybe a
const Nothing = () =>
({ match: ({ Nothing: f }) => f () })
// unfold : (state -> Maybe (a, state), state) -> List a
const unfold = (f, init) =>
f (init) .match
( { Nothing: () => []
, Just: ([ x, next ]) => [ x, ...unfold (f, next) ]
}
)
// fibseq : Int -> List Int
const fibseq = init =>
unfold
( ([ n, a, b ]) =>
n === 0
? Nothing ()
: Just ([ a, [ n - 1, b, a + b ] ]) // <-- yikes, read more below
, [ init, 0, 1 ]
)
console .log (fibseq (10))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]
I cheated a little above using a []
as a tuple. This kept the program shorter but it's better to explicitly model things and consider their types. You tagged this question with functional-programming so it's worth going the extra inch to remove this kind of implicit handling from our programs. By showing this as a separate step, we isolate a technique that can be applied not just to unfold
, but for any program we design –
// type Maybe a = Nothing | Just a
// type Tuple a b = { first: a, second: b }
// Just : a -> Maybe a
const Just = x =>
({ match: ({ Just: f }) => f (x) })
// Nothing : unit -> Maybe a
const Nothing = () =>
({ match: ({ Nothing: f }) => f () })
// Tuple : (a, b) -> Tuple a b
const Tuple = (first, second) =>
({ first, second })
// unfold : (state -> Maybe Tuple (a, state), state) -> List a
const unfold = (f, init) =>
f (init) .match
( { Nothing: () => []
, Just: (t) => [ t.first, ...unfold (f, t.second) ] // <-- Tuple
}
)
// fibseq : Int -> List Int
const fibseq = init =>
unfold
( ([ n, a, b ]) =>
n === 0
? Nothing ()
: Just (Tuple (a, [ n - 1, b, a + b ])) // <-- Tuple
, [ init, 0, 1 ]
)
console .log (fibseq (10))
// [ 0, 1, 1, 2, 3, 5, 8, 13, 21, 34 ]