1

I have a service, written in Scala, that uses scalaz.Reader for DI and a test for it.

In the test the op function is defined, to compose functions of the service.

import scala.util.{Failure, Success, Try}
import scalaz.{Reader => ScalazReader}

trait AccountServiceScalazReader[Account, Amount, Balance] {
  def open(no: String, name: String, openingDate: Option[Date]): ScalazReader[AccountRepository, Try[Account]]
  def close(no: String, closeDate: Option[Date]): ScalazReader[AccountRepository, Try[Account]]
  def debit(no: String, amount: Amount): ScalazReader[AccountRepository, Try[Account]]
  def credit(no: String, amount: Amount): ScalazReader[AccountRepository, Try[Account]]
  def balance(no: String): ScalazReader[AccountRepository, Try[Balance]]
}

object AccountServiceScalazReader extends AccountServiceScalazReader[Account, Amount, Balance] {

  def open(no: String, name: String, openingDate: Option[Date]) = ScalazReader { (repo: AccountRepository) =>
    repo.query(no) match {
      case Success(Some(a)) => Failure(new Exception(s"Already existing account with no $no"))
      case Success(None) =>
        if (no.isEmpty || name.isEmpty) Failure(new Exception(s"Account no or name cannot be blank") )
        else if (openingDate.getOrElse(today) before today) Failure(new Exception(s"Cannot open account in the past"))
        else repo.store(Account(no, name, openingDate.getOrElse(today)))
      case Failure(ex) => Failure(new Exception(s"Failed to open account $no: $name", ex))
    }
  }

  def close(no: String, closeDate: Option[Date]) = ScalazReader { (repo: AccountRepository) =>
    repo.query(no) match {
      case Success(Some(a)) =>
        if (closeDate.getOrElse(today) before a.dateOfOpening)
          Failure(new Exception(s"Close date $closeDate cannot be before opening date ${a.dateOfOpening}"))
        else repo.store(a.copy(dateOfClosing = closeDate))
      case Success(None) => Failure(new Exception(s"Account not found with $no"))
      case Failure(ex) => Failure(new Exception(s"Fail in closing account $no", ex))
    }
  }

  def debit(no: String, amount: Amount) = ScalazReader { (repo: AccountRepository) =>
    repo.query(no) match {
      case Success(Some(a)) =>
        if (a.balance.amount < amount) Failure(new Exception("Insufficient balance"))
        else repo.store(a.copy(balance = Balance(a.balance.amount - amount)))
      case Success(None) => Failure(new Exception(s"Account not found with $no"))
      case Failure(ex) => Failure(new Exception(s"Fail in debit from $no amount $amount", ex))
    }
  }

  def credit(no: String, amount: Amount) = ScalazReader { (repo: AccountRepository) =>
    repo.query(no) match {
      case Success(Some(a)) => repo.store(a.copy(balance = Balance(a.balance.amount + amount)))
      case Success(None) => Failure(new Exception(s"Account not found with $no"))
      case Failure(ex) => Failure(new Exception(s"Fail in credit to $no amount $amount", ex))
    }
  }

  def balance(no: String) = ScalazReader((repo: AccountRepository) => repo.balance(no))
}

Test:

import org.junit.{Assert, Test}

import scala.util.Try

class AccountServiceScalazReaderTest {

  import com.savdev.fp.monad.di.reader.AccountServiceScalazReader._
  def op(no: String):scalaz.Reader[AccountRepository, Try[Balance]]
    = for {
    _ <- credit(no, BigDecimal(100))
    _ <- credit(no, BigDecimal(300))
    _ <- debit(no, BigDecimal(160))
    b <- balance(no)
  } yield b

  @Test def testOpComposition: Unit = {
    val newOp = for {
      _ <- open("a-123", "Alex", Option.empty)
      b <- op("a-123")
    } yield b

    val balance = newOp run (new TestAccountRepository)
    Assert.assertTrue(balance.isSuccess)
    Assert.assertEquals(Balance(240), balance.get)
    println(balance)
  }

  @Test def testOpCompositionNotExistingAccount: Unit = {
    val balance = op("a-123") run (new TestAccountRepository)
    Assert.assertTrue(balance.isFailure)
    Assert.assertEquals("No account exists with no a-123", balance.failed.get.getMessage)
    println(balance)
  }
}

Now I am trying to write the same test from Java code. And I cannot even define a signature of the op function:

import scala.util.Try;
import scalaz.Reader;

public class AccountServiceScalazReaderFromJavaTest {

  scalaz.Reader<AccountRepository, Try<Balance>> op(String no) {
    return null;
  }
}

I am getting now:

AccountServiceScalazReaderFromJavaTest.java:[12,9] cannot find symbol
  symbol:   class Reader
  location: package scalaz

What do I miss? How to implement the op from Java code?

Alexandr
  • 9,213
  • 12
  • 62
  • 102

1 Answers1

1

Scala has type aliases, something that does not exist in Java. Reader is not a class, it is an alias with a companion object:

type Reader[E, A] = ReaderT[Id, E, A]

object Reader extends scala.Serializable {
  def apply[E, A](f: E => A): Reader[E, A] = Kleisli[Id, E, A](f)
}

type ReaderT[F[_], E, A] = Kleisli[F, E, A]

val ReaderT = Kleisli

So I guess it would be something closer to

Kleisli<?, AccountRepository, Try<Balance>> op(String no) {
  return null;
}

though Java does not have HKT, so I guess there might be some trial-and error before javac accept Id[_] (especially since Id[_] is also a type alias, not a class).

Mateusz Kubuszok
  • 24,995
  • 4
  • 42
  • 64