For { } yieldCaseClass

yieldCaseClass

val greeks = for {
  alpha <- intMonad
  beta <- longMonad
  gamma <- doubleMonad
  delta <- stringMonad
} yieldCaseClass Greek

would desugar to

case class Greek(alpha: Int, beta: Long, gamma: Double, delta: String)

val greeks = for {
  alpha <- intMonad
  beta <- longMonad
  gamma <- doubleMonad
  delta <- stringMonad
} yield Greek(alpha, beta, gamma, delta)

yieldClass

val greeks = for {
  alpha <- intMonad
  beta <- longMonad
  gamma <- doubleMonad
  delta <- stringMonad
} yieldClass Greek

would desugar to

class Greek(val alpha: Int, val beta: Long, val gamma: Double, val delta: String)

val greeks = for {
  alpha <- intMonad
  beta <- longMonad
  gamma <- doubleMonad
  delta <- stringMonad
} yield new Greek(alpha, beta, gamma, delta)

yieldObject

val objects = for {
  alpha <- intMonad
  beta <- longMonad
  gamma <- doubleMonad
  delta <- stringMonad
} yieldObject

would desugar to

val objects = for {
  _alpha <- intMonad
  _beta <- longMonad
  _gamma <- doubleMonad
  _delta <- stringMonad
} yield new Object {
  val alpha = _alpha
  val beta = _beta
  val gamma = _gamma
  val delta = _delta
}

You should write some motivation, and a clear proposal, if you want us to consider this.

Personally I am not convinced at all, having never felt the slightest need for this. It is also super-specific. Scala is a language where we like to have few highly general concepts, rather than many specific features. Just the fact that you are suggesting 3 new keywords at once is a bad sign.

3 Likes

Any one of the keywords would serve my purposes. I offered all three in case one was better than the others.

My motivation: creating large scalacheck generator combinators. Is that the only one?

Here is my motivating real world example:

  import org.scalacheck.{Arbitrary, Gen}

  val lastMidnight = org.threeten.bp.LocalDate.now().atStartOfDay(org.threeten.bp.ZoneOffset.UTC).toInstant
  val secondsInDay = Gen.choose[Int](0, 60 * 60 * 24 - 1)

  val objectId = for {
    s <- secondsInDay
    counter <- Gen.choose[Int](0x1, 0xffffff)
  } yield new ObjectId((lastMidnight.getEpochSecond + s).toInt, ObjectId.getGeneratedMachineIdentifier, ObjectId.getGeneratedProcessIdentifier.toShort, counter)

  val nonEmptyAlphaNumStr = Gen.nonEmptyListOf(Gen.alphaNumChar).map(_.mkString)

  val orderTestData = for {
    _secondsInDay <- secondsInDay
    _nanoAdjustment <- Gen.choose[Long](0, 99999999)
    _instantOccurred = org.threeten.bp.Instant.ofEpochSecond(lastMidnight.getEpochSecond + secondsInDay, _nanoAdjustment)
    _counter <- Gen.choose(0x1, 0xffffff)
    _uniqueId = new ObjectId(_instantOccurred.getEpochSecond.toInt, ObjectId.getGeneratedMachineIdentifier, ObjectId.getGeneratedProcessIdentifier.toShort, _counter)
    _externalId <- nonEmptyAlphaNumStr
    _systemInstance <- nonEmptyAlphaNumStr
    _currency <- Gen.oneOf("USD", "GBP", "CAD")
    _text <- Gen.alphaNumStr
    _direction <- Gen.choose[Byte](0, (Direction.names.length - 1).toByte)
    _systemRegion <- Gen.choose[Byte](0, (TradingSystemRegion.names.length - 1).toByte)
    _systemType <- Gen.choose[Byte](0, (TradingSystem.names.length - 1).toByte)
    _counterSystemType <- Gen.choose[Byte](0, (TradingSystem.names.length - 1).toByte)
    _latencyNanos01 <- Gen.choose[Long](10000, 1000000000)
    _latencyNanos02 <- Gen.choose[Long](10000+_latencyNanos01, 1000000000+_latencyNanos01)
    _instantSent = if (_direction == Direction.IN) _instantOccurred.plusNanos(_latencyNanos01) else _instantOccurred.plusNanos(_latencyNanos02)
    _instantRecv = if (_direction == Direction.IN) Option(_instantOccurred.plusNanos(_latencyNanos02)) else None
    _orderType <- Gen.oneOf(OrderType.LIMIT, OrderType.MARKET)
    _isChild = if (_direction == Direction.OUT) Option(()) else None
    _orderId <- Arbitrary.arbitrary[Long]
    _parentOrderId <- _isChild.fold(Gen.const(None: Option[Long]))(_ => Arbitrary.arbitrary[Long].map(Option.apply))
    _parentOrderUniqueId <- _isChild.fold(Gen.const(None: Option[String]))(_ => nonEmptyAlphaNumStr.map(Option.apply))
  } yield new Object {
    val instantOccurred = _instantOccurred
    val uniqueId = _uniqueId
    val externalId = _externalId
    val systemInstance = _systemInstance
    val currency = _currency
    val text = _text
    val direction = _direction
    val systemRegion = _systemRegion
    val systemType = _systemType
    val counterSystemType = _counterSystemType
    val instantSent = _instantSent
    val instantRecv = _instantRecv
    val orderType = _orderType
    val isChild = _isChild
    val orderId = _orderId
    val parentOrderId = _parentOrderId
    val parentOrderUniqueId = _parentOrderUniqueId
  }

Its about to grow by another 50 entries and I am looking for a way to reduce the boiler plate.

It looks like you want to combine several applicatives to a single tuple or something like this. Monadic for does not seem to be the best fitting for this case.

@buzden

I am creating test input that will be used to populate a flatbuffer.

The code under test will then transform the flatbuffer into a protocol buffer.

I wanted type safe access to the generated input values to compare with the field values of the protocol buffer.

I have a loose grasp on the difference between applicatives and monads, but I am not quite able to make the leap to writing the boiler plate free code those concepts imply.

What is this Object? Why don’t you break it into smaller pieces?

I would look into Shapeless Generic. It would be really nice if there was a
shorthand macro or something to make it easier to write such Gens. That
said, it’s different than derivation like in Circe semiauto because you
often need to define specific fields specifically.

@nafg
The object is a type safe “Map” of the test input data.
I don’t think I can do this with shapeless because of the interdependence of the field values
e.g.

  • instantOccurred depends on nanoAdjustment and secondsInDay
  • uniqueId depends on instantOccurred
  • instantSent depends on direction, instantOccurred, latencyNanos01, and latencyNanos02.

I think that’s a non-sequiteur – I don’t see why, eg, a Shapeless record would have more problems than slamming it into an Object the way you’re doing, and I suspect the resulting type would be easier to work with. (But I will admit that I don’t have a lot of hands-on Shapeless experience.)