scalaeither

better way to dissolve multiple Either arguments


I have a model, that looks like this:

case class ItemEntity private(
    userId: IdValue,
    title: TitleValue,
    description: DescriptionValue,
    media: MediaValue,
    price: MoneyValue,
    stock: StockValue
)

And in the companion object of this model, is a constructor, that currently looks like this:

object ItemEntity
{
    def fromUnsafe(
        mayBeUserId: Either[IdError, IdValue],
        mayBeTitle: Either[TitleError, TitleValue],
        mayBeDescription: Either[DescriptionError, DescriptionValue],
        mayBeMedia: Either[MediaError, MediaValue],
        mayBePrice: Either[MoneyError, MoneyValue],
        mayBeStock: Either[StockError, StockValue]
    ): Either[ItemError, ItemEntity] =
    {
        mayBeUserId match
            case Right(id) => mayBeTitle match
                case Right(title) => mayBeDescription match
                    case Right(description) => mayBeMedia match
                        case Right(media) => mayBePrice match
                            case Right(price) => mayBeStock match
                                case Right(stock) => Right(
                                    ItemEntity(
                                        userId = id,
                                        title = title,
                                        description = description,
                                        media = media,
                                        price = price,
                                        stock = stock
                                    )
                                )
                                case Left(stockError) => Left(ItemError.Error(stockError))
                            case Left(priceError) => Left(ItemError.Error(priceError))
                        case Left(mediaError) => Left(ItemError.Error(mediaError))
                    case Left(descriptionError) => Left(ItemError.Error(descriptionError))
                case Left(titleError) => Left(ItemError.Error(titleError))
            case Left(idError) => Left(ItemError.Error(idError))
    }
}

As you see, the code has a lot of boilerplate.

But I cannot think of a better way to wirte this constructor...

So my question is:

Is there a better way, to write this?


Solution

  • You can use for-comprehension but first you will have to map or widen your left value to the same type:

    def fromUnsafe(
                    mayBeUserId: Either[IdError, IdValue],
                    mayBeTitle: Either[TitleError, TitleValue],
                    mayBeDescription: Either[DescriptionError, DescriptionValue],
                    mayBeMedia: Either[MediaError, MediaValue],
                    mayBePrice: Either[MoneyError, MoneyValue],
                    mayBeStock: Either[StockError, StockValue]
                  ): Either[ItemError, ItemEntity] =
      for {
        id <- mayBeUserId.left.map(ItemError.Error)
        title <- mayBeTitle.left.map(ItemError.Error)
        description <- mayBeDescription.left.map(ItemError.Error)
        media <- mayBeMedia.left.map(ItemError.Error)
        price <- mayBePrice.left.map(ItemError.Error)
        stock <- mayBeStock.left.map(ItemError.Error)
      } yield ItemEntity(id, title, description, media, price, stock)
    

    If the error types can be widen to the same type then you can also move the left.map after the for-comprehension.

      (for {
        id <- mayBeUserId
        title <- mayBeTitle
        description <- mayBeDescription
        media <- mayBeMedia
        price <- mayBePrice
        stock <- mayBeStock
      } yield ItemEntity(id, title, description, media, price, stock)).left.map(ItemError.Error)
    

    You can also replace left.map with leftMap and/or leftWiden with Cats.