An example from «Learning concurrent programming in Scala»:
package org.learningconcurrency
import java.io.File
import java.util.concurrent.atomic.AtomicReference
import java.util.concurrent.{ConcurrentHashMap, ForkJoinPool, LinkedBlockingQueue}
import org.apache.commons.io.FileUtils
import scala.annotation.tailrec
/**
* Created by kaiyin on 1/19/16.
*/
import org.learningconcurrency.ch3.Ch3.execute
import scala.collection.convert.decorateAsScala._
object Issue {
sealed trait State
class Idle extends State
class Creating extends State
class Copying(val n: Int) extends State
class Deleting extends State
class Entry(val isDir: Boolean) {
val state = new AtomicReference[State](new Idle)
}
class FileSystem(val root: String) {
val rootDir = new File(root)
val files: collection.concurrent.Map[String, Entry] = new ConcurrentHashMap[String, Entry]().asScala
for (f <- FileUtils.iterateFiles(rootDir, null, false).asScala)
files.put(f.getName, new Entry(false))
def deleteFile(filename: String): Unit = {
files.get(filename) match {
case None =>
log(s"Path '$filename' does not exist!")
case Some(entry) if entry.isDir =>
log(s"Path '$filename' is a directory!")
case Some(entry) => execute {
if (prepareForDelete(entry))
if (FileUtils.deleteQuietly(new File(filename)))
files.remove(filename)
}
}
}
}
#tailrec private def prepareForDelete(entry: Entry): Boolean = {
val s0 = entry.state.get
s0 match {
case i: Idle =>
if (entry.state.compareAndSet(s0, new Deleting)) true
else prepareForDelete(entry)
case c: Creating =>
log("File is being created, cannot delete")
false
case c: Copying =>
log("File is being created, cannot delete")
false
case d: Deleting =>
false
}
}
def main(args: Array[String]) {
val fs = new FileSystem("/tmp")
fs.files.foreach(println _)
// Thread.sleep(500)
fs.deleteFile("test")
}
}
It just checks the state of one file and then deletes it, and the code looks like it should work, but after running it, the file I have created by touch /tmp/test is still there.
The problem is related to FileUtils.deleteQuietly(new File(filename)) line. According to the docs, deleteQuietly does the same as File.delete(), but instead of raising exceptions, it just returns true if the file was deleted and false if it was not.
Adding a debugging log would show that deleteQuietly is not working:
if (FileUtils.deleteQuietly(new File(filename))) {
println("File was deleted!")
files.remove(filename)
} else {
println("File was NOT deleted!")
}
It should print File was NOT deleted.
So what's wrong? Let's inspect the parameter you passed to deleteQuietly:
println((new File(filename)).exists()) // returns false
It seems that File didn't found your file. What is missing?
Since you are only providing the relative path test, you need also to provide the parent directory:
case Some(entry) => execute {
if (prepareForDelete(entry))
if (FileUtils.deleteQuietly(new File(rootDir, filename)))
files.remove(filename)
}
Related
I have a graph that reads from sqs, writes to another system and then deletes from sqs. In order to delete from sqs i need a receipt handle on the SqsMessage object
In the case of Http flows the signature of the flow allows me to say which type gets emitted downstream from the flow,
Flow[(HttpRequest, T), (Try[HttpResponse], T), HostConnectionPool]
In this case i can set T to SqsMessage and i still have all the data i need.
However some connectors e.g google cloud pub sub emits a completely useless (to me) pub sub id.
Downstream of the pub sub flow I need to be able to access the sqs message id which i had prior to the pub sub flow.
What is the best way to work around this without rewriting the pub sub connector
I conceptually want something a bit like this:
Flow[SqsMessage] //i have my data at this point
within(
.map(toPubSubMessage)
.via(pubSub))
... from here i have the same type i had before within however it still behaves like a linear graph with back pressure etc
You can use PassThrough integration pattern.
As example of usage look on akka-streams-kafka -> Class akka.kafka.scaladsl.Producer -> Mehtod def flow[K, V, PassThrough]
So just implement your own Stage with PassThrough element, example inakka.kafka.internal.ProducerStage[K, V, PassThrough]
package my.package
import java.util.concurrent.atomic.AtomicInteger
import scala.concurrent.Future
import scala.util.{Failure, Success, Try}
import akka.stream._
import akka.stream.ActorAttributes.SupervisionStrategy
import akka.stream.stage._
final case class Message[V, PassThrough](record: V, passThrough: PassThrough)
final case class Result[R, PassThrough](result: R, message: PassThrough)
class PathThroughStage[R, V, PassThrough]
extends GraphStage[FlowShape[Message[V, PassThrough], Future[Result[R, PassThrough]]]] {
private val in = Inlet[Message[V, PassThrough]]("messages")
private val out = Outlet[Result[R, PassThrough]]("result")
override val shape = FlowShape(in, out)
override protected def createLogic(inheritedAttributes: Attributes) = {
val logic = new GraphStageLogic(shape) with StageLogging {
lazy val decider = inheritedAttributes.get[SupervisionStrategy]
.map(_.decider)
.getOrElse(Supervision.stoppingDecider)
val awaitingConfirmation = new AtomicInteger(0)
#volatile var inIsClosed = false
var completionState: Option[Try[Unit]] = None
override protected def logSource: Class[_] = classOf[PathThroughStage[R, V, PassThrough]]
def checkForCompletion() = {
if (isClosed(in) && awaitingConfirmation.get == 0) {
completionState match {
case Some(Success(_)) => completeStage()
case Some(Failure(ex)) => failStage(ex)
case None => failStage(new IllegalStateException("Stage completed, but there is no info about status"))
}
}
}
val checkForCompletionCB = getAsyncCallback[Unit] { _ =>
checkForCompletion()
}
val failStageCb = getAsyncCallback[Throwable] { ex =>
failStage(ex)
}
setHandler(out, new OutHandler {
override def onPull() = {
tryPull(in)
}
})
setHandler(in, new InHandler {
override def onPush() = {
val msg = grab(in)
val f = Future[Result[R, PassThrough]] {
try {
Result(// TODO YOUR logic
msg.record,
msg.passThrough)
} catch {
case exception: Exception =>
decider(exception) match {
case Supervision.Stop =>
failStageCb.invoke(exception)
case _ =>
Result(exception, msg.passThrough)
}
}
if (awaitingConfirmation.decrementAndGet() == 0 && inIsClosed) checkForCompletionCB.invoke(())
}
awaitingConfirmation.incrementAndGet()
push(out, f)
}
override def onUpstreamFinish() = {
inIsClosed = true
completionState = Some(Success(()))
checkForCompletion()
}
override def onUpstreamFailure(ex: Throwable) = {
inIsClosed = true
completionState = Some(Failure(ex))
checkForCompletion()
}
})
override def postStop() = {
log.debug("Stage completed")
super.postStop()
}
}
logic
}
}
I'm beginning to using Scala and the AKKA pattern, and i have wrote this code, but it doesn't work and i don't know why...
I have created a little project that read user input from the console.
when this user have wrote a 'keyword', the keyWord Actor (Child) will interpret it and will communicate with the console Actor (Grand parent).
the action Actor will be use to broadcast and do some more stuff.
When i enter the command 'rename' in the console Actor, i enter into the action Actor and after that in the keyWord Actor and enter in the Rename Method, but after that nothing, i didn't enter into the rename method on the console Actor.
Can you help me ?
If you saw any wrong pratice, please don't hesite to tell me how to resolve that :).
Thank you !
Main
import ConsoleActor._
import akka.actor.ActorSystem
object Main extends App {
val system = ActorSystem("My-Little-IRC")
val consoleActor = system.actorOf(ConsoleActor.props, "consoleActor")
consoleActor ! ReadConsoleInput
system.terminate()
}
consoleActor
import ActionActor.TreatInputUser
import akka.actor.{Actor, Props}
import scala.io.StdIn
object ConsoleActor {
case class ReadConsoleInput()
case class StopLoop()
case class Rename()
case class WhoIAm()
def props = Props[ConsoleActor]
}
class ConsoleActor() extends Actor {
val keyWordActor = context.actorOf(KeyWordActor.props(this.self), "keyWordActor")
val actionActor = context.actorOf(ActionActor.props(keyWordActor), "actionActor")
var currentUser: String = ""
var loop: Boolean = true;
import ConsoleActor._
def isValidString( str: String ): Boolean = {
var isValid: Boolean = false
if (str != null && !str.trim().isEmpty)
isValid = true
isValid
}
def initiatePresentation( ) = {
println("Hi ! Who are you ?")
currentUser = StdIn.readLine()
println(s"Nice to meet you ${currentUser}, I'm your console app !")
}
def receive = {
case ReadConsoleInput => {
initiatePresentation
var value = ""
while (loop) {
println("Yes ?")
value = StdIn.readLine()
if (isValidString(value)) {
actionActor ! TreatInputUser(value)
}
}
}
case StopLoop => {
println("stop Loooop !")
loop = false
}
case Rename => {
println(s"${currentUser} was a good name ! Which is your new name ?")
currentUser = StdIn.readLine()
println(s"Nice to meet you -again- ${currentUser}")
}
case WhoIAm =>{
println(s"I'm ${currentUser}")
}
}
}
actionActor
import ActionActor.TreatInputUser
import akka.actor.{Actor, ActorRef, Props}
import akka.util.Timeout
import scala.concurrent.duration._
import akka.pattern.ask
import scala.concurrent.Await
object ActionActor {
case class TreatInputUser(string: String)
def props(keyWordActor: ActorRef) = Props(new ActionActor(keyWordActor))
}
class ActionActor(keyWordActor: ActorRef) extends Actor {
import KeyWordActor._
def receive = {
case TreatInputUser(string) => {
implicit val timeout = Timeout(5 seconds)
var isKeyWord = keyWordActor ? IsKeyWord(string)
val isKeyWordResult = Await.result(isKeyWord, timeout.duration).asInstanceOf[ Boolean ]
println(isKeyWordResult)
if (isKeyWordResult) {
keyWordActor ! HandleKeyWord(string)
}
else {
println("bla bla bla")
}
}
}
}
keyWord actor
import ConsoleActor.{Rename, StopLoop, WhoIAm}
import akka.actor.{Actor, ActorRef, Props}
object KeyWordActor {
case class IsKeyWord(key : String)
case class HandleKeyWord(key : String)
def props(consoleActor: ActorRef) = Props(new KeyWordActor(consoleActor))
}
class KeyWordActor(consoleActor: ActorRef) extends Actor {
import KeyWordActor._
val KeysWord : Map[ String, ()=> Any] = Map("rename" -> renameFunction, "stop" -> stopFunction, "42" -> uselessfunction, "john doe ?" -> AmIJohnDoe)
def renameFunction() = {
println("here")
consoleActor ! Rename
}
def stopFunction() = {
consoleActor ! StopLoop
}
def uselessfunction() = {
println("useless")
}
def AmIJohnDoe() ={
consoleActor ! WhoIAm
}
def receive = {
case IsKeyWord(key) => {
sender ! KeysWord.contains(key.toLowerCase)
}
case HandleKeyWord(key) => {
if (KeysWord.contains(key.toLowerCase)) {
KeysWord(key.toLowerCase)()
}
}
}
}
You must not block in the receive method. The way you wrote it (with a while loop), the initial ReadConsoleInput message never finishes processing, and any subsequent messages (like StopLoop) will sit untouched in the Actor mailbox forever.
If you must selectively read from StdIn (as opposed to just continuously reading in e.g. your Main class) then one approach could be to change your ConsoleActor so that when it receives a ReadConsoleInput message, it should just try to do StdIn.readLine once, and forward the result to the ActionActor. Since the StdIn.readLine call itself is also blocking, you should do it asynchronously. The "pipe" pattern comes in handy here:
import akka.pattern.pipe
import scala.concurrent.Future
//...
def receive = {
case ReadConsoleInput =>
import context.dispatcher //provide a thread pool to do async work
Future(StdIn.readLine()) //read a line of input asynchronously
.filter(isValidString) //only continue if line is valid
.map(TreatInputUser) //wrap the (valid) String in a message
.pipeTo(actionActor) //forward it
case Rename => ...
}
This way, the ConsoleActor immediately becomes available again to process new messages, while your ActionActor will receive a TreatInputUser message whenever the user finishes typing a line in the console.
You can apply the same pattern inside your ActionActor, instead of relying on Await.
If you want to close the loop so you can continue sending messages, I'd use behaviour to ensure that two StdIn.readLine calls are not interfering.
How would I test that a given behavior sends the messages I expect?
Say, three messages of some type, one after the other...
With regular actors (untyped) there was the TestProbe from regular Akka with methods like expectedMsg:
http://doc.akka.io/api/akka/current/index.html#akka.testkit.TestProbe
With akka-typed I'm scratching my head still. There is something called EffectfulActorContext, but I've no idea how to use that.
Example
Say I am writing a simple PingPong service, that given a number n replies with Pong(n) n-times. So:
-> Ping(2)
Pong(2)
Pong(2)
-> Ping(0)
# nothing
-> Ping(1)
Pong(1)
Here is how this behavior might look:
case class Ping(i: Int, replyTo: ActorRef[Pong])
case class Pong(i: Int)
val pingPong: Behavior[Ping] = {
Static {
case Ping(i, replyTo) => (0 until i.max(0)).map(_=> replyTo ! Pong(i))
}
}
My Hack
Now since I can't figure out how to make this work, the "hack" that I am doing right now is making the actor always reply with a list of responses. So the behavior is:
case class Ping(i: Int, replyTo: ActorRef[List[Pong]])
case class Pong(i: Int)
val pingPong: Behavior[Ping] = {
Static {
case Ping(i, replyTo) => replyTo ! (0 until i.max(0)).map(_=>Pong(i)).toList
}
}
Given this hacky change, the tester is easy to write:
package com.test
import akka.typed.AskPattern._
import akka.typed.ScalaDSL._
import akka.typed.{ActorRef, ActorSystem, Behavior, Props}
import akka.util.Timeout
import com.test.PingPong.{Ping, Pong}
import org.scalatest.{FlatSpec, Matchers}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
import scala.concurrent.{Await, Future}
object PingPongTester {
/* Expect that the given messages arrived in order */
def expectMsgs(i: Int, msgs: List[Pong]) = {
implicit val timeout: Timeout = 5 seconds
val pingPongBe: ActorSystem[Ping] = ActorSystem("pingPongTester", Props(PingPong.pingPong))
val futures: Future[List[Pong]] = pingPongBe ? (Ping(i, _))
for {
pongs <- futures
done <- {
for ((actual, expected) <- pongs.zip(msgs)) {
assert(actual == expected, s"Expected $expected, but received $actual")
}
assert(pongs.size == msgs.size, s"Expected ${msgs.size} messages, but received ${pongs.size}")
pingPongBe.terminate
}
} Await.ready(pingPongBe.whenTerminated, 5 seconds)
}
}
object PingPong {
case class Ping(i: Int, replyTo: ActorRef[List[Pong]])
case class Pong(i: Int)
val pingPong: Behavior[Ping] = {
Static {
case Ping(i, replyTo) => replyTo ! (0 until i.max(0)).map(_=>Pong(i)).toList
}
}
}
class MainSpec extends FlatSpec with Matchers {
"PingPong" should "reply with empty when Pinged with zero" in {
PingPongTester.expectMsgs(0, List.empty)
}
it should "reply once when Pinged with one" in {
PingPongTester.expectMsgs(1, List(Pong(1)))
}
it should "reply with empty when Pinged with negative" in {
PingPongTester.expectMsgs(-1, List.empty)
}
it should "reply with as many pongs as Ping requested" in {
PingPongTester.expectMsgs(5, List(Pong(5), Pong(5), Pong(5), Pong(5), Pong(5)))
}
}
I'm using EffectfulActorContext for testing my Akka typed actors and here is an untested example based on your question.
Note: I'm also using the guardianactor provided in the Akka-typed test cases.
class Test extends TypedSpec{
val system = ActorSystem("actor-system", Props(guardian()))
val ctx: EffectfulActorContext[Ping] = new EffectfulActorContext[Ping]("ping", Ping.props(), system)
//This will send the command to Ping Actor
ctx.run(Ping)
//This should get you the inbox of the Pong created inside the Ping actor.
val pongInbox = ctx.getInbox("pong")
assert(pongInbox.hasMessages)
val pongMessages = pongInbox.receiveAll()
pongMessages.size should be(1) //1 or whatever number of messages you expect
}
Edit (Some more info): Cases where I need to add a replyTo ActorRef in my messages I do the following:
case class Pong(replyTo: ActorRef[Response])
val responseInbox: SyncInbox[Response] = Inbox.sync[Response]("responseInbox")
Pong(responseInbox.ref)
My initial approach to testing was to extend Behavior class
class TestQueueBehavior[Protocol] extends Behavior[Protocol] {
val messages: BlockingQueue[Protocol] = new LinkedBlockingQueue[Protocol]()
val behavior: Protocol => Unit = {
(p: Protocol) => messages.put(p)
}
def pollMessage(timeout: FiniteDuration = 3.seconds): Protocol = {
messages.poll(timeout.toMillis, TimeUnit.MILLISECONDS)
}
override def management(ctx: ActorContext[Protocol], msg: Signal): Behavior[Protocol] = msg match {
case _ ⇒ ScalaDSL.Unhandled
}
override def message(ctx: ActorContext[Protocol], msg: Protocol): Behavior[Protocol] = msg match {
case p =>
behavior(p)
Same
}
}
then I could call behavior.pollMessage(2.seconds) shouldBe somethingToCompareTo which was very similar to using TestProbe.
Although I think EffectfulActorContext is the right way to go, unfortunately couldn't figure out how to properly use it.
It is quite clear what the loop below accomplishes. I somewhat think that it could be made more scala-esque, but I can't quite see it. I'm posting this to see if anyone has more inspiration than me.
var bunnies: List[Bunny] = List.fill(nBunniesAtStart)(generateInitialBunny)
var doContinue = true
while (doContinue) {
val prevBunnies = bunnies
bunnies = evolveOneYear(bunnies)
print(bunnies, prevBunnies)
if (bunnies.isEmpty) {
println("\n\n\n No more bunnies...")
doContinue = false
} else {
println("Hit Enter to continue, or q + Enter to quit.\n")
doContinue = readLine.isEmpty()
}
}
I wrote this code as an answer to a codereview post.
EDIT:
thanks to #wingedsubmariner and #Kigyo, I have this alternative:
val startBunnies: List[Bunny] = List.fill(nBunniesAtStart)(generateInitialBunny)
userInputLoop(startBunnies, "")
#tailrec
def userInputLoop(bunnies: List[Bunny], userInput: String): Unit = {
if (userInput.nonEmpty) println("Exiting")
else evolveOneYear(bunnies) match {
case Nil =>
print(Nil, bunnies)
println("No more bunnies...")
case newBunnies =>
print(newBunnies, bunnies)
userInputLoop(newBunnies, readLine())
}
}
Or
val startBunnies: List[Bunny] = List.fill(nBunniesAtStart)(generateInitialBunny)
userInputLoop(startBunnies)
#tailrec
def userInputLoop(prevBunnies: List[Bunny]): Unit = {
evolveOneYear(prevBunnies) match {
case Nil =>
print(Nil, prevBunnies)
println("No more bunnies...")
case bunnies =>
print(bunnies, prevBunnies)
if (readLine().nonEmpty) println("Exiting.")
else userInputLoop(bunnies)
}
}
EDIT 2:
Another attempt, build from some ideas of Chris Martin and Ben Kovitz:
class NoMoreBunniesException extends Exception("No more bunnies...")
class UserStoppageException extends Exception("Exiting at your request.")
def doesUserWantToContinue(): Try[_] = {
println("Hit Enter to continue, or q + Enter to quit.\n");
if (readLine().isEmpty) Success() else Failure(new UserStoppageException)
}
def validateBunnies(bunnies: List[Bunny]): Try[_] = {
if (bunnies.isEmpty) Failure(new NoMoreBunniesException)
else Success()
}
def checkNotEmptyAndUserContinuation(bunnies: List[Bunny]): Try[_] =
validateBunnies(bunnies).flatMap(_ => doesUserWantToContinue)
val firstBunnies = List.fill(nBunniesAtStart)(generateInitialBunny)
println(s"${buildBunniesReport(firstBunnies).toString}\n\n")
val timeline = Stream.iterate(firstBunnies)(evolveOneYear)
val timelineWithPrev = timeline.tail.zip(timeline)
val statusAndReportTimeline = timelineWithPrev.map {
case (bunnies, prevBunnies) =>
(checkNotEmptyAndUserContinuation(bunnies), buildFullReport(bunnies, prevBunnies))
}
// main loop including user interaction
statusAndReportTimeline.takeWhile {
case (Success(_), _) => true
case (Failure(e), report) => { println(s"${report}\n\n${e.getMessage}"); false }
}.foreach { case (_, report) => println(report) }
Here's a solution that's more functional (perhaps also more abstruse):
val firstBunnies = List.fill(nBunniesAtStart)(generateInitialBunny)
val timeline = Stream.iterate(firstBunnies)(evolveOneYear)
val inputLines = Source.fromInputStream(System.in).getLines()
timeline.zip(timeline.tail).iterator
.takeWhile({ case (previousBunnies, bunnies) => previousBunnies.nonEmpty })
.zip(Iterator.single("") ++ inputLines)
.takeWhile({ case (_, input) => input.isEmpty })
.map({ case ((previousBunnies, bunnies), _) =>
(bunnies, previousBunnies) + (
if (bunnies.isEmpty) "No more bunnies..."
else "Hit Enter to continue, or q + Enter to quit."
)
})
.foreach(println)
You can make it more idiomatic scala by using a tail-recursive function instead of a while loop and eliminiating the vars:
import scala.annotation.tailrec
val startBunnies = List.fill(nBunniesAtStart)(generateInitialBunny)
#tailrec
def loop(prevBunnies: List[Bunny]): Unit = {
val bunnies = evolveOneYear(prevBunnies)
print(bunnies, prevBunnies)
if (bunnies.isEmpty) {
println("\n\n\n No more bunnies...")
} else {
println("Hit Enter to continue, or q + Enter to quit.\n")
if (readLine.isEmpty)
loop(bunnies)
}
}
loop(startBunnies)
// Separate the functional logic ...
val firstBunnies = List.fill(nBunniesAtStart)(generateInitialBunny)
val timeline = Stream.iterate(firstBunnies)(evolveOneYear)
for ((previousBunnies, bunnies) <- timeline zip timeline.tail) {
// ... from the I/O.
print(bunnies, previousBunnies)
if (bunnies.isEmpty) {
println("No more bunnies...")
return
} else {
println("Hit Enter to continue, or q + Enter to quit.")
}
if (readLine().nonEmpty) return
}
Here's yet another way to do it, aiming for clarity and simplicity.
import util.control.Breaks._
val bunniesStream: Stream[List[String]] = firstBunnies #:: bunniesStream.map(evolveOneYear)
breakable {
for (bunnies <- bunniesStream) {
println(bunnies)
if (bunnies.isEmpty) {
println("No more bunnies...");
break
} else {
println("Hit Enter to continue, or q + Enter to quit.\n");
if (!readLine().isEmpty) break
}
}
}
The use of breakable suggests that this is not idiomatic Scala code.
The more I consider this, the more I'm bothered by the idea of blocking on the InputStream over a human timescale. So here's an approach using Akka!
The setup:
import akka.actor.{Actor, ActorRef, ActorSystem, Props}
// The actor system
val system = ActorSystem()
// The actors: a bunny farm, and a console to interact with it
val farm = system.actorOf(Props[BunnyFarm])
system.actorOf(Props(classOf[BunnyConsole], farm))
// Keep alive until the actor system terminates
system.awaitTermination()
The bunny farm:
object BunnyFarm {
// Define the messages that a bunny farm uses
case object Advance
case class Result(bunnies: Seq[Bunny], previousBunnies: Seq[Bunny])
}
class BunnyFarm extends Actor {
import BunnyFarm._
// A bunny farm's state consists of a list of its bunnies
var bunnies = List.fill(nBunniesAtStart)(generateInitialBunny)
def receive = {
case Advance =>
// Advance the state of the farm one year
val previousBunnies = bunnies
bunnies = evolveOneYear(bunnies)
// Reply to the sender with the result
sender ! Result(bunnies = bunnies, previousBunnies = previousBunnies)
}
}
The console interface:
class BunnyConsole(farm: ActorRef) extends Actor with akka.camel.Consumer {
// Read from stdin
def endpointUri = "stream:in"
// Initially advance the farm once
farm ! BunnyFarm.Advance
def receive = {
case m: akka.camel.CamelMessage => self forward m.bodyAs[String]
// Each string message represents a line of user input
case s: String => s match {
case "" => farm ! BunnyFarm.Advance
case _ => quit()
}
// When the bunny farm sends a result...
case r: BunnyFarm.Result =>
println(s"Previous bunnies: ${r.previousBunnies}")
println(s"New bunnies: ${r.bunnies}")
if (r.bunnies.nonEmpty) {
println("Hit Enter to continue, or q + Enter to quit.")
} else {
println("No more bunnies...")
quit()
}
}
// Terminate the actor system, thus halting the program
def quit() = context.system.shutdown()
}
Dependencies:
com.typesafe.akka:akka-actor
com.typesafe.akka:akka-camel
org.apache.camel:camel-stream
Edit - The same solution refactored for brevity.
Setup:
import akka.actor.{Actor, ActorSystem, Props}
val system = ActorSystem()
system.actorOf(Props(classOf[BunnyConsole]))
system.awaitTermination()
Console:
class BunnyConsole extends Actor with akka.camel.Consumer {
def endpointUri = "stream:in"
var bunnies = List.fill(nBunniesAtStart)(generateInitialBunny)
advance()
def receive = {
case m: akka.camel.CamelMessage => m.bodyAs[String] match {
case "" => advance()
case _ => quit()
}
}
def advance() {
val previousBunnies = bunnies
bunnies = evolveOneYear(bunnies)
print(bunnies, previousBunnies)
if (bunnies.nonEmpty) {
println("Hit Enter to continue, or q + Enter to quit.")
} else {
println("No more bunnies...")
quit()
}
}
def quit() = context.system.shutdown()
}
Here's yet another approach. It's horrible. I post it here in the hope that it will inspire someone to re-do its essential idea in a more readable or at least conventional way.
val bunniesStream = Stream.iterate(firstBunnies)(evolveOneYear)
case class Interaction(bunnies: List[String]) {
lazy val print: Unit = println(bunnies)
lazy val continue: Boolean = {
print
if (bunnies.isEmpty) { println("No more bunnies..."); false }
else userOK
}
lazy val userOK: Boolean = {
println("Hit Enter to continue, or q + Enter to quit.\n");
readLine().isEmpty
}
}
bunniesStream.map(Interaction).takeWhile(_.continue).force
The idea that I'm trying to implement here is to get the user's input by lazy evaluation. What makes it hard to do the loop in a functional style is the fact that you don't know when to stop the loop until after you've read the user's input, but you might need to stop the loop before reading the user's input.
The last line bundles all the input and output into an expression. Without .force, that expression should evaluate to an object which you can then pass to other functions as you like. This seems to follow the spirit of functional programming. No input or output should happen until you do a .force. Except it does, because there's something fundamentally wrong with my approach. I don't quite know what it is. Maybe the error has something to do with my mixing decision-making with input/output in the Interaction class.
I have some (Akka) actor code that is using a case class + the copy constructor to update state:
def foo(state:StateCaseClass) : Receive = {
import state._
{
case Bar(updates) =>
context become foo(copy(/* change a limited number of things */))
// ... other message processing w/ lots of context become foo(copy(...))
}
}
I'd like to add below the import
def update = context become foo(copy(_))
so that the code can be
def foo(state:StateCaseClass) : Receive = {
import state._
def update = context become foo(copy(_))
{
case Bar(updates) =>
update(/* change a limited number of things */)
// ... etc
}
}
but that doesn't compile. I can of course tweak the def update a bit to get rid of most of boilerplate, but the copy still sticks around:
def foo(state:StateCaseClass) : Receive = {
import state._
def update(newState:StateCaseClass) = context become foo(newState)
{
case Bar(updates) =>
update(copy(/* change a limited number of things */))
// ... etc
}
}
Is there comparable syntax that will let me pass through the args to the case class copy constructor and dry out that last bit?
Disclaimer: I guess the best solution is to use context become explicitly. And I don't recommend you to use the code below.
I guess it's impossible without metaprogramming (macros). You have to create a method with default values for named parameters.
You could always create such method manually like this:
def update(filed1: Int = state.field1, field2: String = state.field2) =
context become foo(StateCaseClass(filed1, filed2))
...
update(field1 = 0)
...
update(field2 = "str")
But I guess it's not what you want.
The only way to get such method without metaprogramming is... to use method copy itself. Method copy calls constructor and you could call become in constructor.
The code below works, but I strongly don't recommend you to use it! It's a cryptocode and it will confuse all other developers.
import akka.actor._
trait ReceiveHelper extends PartialFunction[Any, Unit] {
def receive: PartialFunction[Any, Unit]
override def apply(v: Any) = receive(v)
override def isDefinedAt(v: Any) = receive isDefinedAt v
}
sealed trait TestActorMessage
case object Get extends TestActorMessage
case class SetInt(i: Int) extends TestActorMessage
case class SetString(s: String) extends TestActorMessage
class TestActor extends Actor {
case class Behaviour(intField: Int, strField: String) extends ReceiveHelper {
context become this
val receive: Receive = {
case Get => sender ! (intField -> strField)
case SetInt(i) => copy(intField = i)
case SetString(s) => copy(strField = s)
}
}
def receive = Behaviour(0, "init")
}
Usage:
val system = ActorSystem("testSystem")
val testActor = system.actorOf(Props[TestActor], "testActor")
import akka.pattern.ask
import akka.util.Timeout
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
implicit val timeout = Timeout(5 seconds)
testActor ? Get foreach println
// (0,init)
testActor ! SetInt(666)
testActor ? Get foreach println
// (666,init)
testActor ! SetString("next")
testActor ? Get foreach println
// (666,next)