more sugaring in for comprehensions

119 views
Skip to first unread message

Simon Schäfer

unread,
Apr 11, 2013, 10:37:21 AM4/11/13
to scala-l...@googlegroups.com
What I have at the moment:

def execute(xs: Seq[Int], abort: Boolean, debug: Boolean): Seq[Int] = {
if (abort) return Nil
val as = xs map (_+1)

if (debug) println("debug")

if (abort) return Nil
val bs = as map (_+1)

if (abort) return Nil
val cs = bs map (_+1)

as ++ cs
}

In my real application `abort` is set by another thread and whenever it
is set to true control flow should be return as fast as possible.

The problem I have with this code is that I need to remember the
condition between calculations and that the early return looks ugly.

I tried to simplify it with the for-comprehension but I'm not happy with
the result:

def execute(xs: Seq[Int], abort: Boolean, debug: Boolean): Seq[Int] = {
def abortEarly[A](f: => A): Option[A] =
if (abort) None else Some(f)

val ret = for {
as <- abortEarly { xs map (_+1) }

_ = if (debug) println("debug")

bs <- abortEarly { as map (_+1) }

cs <- abortEarly { bs map (_+1) }
} yield as ++ cs

ret getOrElse Nil
}

I still need to remember the `abortEarly`. Making it to an implicit def
didn't work because `bs <- as map (_+1)` correctly typechecks, therefore
the compiler doesn't look for implicits.

What I really want to write is something like this:

val ret = for[abortEarly] {
as = xs map (_+1)

_ = if (debug) println("debug2")

bs = as map (_+1)

cs = bs map (_+1)
} yield ab ++ cs

where `as = xs map (_+1)` desugars to `as <- abortEarly(xs map (_+1))`
in the context of for[abortEarly], but it is not valid syntax.

So what to do? Does someone have an idea how I can solve my problem
elegantly?

Aleksey Nikiforov

unread,
Apr 11, 2013, 2:22:46 PM4/11/13
to scala-l...@googlegroups.com
This looks like a fold on a list of functions, with an extra termination condition. You could capture this pattern as follows:

 def chain[T](conitnue: => Boolean)(init: T, abnormal: T)(s: T => T*) :T = {
    var last = init
    for (step <- s) {
      if (!conitnue) return abnormal
      last = step(last)
    }
    last
  }
  
  def main(args: Array[String]) {
    val debug = true
    var abort = false
    val xs = List(1, 2, 3, 4)
    
    val res = chain(!abort)(xs, Nil)(
        _ map (_ + 1),
        xs => { if (debug) println("debug"); xs },
        _ map (_ + 1),
        _ map (_ + 1)
      )
      
    println(res.mkString(", "))
  }




--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-language+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.



Ryan Hendrickson

unread,
Apr 11, 2013, 5:27:26 PM4/11/13
to scala-l...@googlegroups.com
> What I really want to write is something like this:
>
> val ret = for[abortEarly] {
> as = xs map (_+1)
>
> _ = if (debug) println("debug2")
>
> bs = as map (_+1)
>
> cs = bs map (_+1)
> } yield ab ++ cs

Aleksey's solution won't give you this, because you want to have access to all of the variables you've bound in your for-comprehension (in order to compute as ++ cs at the end, for instance), which means the data you're passing around is not just one type. Try this; I think it has the properties you want:

val ret = (for {
_ <- new Abortable(abort).start
as = xs map (_+1)

_ = if (debug) println("debug2")

bs = as map (_+1)

cs = bs map (_+1)
} yield as ++ cs) getOrElse Nil


class Abortable(shouldAbort: => Boolean) {
trait OpList[A] {
def fold[Z](z: => Z)(f: A => Z): Z
}

class OpNil[A](value: A) extends OpList[A] {
def fold[Z](z: => Z)(f: A => Z) = f(value)
}

class OpCons[A, B](op: A => B, tail: OpList[A]) extends OpList[B] {
def fold[Z](z: => Z)(f: B => Z) = tail.fold(z) { a =>
if (shouldAbort) z else f(op(a))
}
}

class Computation[A](opList: OpList[A]) {
def map[B](op: A => B): Computation[B] = new Computation(new OpCons(op, opList))

def getOrElse(orElse: => A): A = opList.fold(orElse)(identity)
}

lazy val start: Computation[Unit] = new Computation(new OpNil(()))
}







(please forgive me my corporate legal disclaimer)

----------------------------------------

This message is intended exclusively for the individual(s) or entity to
which it is addressed. It may contain information that is proprietary,
privileged or confidential or otherwise legally exempt from disclosure.
If you are not the named addressee, you are not authorized to read,
print, retain, copy or disseminate this message or any part of it.
If you have received this message in error, please notify the sender
immediately by e-mail and delete all copies of the message.

John Nilsson

unread,
Apr 11, 2013, 6:01:15 PM4/11/13
to scala-l...@googlegroups.com
How about something along the lines of

  def execute(xs: Seq[Int], abort: Boolean, debug: Boolean): Seq[Int] = {
    val as = xs.view map (_+1)

    val bs = as map (_+1)
    val cs = bs map (_+1)
    (as ++ cs).takeWhile(_ => !abort)
  }

BR,
John


Simon Schäfer

unread,
Apr 11, 2013, 6:20:31 PM4/11/13
to scala-l...@googlegroups.com

On 04/12/2013 12:01 AM, John Nilsson wrote:
How about something along the lines of

  def execute(xs: Seq[Int], abort: Boolean, debug: Boolean): Seq[Int] = {
    val as = xs.view map (_+1)
    val bs = as map (_+1)
    val cs = bs map (_+1)
    (as ++ cs).takeWhile(_ => !abort)
  }

