views:

141

answers:

2

This isn't necessarily a Scala question, it's a design question that has to do with avoiding mutable state, functional thinking and that sort. It just happens that I'm using Scala.

Given this set of requirements:

  • Input comes from an essentially infinite stream of random numbers between 1 and 10

  • Final output is either SUCCEED or FAIL

  • There can be multiple objects 'listening' to the stream at any particular time, and they can begin listening at different times so they all may have a different concept of the 'first' number; therefore listeners to the stream need to be decoupled from the stream itself.

Pseudocode:

if (first number == 1) SUCCEED
else if (first number >= 9) FAIL
else {
  first = first number
  rest  = rest of stream
  for each (n in rest) {
    if (n == 1) FAIL
    else if (n == first) SUCCEED
    else continue
  }
}

Here is a possible mutable implementation:

sealed trait Result
case object Fail extends Result
case object Succeed extends Result
case object NoResult extends Result

class StreamListener {
  private var target: Option[Int] = None

  def evaluate(n: Int): Result = target match {
    case None =>
      if (n == 1) Succeed
      else if (n >= 9) Fail
      else {
        target = Some(n)
        NoResult
      }

    case Some(t) =>
      if (n == t) Succeed
      else if (n == 1) Fail
      else NoResult
  }
}

This will work but smells to me. StreamListener.evaluate is not referentially transparent. And the use of the NoResult token just doesn't feel right. It does have the advantage though of being clear and easy to use/code. Besides there has to be a functional solution to this right?

I've come up with 2 other possible options:

  • Having evaluate return a (possibly new) StreamListener, but this means I would have to make Result a subtype of StreamListener which doesn't feel right.

  • Letting evaluate take a Stream[Int] as a parameter and letting the StreamListener be in charge of consuming as much of the Stream as it needs to determine failure or success. The problem I see with this approach is that the class that registers the listeners should query each listener after each number is generated and take appropriate action immediately upon failure or success. With this approach, I don't see how that could happen since each listener is forcing evaluation of the Stream until it completes evaluation. There is no concept here of a single number generation.

Is there any standard Scala/FP idiom I'm overlooking here?

+1  A: 

I don't know Scala, but e.g. in Haskell lists are lazy and can represent 'streams', and 'call by need' ensures the list/stream is only evaluated as far as needed (and each cell only once).

In F#, in the PowerPack there is a LazyList module that behaves the same way. That is, you can define an infinite stream of values, pattern match on it like a head/rest list, only evaluate 'as needed', and different consumers can have different 'pointers' into it at different locations (like an immutable head/rest list, only the evaluation effects are synchronized for the most-advanced 'pointer' on the 'frontier' of evaluation).

So I think you just need the right 'data structure' like a lazy list, and the rest falls out naturally. (Side note: your function as spec'd can get stuck in an infinite loop (non-termination), which is a kind of effect maybe.)

Brian
You're right, there is non-termination is possible, but not likely unless the random number generator doesn't actually return a uniformly distributed stream of numbers. Or did you mean something else?
Garrett Rowe
+4  A: 

Considering your first possible option, I'm not sure why you would make Result a sub-type of StreamListener instead of making just the specific sub-types of Result that are relevant be StreamListeners.

sealed trait Result
sealed trait FinalizedResult extends Result

trait StreamListener {
  def evaluate(n: Int): Result
}

case object Uninitialized extends Result with StreamListener {
  def evaluate(n: Int): Result = {
    n match {
      case i if (n == 1) => Succeed
      case i if (n >= 9) => Fail
      case _ => Initialized(n)
    }
  }
}

case class Initialized(target: Int) extends Result with StreamListener {
  def evaluate(n: Int): Result = {
    n match {
      case i if (n == target) => Succeed
      case i if (n == 1) => Fail
      case _ => this
    }
  } 
}

case object Succeed extends FinalizedResult
case object Fail extends FinalizedResult

But then, aren't you just shuffling the mutability up to the calling code to track the references to Results/StreamListeners?

Mitch Blevins
Thanks. I like this solution a lot. What do you mean by shifting mutability up to the calling code? I figured that the calling code could maintain a list of StreamListeners, and map the results of calling evaluate on each one, then partition the resulting list into (FinalizedResults, EverythingElse), handle the Successes/Failures then create a new state out of itself with the unresolved Listeners + newly registered listeners. I'll have to play with it for a while to see if I run into problems wit this approach.
Garrett Rowe
I didn't think through the calling code very far. Sounds like you got a good approach to it.
Mitch Blevins