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

Scala refined库:如何安全拼接NonEmptyString无需unsafeFrom方法?

问题

我正在使用refined库中的NonEmptyString类型。当拼接两个字符串(其中至少一个为非空)时,结果显然也是一个非空字符串。但有没有办法在不使用NonEmptyString.unsafeFrom这类不安全方法的前提下,让Scala编译器认可这一事实?

解决方案

可以通过以下几种安全方式实现,完全不需要依赖不安全的unsafeFrom操作:

1. 给NonEmptyString扩展安全拼接方法

利用refined的编译期宏refineMV,给NonEmptyString和普通String编写扩展方法。因为已知NonEmptyString本身非空,拼接任何字符串后的结果必然非空,宏会在编译期直接验证这一事实,生成合法的NonEmptyString实例,无运行时检查开销。

import eu.timepit.refined._
import eu.timepit.refined.types.string.NonEmptyString
import eu.timepit.refined.collection.NonEmpty
import scala.language.implicitConversions

// NonEmptyString拼接普通String
extension (nes: NonEmptyString) {
  def +(s: String): NonEmptyString = refineMV[NonEmpty](nes.value + s)
}

// 普通String拼接NonEmptyString
extension (s: String) {
  def +(nes: NonEmptyString): NonEmptyString = refineMV[NonEmpty](s + nes.value)
}

// 使用示例
val hello: NonEmptyString = refineV[NonEmpty]("Hello").right.get // 安全初始化
val world = " World"
val combined: NonEmptyString = hello + world // 编译通过,无unsafe操作

2. 编译期约束推导(处理至少一个非空的场景)

如果你的场景是两个字符串都可能为空,但能保证至少一个非空,可以用refined的Or逻辑约束定义类型条件,让编译器在编译期检查该条件,只有满足时才允许生成NonEmptyString。

import eu.timepit.refined._
import eu.timepit.refined.types.string.NonEmptyString
import eu.timepit.refined.collection.NonEmpty
import eu.timepit.refined.logical.Or

// 定义约束:两个字符串中至少一个非空
type AtLeastOneNonEmpty = NonEmpty Or NonEmpty

def concatSafe(a: String, b: String)(implicit ev: Refined[(String, String), AtLeastOneNonEmpty]): NonEmptyString = {
  refineMV[NonEmpty](a + b)
}

// 合法调用(至少一个非空)
val valid1 = concatSafe("Hello", "")
val valid2 = concatSafe("", "World")

// 非法调用(两个都为空)会触发编译错误
// val invalid = concatSafe("", "")

3. 直接使用refineV的编译期推导

如果能在代码中明确证明拼接后的字符串非空(比如其中一个是已知的NonEmptyString),可以直接用refineV的编译期推导,它会自动验证并返回Right(NonEmptyString),无需运行时检查。

import eu.timepit.refined._
import eu.timepit.refined.types.string.NonEmptyString
import eu.timepit.refined.collection.NonEmpty

val nes: NonEmptyString = refineV[NonEmpty]("foo").right.get
val s = "bar"

// 编译期推导拼接结果非空,直接获取Right值
val combined: NonEmptyString = refineV[NonEmpty](nes.value + s).right.get

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 16:22:46