Как использовать scalaz.Reader из Java - PullRequest
0 голосов
/ 04 сентября 2018

У меня есть сервис, написанный на Scala, который использует scalaz.Reader для DI и тест для него.

В тесте определена функция op для составления функций сервиса.

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))
}

Тест:

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)
  }
}

Теперь я пытаюсь написать тот же тест из кода Java. И я даже не могу определить сигнатуру функции op:

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

public class AccountServiceScalazReaderFromJavaTest {

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

Я получаю сейчас:

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

Что мне не хватает? Как реализовать op из Java-кода?

1 Ответ

0 голосов
/ 04 сентября 2018

Scala имеет псевдонимы типов, чего нет в Java. Reader - это не класс, это псевдоним с сопутствующим объектом:

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

Так что я думаю, это было бы что-то ближе к

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

хотя в Java нет HKT, поэтому я предполагаю, что могут быть некоторые пробные ошибки до javac accept Id[_] (тем более что Id[_] также является псевдонимом типа, а не классом).

...