I thought about lazily evaluate all values but for my real applications this is not applicable in general. First, this would lead to huge refactorings because most functions doing computations expecting and returning sequences and not single values and secondly Sets and Maps are used too and not only Seqs. Probably this would lead to additional steps in filtering out only distinct values on same places.

BR,
John


To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.


--
You received this message because you are subscribed to the Google Groups "scala-language" group.
To unsubscribe from this group and stop receiving emails from it, send an email to scala-languag...@googlegroups.com.

Ryan Hendrickson

unread,
Apr 11, 2013, 6:28:36 PM4/11/13
to scala-l...@googlegroups.com
> Aleksey's solution won't give you this, because you want to have access
> to all of the variables you've bound in your for-comprehension (in order
> to compute as ++ cs at the end, for instance), which means the data
> you're passing around is not just one type. Try this; I think it has the
> properties you want:

Shoot, this was slightly too good to be true. Compiler optimizes all the bindings into one map call. Please disregard. :-(

Simon Schäfer

unread,
Apr 11, 2013, 6:39:36 PM4/11/13
to scala-l...@googlegroups.com

On 04/12/2013 12:28 AM, Ryan Hendrickson wrote:
>> Aleksey's solution won't give you this, because you want to have access
>> to all of the variables you've bound in your for-comprehension (in order
>> to compute as ++ cs at the end, for instance), which means the data
>> you're passing around is not just one type. Try this; I think it has the
>> properties you want:
> Shoot, this was slightly too good to be true. Compiler optimizes all the bindings into one map call. Please disregard. :-(
Yes, after thinking "man, this is awesome" I noticed it some minutes ago
too. Maybe with flatMap?

Ryan Hendrickson

unread,
Apr 11, 2013, 8:25:33 PM4/11/13
to scala-l...@googlegroups.com
> On 04/12/2013 12:28 AM, Ryan Hendrickson wrote:
> >> Aleksey's solution won't give you this, because you want to have access
> >> to all of the variables you've bound in your for-comprehension (in order
> >> to compute as ++ cs at the end, for instance), which means the data
> >> you're passing around is not just one type. Try this; I think it has the
> >> properties you want:
> > Shoot, this was slightly too good to be true. Compiler optimizes all the bindings into one map call. Please disregard. :-(
> Yes, after thinking "man, this is awesome" I noticed it some minutes ago
> too. Maybe with flatMap?

With flatMap, you have exactly the solution you worked out with Options. (Come to think of it, my solution is way overkill; Computation could be a simple wrapper around Option with a map that calls filter and then map on the underlying Option value, and that would have worked equally well in the counterfactual world where the compiler doesn't take advantage of the functor laws to consolidate the bindings.)

Ah well. It would have been a terrific abuse of syntax.

How do you feel about continuations? Just for fun, here's another abuse of language features that doesn't really solve your repetition problem, but at least looks cool!

import scala.util.continuations._

def abortable[A](shouldAbort: => Boolean)(body: (=> Unit @cps[Option[A]]) => (A @cps[Option[A]])): Option[A] = {
def checkForAbort: Unit @cps[Option[A]] = shift { (k: (Unit => Option[A])) =>
if (shouldAbort)
None
else
k(())
}
reset0(Some(body(checkForAbort)))
}

val ret = abortable[Seq[Int]](abort) { --- =>
val as = xs map (_+1)
---
if (debug) println("debug")
val bs = as map (_+1)
---
val cs = bs map (_+1)
---
as ++ cs
} getOrElse Nil

If I ever wrote this in production code, though, I'm pretty sure I'd get tarred and feathered by the rest of my team. And I'd probably deserve it.

Aleksey Nikiforov

unread,
Apr 11, 2013, 8:39:56 PM4/11/13
to scala-l...@googlegroups.com
Since we are posting solutions that can get you feathered by the rest of the team, here is a rather ugly fix to my original solution:

  import scala.reflect._
  def chain[T : ClassTag](conitnue: => Boolean)(init: T, abnormal: T)(accum: Array[T] => T)(s: T => T*) :T = {
    var list = init :: Nil
    for (step <- s) {
      if (!conitnue) return abnormal
      list = step(list.head) :: list
    }
    accum(list.reverse.toArray)
  }
  
  def main(args: Array[String]) {
    val debug = true
    var abort = false
    val xs = List(1, 2, 3, 4)
    
    val result = chain(!abort)(xs, Nil)(
        steps => steps(0) ++ steps(4)
      )(
        _ map (_ + 1),
        xs => { if (debug) println("debug"); xs },
        _ map (_ + 1),
        _ map (_ + 1)
      )
      
    println(result.mkString(", "))
  }

Simon Schäfer

unread,
Apr 12, 2013, 6:25:48 PM4/12/13
to scala-l...@googlegroups.com
This code has the problem that it is not possible to name the
intermediate results. And I don't like how it looks, I believe I have to
feather myself when I use this. ;)

Simon Schäfer

unread,
Apr 12, 2013, 6:35:37 PM4/12/13
to scala-l...@googlegroups.com

On 04/12/2013 02:25 AM, Ryan Hendrickson wrote:
> How do you feel about continuations? Just for fun, here's another abuse of language features that doesn't really solve your repetition problem, but at least looks cool!
I have never used continuations, actually I don't understand them. This
is another reason to give them a try. But because of these --- in the
code it doesn't look as the hoped ultimate solution.

Maybe for GSOC someone picks out the for-comprehension project. If that
is the case I will forward this thread to the elected people, just as an
idea to think about if it is for general interest.
Reply all
Reply to author
Forward
0 new messages