You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何重构以在case语句返回不同类型并执行Doobie查询

Doobie中根据条件执行不同查询且返回不同Either Right类型的实现方案

问题描述

需要根据匹配条件执行不同的Doobie查询,这些查询返回的Either的Right类型可能不同(比如有的返回SomeType1,有的返回SomeType2)。原代码尝试在EitherT.fromEither的match分支中返回ConnectionIO[Either[String, _]],但存在类型不匹配的问题,且不在意函数最终返回类型(当前为List[User],可改为Unit或其他类型)。

原代码示例:

def blah(locationId: Int): ConnectionIO[Either[String, List[User]]] = 
(for {
    usersToUpdate <- EitherT.fromEither[ConnectionIO] (
       UserDao.getUsersByLocation(locationId) match {
           case Nil => 
        Left("...")
           case first :: _ => 
        SomeDao.blah2(..) // ConnectionIO[Either[String, SomeType1]]
           case ... => 
        SomeDao.blah(..) // ConnectionIO[Either[String, SomeType2]]

          }
       }
    )
   updatedUsers <- usersToUpdate.traverse { EitherT(_) }
} yield updatedUsers).value

问题分析

原代码存在两个核心问题:

  1. EitherT.fromEither接收的是纯Either值,但分支中返回的是ConnectionIO[Either[String, _]],类型不兼容;
  2. 不同match分支的Either Right类型不同(SomeType1/SomeType2),无法自动统一为同一类型,导致编译失败。

可行实现方案

方案1:统一返回为Unit(推荐,符合“不在意返回类型”的需求)

既然不在乎最终返回类型,可以将所有分支的Right结果都映射为Unit,统一返回类型为ConnectionIO[Either[String, Unit]]。同时需要先将UserDao.getUsersByLocation的结果lift到EitherT上下文,再进行匹配:

def blah(locationId: Int): ConnectionIO[Either[String, Unit]] = 
  (for {
    // 先获取用户列表,lift到EitherT上下文
    users <- EitherT.liftF(UserDao.getUsersByLocation(locationId))
    result <- users match {
      case Nil => 
        // 返回左值,指定Right类型为Unit
        EitherT.leftT[ConnectionIO, Unit]("No users found for the location")
      case first :: _ => 
        // 执行blah2,将Right的SomeType1映射为Unit
        EitherT(SomeDao.blah2(...).map(_.map(_ => ())))
      case otherCase => 
        // 执行blah,将Right的SomeType2映射为Unit
        EitherT(SomeDao.blah(...).map(_.map(_ => ())))
    }
  } yield result).value

方案2:定义公共父类型保留类型信息

如果需要保留不同分支的结果类型信息,可以定义一个密封特质作为所有结果类型的父类,将不同分支的结果包装为该特质的子类,从而统一类型:

// 定义公共父类型
sealed trait QueryResult
case class ResultType1(value: SomeType1) extends QueryResult
case class ResultType2(value: SomeType2) extends QueryResult

def blah(locationId: Int): ConnectionIO[Either[String, QueryResult]] = 
  (for {
    users <- EitherT.liftF(UserDao.getUsersByLocation(locationId))
    result <- users match {
      case Nil => 
        EitherT.leftT[ConnectionIO, QueryResult]("No users found for the location")
      case first :: _ => 
        // 将SomeType1包装为ResultType1
        EitherT(SomeDao.blah2(...).map(_.map(ResultType1(_))))
      case otherCase => 
        // 将SomeType2包装为ResultType2
        EitherT(SomeDao.blah(...).map(_.map(ResultType2(_))))
    }
  } yield result).value

方案3:使用Any(不推荐,丢失类型安全)

如果完全不在意类型安全,可以直接将Right类型统一为Any,但这种方式会失去Scala的类型检查优势,仅作为临时应急方案:

def blah(locationId: Int): ConnectionIO[Either[String, Any]] = 
  (for {
    users <- EitherT.liftF(UserDao.getUsersByLocation(locationId))
    result <- users match {
      case Nil => 
        EitherT.leftT[ConnectionIO, Any]("No users found for the location")
      case first :: _ => 
        EitherT(SomeDao.blah2(...).map(_.asInstanceOf[Either[String, Any]]))
      case otherCase => 
        EitherT(SomeDao.blah(...).map(_.asInstanceOf[Either[String, Any]]))
    }
  } yield result).value

内容的提问来源于stack exchange,提问作者Blankman

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 06:20:23