Is there a functional way to init an array in JavaScript ES6?
Asked Answered
M

6

35

I finally gave up and wrote a for loop to initialize a simple array of objects where each object has an incremented counter (id) as an attribute of the object. In other words, I just want:

var sampleData = [{id: 1},{id: 2},...];

I was hoping for a compact syntax I could just put on my return statement.

let sampleData = [];
for (var p = 0; p < 25; p++){
    sampleData.push({id: p});
}

return {
    data: sampleData,
    isLoading: true
};
Mistymisunderstand answered 26/7, 2018 at 1:42 Comment(1)
F
59

Array.from() is a nice way to do this. You can pass a {length: somlength} object or some other array-like object and a function that defines each item. The first argument (calling it _ just to indicate it's not used) to that function would be the item from an array we passed in (but we only passed in a length so it doesn't mean much), the second i is the index, which is used for your id:

let sampleData = Array.from({length: 10}, (_, id) => ({id}))

console.log(sampleData)
Fretful answered 26/7, 2018 at 1:46 Comment(5)
Nice solution @Fretful , I had thought that "underline" was a lodash thing. Any pointers to where I can read more about the "underline" and what it means?Mistymisunderstand
@Pete, what the thing you're asking about it -- looks like a character didn't come through. I added a bit about _ it's just a function argument -- could have called it anything. I use _ sometimes for arguments that will be unused or undefined.Fretful
Yup, meant the underscore. very elegant. I need of course expand it out so I can understand it. I'll check your answer as soon as SO lets me.Mistymisunderstand
@Pete: _ was added because Mark needed second argument only, so generally using _ to skip arguments that you're not needed. Unless you're using lodash of courseWraparound
To add to this, quite a few languages include _ as an actual language feature. It will literally ignore that value. Two examples I can think of off the top of my head would be Rust and Haskell. In Javascript, _ is purely convention and is actually being assigned a value. const a = _ => "hi" + _; is valid code, for instance. It's literally just an identifier. It's to communicate intent rather than to change anything about how it works.Teak
W
19

What I usually do is this:

const data = Array(10).fill().map((v, i) => ({id: i + 1}));
console.log({data});

fill ensures it can be used with map

Wicklow answered 26/7, 2018 at 1:57 Comment(1)
This seems equivalent to JohnP's solution, but this fill() is 10%+ faster than the spread operator (fill() VS ... JSBench.me test), but both of them are 30%+ slower when compared to Mark's Array.from(...) solution.Sized
P
9

You can use spread operator with Array and then map each undefined element to the object that you want.

var arr = [...Array(10)].map((_,i)=>({id:i}));
console.log(arr)
Parasitology answered 26/7, 2018 at 1:55 Comment(3)
Why do you need to use the spread operator? Is it because otherwise you'll be mapping over an empty sparse array, which has 0 items?Agist
Yes, Array(10) just sets the length, it has no items in it.Parasitology
Assuming [...Array(10)] is equivalent to Array(10).fill(), isn't the latter more readable? Admittedly, I don't have a very good intuition for what people find clear in Javascript....Beniamino
H
6

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 ]
Hauck answered 26/7, 2018 at 3:38 Comment(7)
Very nice implementation of an anamorpishm. Unfortunately, ana is sometimes just not enough: tails("ana") yields ["ana", "na", "a", ""]. This cannot be implemented with ana, as Maybe doesn't provide a value for the base case. If you combine Mabye with Either you get an even more general morphism called apo. I picture apo as the big yet kind brother of ana. Happy (un)foldology :DLogan
Thank you kindly, bob. Indeed tails is tricky to implement with ana but it's still possible using a compound state. Thanks for sharing apo. I like exposure to category theory when I can get it; I don't make enough time to explore the dense topic too thoroughly on my own. Other readers that are feeling lost can start by reading about apomorphism.Hauck
Btw, you should totally show me how to write tails using apo :DHauck
This cannot be implemented - usually I don't make such absolute claims. If I learn something new, I get all excited though. Anyway, here is my first rough implementation of tails using apo. You probably don't like it, because it's pretty imperative (trampolines, local mutations) etc., so that I can eventually use it in production.Logan
Recursion schemes without the Fix stuff is totally worth learning. zygo encodes two folds where the latter depends on the former. mutu abstracts mutual recursion also combining two folds. histo gives your algebra access to all intermediate results. futu, well, I am not quite there yet...Logan
Thanks for sharing apo! We can implement cata for your union and add a Loop type. The result is a purely functional apo that is stack-safe. The array concats could be easily swapped with a mutating push function to optimise it a bit. - That aside, I'm confused with the Some("", Left([s])) bit in the example. Why Left([s]) instead of Left(s)?Hauck
I am confused with this very part too. In haskell string ist just [char]. I should drop the array....Logan
C
4

The .from() example is great but if you really want to get creative check this out.

const newArray = length => [...`${Math.pow(10, length) - 1}`]
newArray(2)
newArray(10)

Massively limited though

newArray(1000)
["I", "n", "f", "i", "n", "i", "t", "y"]
Cholera answered 26/7, 2018 at 2:0 Comment(1)
This doesn't really answer the question, but in the spirit of fun and creativity have a +1.Nagoya
B
3

You can use a simple recursive process to do that.

const iter = (arr, counter) => {
  if (counter === 25) return arr;
  return iter([...arr, {id:counter}], counter + 1)
}
iter([], 0)
Beanpole answered 2/8, 2018 at 19:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.