MMM 7
2024/01/15
Paideia and Squall
距离上一篇 MMM 已经快五年了。
这五年人生,在我看来,绝对算不上什么特别光彩的生活。失败接着失败。于是,在今天,2024/01/18,夜晚,我决定重新开始拾起 Liu.ms。本着多写一点的原则,先把这周的 MMM 补上,下周一再给一篇。希望这次能坚持的久一点。
Paideia
Paideia 是我去年九月底加入的一个项目。当时,我申请本科的导师,周弋涵先生,来到玉鸟集,和我约饭。本来天真的以为他只是来叙叙旧,没想到是想忽悠我免费做一个 AI 面试项目。
进入项目之后,我摸鱼了整整一个月,啥事没干。但突然有一天,我灵感迸发,想到了一些小技巧,能让 ChatGPT 更稳定切题地和我们聊天。于是我莫名其妙变成了项目技术的半边天。又过了一段时间,为了赶在一个活动上刷出 demo,我花两个晚上肝了一整个网站的前后端。于是乎,我莫名其妙就成了 CTO。
有句古话怎么说来着?
人呐,就都不知道……
装逼结束。
Paideia 是一个 AI 面试的 startup。我们的目标是做一个 AI 面试官,可以评估出面试者的通用认知能力。技术方面有很多暂时还不能分享的地方,希望有一天可以说说。但我们在很多层面,是在使用 GPT-4 的基础上,通过 gradient-free 的方法,复兴古典的人工智能规则系统。效果出奇地好。
Paideia 现在是我的主业。我应该会在未来很长一段时间,持续分享它。所以,不妨回归 MMM 的记录本质。在过去的七天,主要干了三件事:
- 在北京,我们参加了奇绩的面试。
- 我们敲定了面试产品的评估部分的基本框架。
奇绩。这是我第二次看陆奇拷打人。上一次是 Benote,我被拷打。这次则是周弋涵。被套路两次之后,我终于后知后觉地总结出了一些奇绩面试的规律,但碍于保密的原因,也不方便讲。Benote 是我回国笔记 app 创业的最终形态,失败的很惨,以后有机会再说,也许还能复活。
评估。AI 面试很自然地分成两部分,一个是对话,给面试者表演的空间;一个是评估,评估套出来的话。我们很早就定下了对话系统的大致方案,但对于评估,效果一直不好。这次,我又熬夜肝了一点点,做出了一个效果不好,但是结构还行的版本。未来数周,我们的 prompt 团队要逐渐改善它。
Squall
Squall 是我的前合伙人,Octree,在他的新公司帮我揽下的外包项目。是一个 TypeScript 项目,上线前我不方便讲细节。说一点别的。
首先,是财务逐渐独立。在过去的几年里,我回国立刻用家里的钱进行创业。在烧干了之后,接下来的快两年的时间里,我一直很惭愧的在啃老,靠着零花钱过日子。Paideia 和 Squall,为我带来了人生以来第一次比较客观的收入。财务相对独立了起来。这一两个月,极大地缓解了我过去数年的啃老所带来的自卑感,整个人精神了很多。
其次,说点技术吧。Squall 是我几年以来,第一次接手的复杂前端项目。这几年的 iOS 开发,让我成为了 Swift 和 Composable Architecture 的拥趸。在我接手 Squall 的瞬间,我就开始撸 TypeScript Composable Architecture 的框架,但效果不好。后来,我冷静地学习了一下这个项目默认使用的依赖管理库,叫做 Jotai,居然出乎意料地可以完成 composable 的要求。
Jotai 里核心的概念叫做 atom。下面的例子来自官网:
import { atom } from ‘jotai’
const priceAtom = atom(10)
const messageAtom = atom(‘hello’)
const productAtom = atom({ id: 12, name: ‘good stuff’ })
这里的 atom 属于一个 spec,而不是值本身的存储。Jotai 提供了 useAtom
,在一些黑科技的作用下,同一个 atom 在不同的 useAtom
的调用下,会绑定到同一个数据源上。甚至,这些 atom 往往是全局变量,不需要用 React 的 Context 去管理,方便了很多。
我们可以给 atom 自由地定义 getter/setter:
const readOnlyAtom = atom((get) => get(priceAtom) * 2)
const writeOnlyAtom = atom(
null, // it’s a convention to pass `null` for the first argument
(get, set, update) => {
// `update` is any single value we receive for updating this atom
set(priceAtom, get(priceAtom) - update.discount)
// or we can pass a function as the second parameter
// the function will be invoked,
// receiving the atom’s current value as its first parameter
set(priceAtom, (price) => price - update.discount)
},
)
const readWriteAtom = atom(
(get) => get(priceAtom) * 2,
(get, set, newPrice) => {
set(priceAtom, newPrice / 2)
// you can set as many atoms as you want at the same time
},
)
注意:
- getter 允许我们 scope 到更小的 state;当然,Jotai 官方似乎更偏向于我们把好几个 state 组合成更大的 state。
- setter 中,
update/newPrice
这个参数的类型是由我们自己来决定的,所以我们可以做成一个 action。
这足够我们做一个低配的「TSCA」:我们可以在子领域上实现一些小的 state/action 组合,然后通过 product/sum type 把 state/action 给拼起来。接着,把大 action 「route」到更小的 reduce 函数上。最后,我们可以通过 read/write atom,来实现 scope 的效果。
缺点:
- 副作用管理就别想了。
- Jotai 只能处理 scope,不能做 TCA 的 pullback,对偶的部分需要自己弄。
- Jotai 官方似乎更倾向于我们将多个 atom 拼起来,而不是像 TCA 一样,使用一个巨大的 store 管理所有信息。
不过因为 Squall 项目本身的原因,这个 pattern 并没有帮我整理多少代码,项目有点向 Redux 退化。后面堆 Paideia 的前端,或许可以把我的 TSCA 给做下去。