简体   繁体   中英

Why doesn't reduce seem to fit when converting from JS to Scala

I have the following JS code...

const collection=["Thing1","Thing2"];
const doSomething = (value)=>{
  switch(value){
    case "Thing1":
      return {
        arrayItems: ["Object 1", "Object 2"]
      }
      break;
    default:
      return {
        arrayItems: ["Object 3", "Object 4"]
      }
  }
}
const result = collection.reduce(
  (result, value)=> result.concat(doSomething(value).arrayItems),
  []
);
console.log(result);

// doSomething returns { "arrayItems": ["Object 1", "Object 2"] } for Thing1 
// and { "arrayItems": ["Object 3", "Object 4"] } for Thing 2
// result should be ["Object 1", "Object 2","Object 3", "Object 4"]

jsfiddle

I would now like to turn this into a similar Scala reduce like this...

val collection = ["Thing 1", "Thing 2"]
val result: Array[...] = collection
                                     .reduce(
    (array: Array[...], value: String) => {
  val objectList = service.doSomething(value)
  if (array != null && array.length > 0) {
    array.concat(objectList)
  } else {
    objectList
  }
});
// doSomething returns { "arrayItems": ["Object 1", "Object 2"] } for Thing1 
// and { "arrayItems": ["Object 3", "Object 4"] } for Thing 2
// result should be ["Object 1", "Object 2","Object 3", "Object 4"]

But when I try this I get...

type mismatch;
 found   : (Array[...], String) => Array[...]
 required: (java.io.Serializable, java.io.Serializable) => java.io.Serializable
    val result: Array[...] = doSomething(value).reduce((array: Array[...], value: String)=>{

Is there a way to do this?

Update

I was asked for a background of what I am trying to accomplish, so the basic steps of what I am trying to accomplish are...

  1. Take an array of strings
  2. Run a reduce function on those strings that produces a Collection of Objects
  3. In the reduce function we use the string to call a service which provides a collection of objects
  4. These objects are then concatenated with the reduce value and returned

Updates per Answers

Thanks guys I didn't know about foldLeft till you brought it up and still working a bit to understand. First here is the working for loop I am trying to avoid...

var array : Array[BaseObject] = Array[BaseObject]()
collection.foreach((value: String) => {
  val objectList = doSomething(value).arrayItems
  array = array.concat(objectList)
});

I tried this...

val array: List[BaseObject] = 
collection.foldLeft(List.empty[BaseObject]){
  (myList, value) => {
    val list : List[BaseObject] = List.from(doSomething(value).arrayList)
    myList ++ list
  }
};

But something little must be wrong because when I parse with gson toJson , I get....

{"head":{...},"next":{}}

Where head is one of the Objects but not the whole set.

Prefer Using FoldLeft as you want to accumulate String s over a List .

I assumed that your ... are String in the following code:

val result: List[String] = myCollection.foldLeft(List.empty[String]){ (myList, value) => {
    val newObject = service.doSomething(value)
    myList :+ newObject
  }
};

Also:

  • Prefer using immutable collections over mutable ones.
  • Usage of null is strongly discouraged in Scala. If your List is empty, that won't even be executed.

You can also use placeholders to shorten your code, which will gradually become:

val result: List[String] = myCollection.foldLeft(List.empty[String]){ (myList, value) => {
    myList :+ service.doSomething(value)
  }
};

then with placeholders

val result: List[String] = myCollection.foldLeft(List.empty[String]){ (_, _) => _ :+ service.doSomething(_)
};

The main problem is that reduce will return a value of the same type as the one contained in the collection. So, if you have an Array of Strings then the result of reduce will be a String (oh well, any supertype of String , that is why in this case that strange Serializable appears) .
There is a more general version of reduce which will allow you to provide any type as output, which is foldLeft that requires an initial value.

Using that, and Lists instead of Arrays we can write this:

def concat(input: List[String])(f: String => List[String]): List[String] =
  input.foldLeft(List.empty[String]) {
    case (acc, str) => acc ++ f(str)
  }

However, appending to a List is somewhat inefficient, so we can rewrite it like this to improve performance.
We will be prepending all results and then we will reverse the list to get the output in the expected order:

def concat(input: List[String])(f: String => List[String]): List[String] =
  input.foldLeft(List.empty[String]) {
    case (acc, str) => f(str) reverse_::: acc
  }.reverse

However, this is a very general operation. We have a collection of some type A and a function that returns collections of some type B and we want to apply that function to all elements of the original collection and group the results into a single collection of type B .
That is the job of flatMap (which btw it is even more generic; because it works for anything that is a Monad , not only a collection) .

def concat(input: List[String])(f: String => List[String]): List[String] =
  input.flatMap(f)

I would recommend you to take a look to the Scaladoc as well as follow some tutorial, to learn more about all the operations the stdlib provides.
Also, please do not hesitate in asking any questions.


You can see the code running here .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM