简体   繁体   中英

How to stay true to functional style in Scala for expressions

I've struggled to find a way to stay true to functional style in for expressions when I need to collect multiple parameters of an object into a List.

As an example, say I have a Notification object, which has both a fromId (the user id the notification is from) and an objectOwnerId (the id of the user who created the original object). These can differ in facebook style notifications ("X also commented on Y's post").

I can collect the userIds with a for expression like so

val userIds = for { notification <- notifications } yield notification.fromId

however say I want to collect both the fromIds and the objectOwnerIds into a single list, is there any way to do this in a single for expression without the user of vars?

I've done something like this in the past:

var ids = List()
for {
    notification <- notifications
    ids = ids ++ List(notification.fromId, notification.objectOwnerId)
}
ids = ids.distinct

but it feels like there must be a better way. The use of a var, and the need to call distinct after I complete the collection are both ugly. I could avoid the distinct with some conditionals, but I'm trying to learn the proper functional methods to do things.

Thanks in advance for any help!

For such cases, there is foldLeft:

(notifications foldLeft Set.empty[Id]) { (set, notification) =>
  set ++ Seq(notification.fromId, notification.ownerId)
}

or in short form:

(Set.empty[Id] /: notifications) { (set, notification) =>
  set ++ Seq(notification.fromId, notification.ownerId)
}

A set doesn't hold duplicates. After the fold you can convert the set to another collection if you want.

val userIds = for { 
  notification <- notifications 
  id <- List(notification.fromId, notification.objectOwnerId)
} yield id

Apply distinct afterwards if required. If the id can only be duplicated on a single notification, you can apply distinct on the second generator instead.

当然,不仅仅是产生fromId,而是产生一个元组

val idPairs:List[(String, String)] = for(notification <- notifications) yield(notification.fromId, notification.objectOwnerId)

Well, here is my answer to the following:

How to map from [Y(x1, x2), Y(x3, x4)] to [x1,x2,x3,x4]?

Use flatMap (see Collection.Traversable , but note it's actually first defined higher up).

case class Y(a: Int, b: Int)
var in = List(Y(1,2), Y(3,4))
var out = in.flatMap(x => List(x.a, x.b))

> defined class Y
> in: List[Y] = List(Y(1,2), Y(3,4))
> out: List[Int] = List(1, 2, 3, 4)

Also, since for..yield is filter , map and flatMap in one (but also see "sugar for flatMap?" that points out that this isn't as efficient as it could be: there is an extra map ):

var out = for { i <- in; x <- Seq(i.a, i.b) } yield x

I would likely pick one of the other answers , however, as this does not directly address the final problem being solved.

Happy coding.

You can also use Stream to transform the pairs into a stream of individual items:

def toStream(xs: Iterable[Y]): Stream[Int] = {
  xs match {
    case Y(a, b) :: t => a #:: b #:: toStream(t)
    case _ => Stream.empty
  }
}

But like pst said, this doesn't solve your final problem of getting the distinct values, but once you have the stream it's trivial:

val result = toStream(ys).toList.removeDuplicates

Or a slight modification to the earlier suggestions to use flatten - add a function that turns a Y into a List:

def yToList(y: Y) = List(y.a, y.b)

Then you can do:

val ys = List(Y(1, 2), Y(3, 4))
(ys map yToList flatten).removeDuplicates

I agree with Dave's solution but another approach is to fold over the list, producing your map of id to User object as you go. The function to apply In the fold will query the db for both users and add them to the map being accumulated.

What about simple map ? AFAIK for yield gets converted to series of flatMap and map anyway. Your problem could be solved simply as follows:

notifications.map(n => (n.fromId, n.objectOwnerId)).distinct

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