关于 struct 当中大量使用 Option 的一些想法

前面尝试类似 JSX 得 DSL 的时候一些想法. 没想明白.

我了解到的现在 Moonbit 文档涉及到关于 JSX 传递大量可选的属性时, 简化写法的语法, 主要是:

optional labelled argument:

fn attrs(~field1: A, ~field2: B) -> S {
  {field1, field1}
}

struct update syntax:

{..base_struct, field1: a, field2: b}

这两个写法, 在 struct 的基础上, 允许简写的方式传入需要设置的属性. 其他属性不传递直接采用默认值.

这里有一个麻烦是, 实际在 JSX 场景当中倾向于大量使用 Option[T]:

struct StyleA {
  font_size: Option[FontSize]
  color: Option[Color]
  font_weight: Option[Number]
  font_style: Option[FontStyle]
  // 大量属性
}

因为界面元素比如样式中经常使用当中有大量的属性 DevDocs — CSS documentation 这些属性也不能说没有 initial value, 但是实际使用当中考虑到 CSS 样式的继承关系, 通常是会省略. 并且在 virtual DOM diff 过程当中, 也有用 None 表示没有 diff 这样一个诉求. 在类型上对应就是 Option::None 的情况了. 另外在 JavaScript DOM Property 去描述的时候, 事件监听器处理时也可能用 None 用于解绑已有监听器.

于是大量会遇到需要用 Option[T] 包裹属性值的情况. 这也就导致了上边的语法, 实际使用当中并没有那么方便, 变成了,

fn attrs(~field1: Option[A], ~field2: Option[B]) -> S {
  {field1, field1}
}
{..base_struct, field1: Some(a), field2: Some(b)}

在 TypeScript 已有的实践当中, 类似 gradual type 的方式(我不了解类型系统内部实现…)使用起来比较简单:

{
  field1?: number,
  field2?: string,
  // ...
}


// 使用时不写默认就是空
{field1: 14}

同时这也导致了 TypeScript 代码当中大量使用 if/else 或者三元表达式去判断, 才能安全使用值.

考虑到这种情况在 Web 开发场景当中出现较多, 我好奇 Moonbit 是否有办法在语法层面对这类经常提供支持, 比如(这个大概比较丑):

{?..base_struct, field1: a, field2: b}

自动转化成 Option::Some 的处理方式, 同时也在类型系统中要求 base_struct 中每个值都用 Option[T] 包裹,

{..base_struct, field1: Some(a), field2: Some(b)}

这个语法或者类似的 struct 在函数传递配置的时候也会经常用到, 所以应该也算一个频率不低的功能. 而其他场景当中, 未必会完全用 Option[T] 修饰所有的参数. 因而不满足限定.

对照 JSX 的使用方式, 还有一个想法是对于所有 value 都是 None 的情况, 能否把内存占用压缩到极小. 但是实际这是运行时的行为, 对于编译时需要确定内存体积的语言, 又显得过于苛求了? 即便有办法, 也仍然需要浪费 CPU 访问属性的性能才能节省这个空间. 归根到底这是 TypeScript 或者 ReScript 语言当中的用法. 即便换一个思路, 内部用动态类型去实现, 只是接口上提供类型, 方便类型提示和 pattern matching 使用, 也可能干扰到 Moonbit 已有的其他功能.

抛出来看看是否有类似的需求或者提供新方案的可能性?

还是推荐包一层函数,你要找的可能是最近引入的另一种新的optional argument

fn image(~width?: Int, ~height?: Int, ~src: String) -> Unit {
  println(width) // Some(n) or None
}

fn main {
  image(width=20, src="path")
}

这里的使用直觉和map pattern里的可选标记?:类似。width和height的类型都是Option[Int], 当在调用处省略时,它们的值是None。

当然如果属性有明确的initial value时,就不需要Option了:

fn image(~width: Int = 0, ~height: Int = 0, ~src: String) -> Unit {
  println(width) // 0
}
2 个赞

这个语法是有帮助的, 至少 DOM 元素标记封装到函数以后, 已经基本上符合预期了. 我回头试一下.