Skip to content

独立API#

注意

自 optics-ts v2.2.0 起,光学有两种语法:方法链独立光学。有关它们之间的差异的更多信息,请参阅两种语法

实验性

此模块是实验性的,可能会接收到向后不兼容的更改,而不会有相应的 semver bump。

以下所有内容都假定有以下导入:

import * as O from 'optics-ts/standalone'

TypeScript 类型#

通常,光学的 TypeScript 类型看起来像 Optic<C, A, T, R>

  • C 是光学类型作为字符串,例如 'Lens''Prism'

  • AT 编码光学的读写方向转换为 "更高级别的" 类型

  • Rtrue 如果光学是可移除的,否则是 undefined

在下面,我们为了清晰起见,省略了确切的类型签名,而使用了一种手摇式的伪语法。光学描述试图明确光学和函数的操作方式。

感兴趣的读者可以参考 hkt.ts 来看看 更高级别的类型/部分应用类型运算符实际上是如何实现的。

操作#

这些函数本身不是光学,而是在光学和数据上操作。

compose#

compose :: (optic, ...optics) => Optic

组合光学以创建更复杂的光学。使用字符串作为光学是prop的简写。

示例:

const fooBar = O.compose('foo', O.optional, 'bar')

O.preview(fooBar, { foo: undefined })
// undefined

O.preview(fooBar, { foo: { bar: 5 } })
// 5

get#

get :: (optic, source) => value
get :: (optic) => (source) => value

通过 EquivalenceIsoLensGetter 读取值。

示例:

O.get(O.pick('foo', 'baz'), { foo: 1, bar: 2, baz: 3 })
// { foo: 1, baz: 3 }

preview#

preview :: (optic, source) => value | undefined
preview :: (optic) => (source) => value | undefined

通过 PrismTraversalAffineFoldFold 读取值。对于 PrismAffineFold,如果光学不匹配(没有焦点),则返回 undefined。对于 TraversalFold,返回第一个焦点的值,或者如果没有焦点,则返回 undefined

示例:

O.preview(O.optional, 1)
// 1

O.preview(O.elems, [])
// undefined

collect#

collect :: (optic, source) => value[]
collect :: (optic) => (source) => value[]

通过 PrismTraversalAffineFoldFold 读取所有焦点值。 对于 PrismAffineFold,返回值是一个包含 0 或 1 个元素的数组。 对于 TraversalFold,返回值是一个包含零个或多个元素的数组。

示例:

O.collect(O.optional, 1)
// [1]

O.collect(O.elems, [])
// []

modify#

modify :: (optic, fn, source) => value
modify :: (optic) => (fn, source) => value
modify :: (optic) => (fn) => (source) => value

通过 EquivalenceIsoLensPrismTraversal 修改焦点值。返回 source 的更新副本,所有焦点都通过映射它们通过函数 fn 进行修改。

示例:

O.modify(O.prop('foo'), (value) => value.length, { foo: 'bar' })
// { foo: 3 }

set#

签名:

set :: (optic, newValue, source) => value
set :: (optic) => (newValue, source) => value
set :: (optic) => (newValue) => (source) => value

通过 EquivalenceIsoLensPrismTraversal 写入常量值。返回 source 的更新副本,所有焦点都被 newValue 替换。

示例:

O.set(O.prop('foo'), null, { foo: 'bar' })
// { foo: null }

remove#

remove :: (optic, source) => value
remove :: (optic) => (source) => value

从其包含的容器中删除 RemovablePrism 的焦点。

示例:

O.remove(O.at(1), [1, 2, 3])
// [1, 3]

同构#

iso#

iso :: (there: (v) => u, back: (u) => v) => Iso

从函数 thereback 创建一个同构。there 接受焦点并将其转换为另一个值。backthere 的逆函数。

注意,iso 是单态的,即你不能在写入时更改值类型。还没有多态的替代品(尚未)。

示例:

const sep = O.iso(
    (value: string) => value.split(','),
    (arr: string[]) => arr.join(',')
)

O.get(sep, 'foo,bar,baz')
// ['foo', 'bar', 'baz']

O.modify(sep, (arr) => [...arr].reverse(), 'foo,bar,baz')
// 'baz,bar,foo'

indexed#

indexed :: Iso

只适用于数组。

indexed 是从值数组到索引-值对数组的同构,即从 [a, b, ...][[0, a], [1, b], ...]

在写入方向,元素按索引排序,只保留重复索引的最后一个。

示例:

O.get(O.indexed, ['a', 'b', 'c'])
// [[0, 'a'], [1, 'b'], [2, 'c']] 

O.set(O.compose(O.indexed, O.at(1), O.nth(0)), 3, ['a', 'b', 'c'])
// ['a', 'c', 'b']

Lenses#

prop#

prop :: (key) => Lens

创建一个聚焦于对象属性 key 的透镜。

你也可以简单地将字符串传递给 compose,而不是使用 prop

示例:

O.set(O.prop('foo'), 42, { foo: null })
// { foo: 42 }

请参阅 atKey 了解在记录上工作的类似棱镜。

nth#

nth :: (n) => Lens

只适用于长度至少为 n + 1元组

创建一个聚焦于元组索引 n 的透镜。这是一个透镜,因为焦点的长度在类型级别上进行检查,所以索引 n 总是已定义的。

请参阅 at 了解在数组上工作的类似棱镜。

pick#

pick :: (...keys) => Lens

创建一个从对象中选择给定属性的透镜。通过透镜写入时,你可以添加或删除属性。

示例:

const data = {
    foo: 'something',
    bar: 42,
    baz: true,
}

O.get(O.pick('foo', 'bar'), data)
// {
//  foo: 'something',
//  baz: true,
// }

O.set(O.pick('foo'), {}, data)
// {
//   bar: 42,
//   baz: true,
// }

O.set(O.pick(), { quux: 'added' }, data)
// {
//   foo: 'something'
//   bar: 42,
//   baz: true,
//   quux: 'added',
// }

filter#

filter :: (fn: (elem) => boolean) => Lens

只适用于数组。

创建一个聚焦于由 fn 匹配的数组元素的透镜。如果 fn 是类型 T 的类型保护,则将焦点的类型缩小为 T[]

写入更长/更短的数组会添加/删除元素。额外的元素被添加到数组的末尾。

示例:

const l = O.filter((x: number) => x % 2 === 1)

O.set(l, ['a', 'b', 'c'], [1, 2, 3, 5, 6])
// ['a', 2, 'b', 'c', 6]

O.set(l, ['a', 'b'], [1, 2, 3, 5, 6])
// ['a', 2, 'b', 6]

O.set(l, ['a', 'b', 'c', 'd', 'e'], [1, 2, 3, 5, 6])
// ['a', 2, 'b', 'c', 6, 'd', 'e']

valueOr#

valueOr :: (defaultValue) => Lens

创建一个透镜,当通过它读取时,如果焦点值为 undefined,则返回 defaultValue。如果焦点不是 undefined,则返回未更改的焦点。

示例:

O.get(O.valueOr(0), undefined)
// 0

O.get(O.valueOr(0), 42)
// 42

O.get(O.compose('maxAge', O.valueOr(100)), { maxAge: undefined })
// 100

partsOf#

partsOf :: (traversal) => Lens

给定一个遍历,创建一个聚焦于遍历焦点的数组的透镜。当通过读取时,结果是一个元素数组,就像由 collect 生成的那样。当通过写入时,遍历的焦点被替换为来自写入数组的值。对于多态写入,遍历的焦点获取写入数组元素的类型。如果写入了更短或更长的数组,会抛出错误。这是为了确保所有焦点都被替换,类型是正确的。

示例:

O.set(
    O.partsOf(O.compose(O.elems, 'foo')),
    ['x', 'y', 'z'],
    [{ foo: 'a' }, { foo: 'b' }, { foo: 'c' }]
)
// [{ foo: 'x' }, { foo: 'y' }, { foo: 'z' }]

O.modify(O.partsOf(O.words), (words) => [...words].reverse(), 'this is a test')
// 'test a is this'

注意,将 partsOf 与设置器(如 appendToprependTo)组合,或通过 partsOf 删除元素将不起作用,因为设置器添加的额外元素或删除的元素将导致 partsOf 抛出错误。

reread, rewrite {#reread,-rewrite}#

reread :: (fn: (value) => value) => Lens
rewrite :: (fn: (value) => value) => Lens

创建一个可以在读方向(reread)或写方向(rewrite)修改值的镜头。这在某些情况下确保数据结构不变性很有用。

rereadrewrite 都是单态的。

示例:

const read = O.reread((x: string) => x.toUpperCase())
const write = O.rewrite((x: string) => x.toUpperCase())

O.get(read, 'foo')
// FOO

O.get(write, 'foo')
// foo

O.set(read, null, 'foo')
// foo

O.set(write, null, 'foo')
// FOO

lens#

lens :: (view: (v) => u, update: (v, u) => v) => Lens

viewupdate 函数创建一个镜头。view 接受当前焦点并返回一个新焦点。update 接受原始焦点和一个值,并用该值更新原始焦点。

注意,lens 是单态的,即你不能在写入时更改值类型。还没有多态的替代品(暂时)。

示例:

const lens = O.lens<number | string, number>(
    (v) => (typeof v === 'string' ? 0 : v),
    (_, u) => u
)

O.get(lens, 100)
// 100

O.get(lens, 'foo')
// 0

Prisms#

optional#

optional :: Prism

如果值不是 undefined,则匹配的棱镜。将类型缩小以删除 undefined,例如 number | undefined 缩小为 number

示例:

const prism = O.compose('foo', O.optional, 'bar')

O.preview(prism, { foo: { bar: 42 } })
// 42

O.preview(prism, { foo: undefined })
// undefined

guard#

guard :: (fn: (value) => boolean) => Prism (单态)
guard :: <F>() => (fn: (value) => boolean) => Prism (多态)

创建一个棱镜,如果值匹配类型保护 fn,则匹配。

第一个,更简单的签名返回一个单态棱镜,只能用于不改变焦点类型的写入。

第二个签名返回一个使用类型转换 F 构造结果类型的多态棱镜。

单态示例:

type Circle = { type: 'circle'; radius: number }
type Square = { type: 'square'; size: number }
type Shape = Circle | Square

function isCircle(value: Shape): value is Circle {
    return value.type === 'circle'
}

const circle = { type: 'circle', radius: 42 }
const square = { type: 'square', size: 10 }

const circleRadius = O.compose(O.guard(isCircle), 'radius')

O.preview(circleRadius, square)
// undefined

O.preview(circleRadius, circle)
// 42

O.set(circleRadius, 99, square)
// { type: 'square', size: 10 }

O.set(circleRadius, 99, circle)
// { type: 'circle', radius: 99 }

多态示例:

type Some<T> = { type: 'some'; value: T }
type None = { type: 'none' }
type Option<T> = Some<T> | None

// O.HKT 是一个 "类型转换" 或 "高阶类型"。this[1] 是输入
// 类型,输出从 prop 0 取得。
//
// SomeF 类型转换只检查 this[1] 中的输入类型是否具有
// Some 的形状,并原样通过。换句话说,写入者可以更改 'value' prop 的类型,但不能更改其他任何东西。
//
interface SomeF extends O.HKT {
    0: this[1] extends Some<any> ? this[1] : never
}

function isSomeNumber(value: Option<number>): value is Some<number> {
    return value.type === 'some'
}

const someValue = O.compose(O.guard<SomeF>()(isSomeNumber), 'value')

const some: Option<number> = { type: 'some', value: 42 }
const none: Option<number> = { type: 'none' }

O.preview(someValue, none)
// undefined

O.preview(someValue, some)
// 42

const result1: Option<string> = O.set(someValue, 'foo', none)
// { type: 'none' }

const result2: Option<string> = O.set(someValue, 'foo', some)
// { type: 'some', value: 'foo' }

at#

at :: (i) => RemovablePrism

只适用于数组和字符串。可移除。

创建一个棱镜,专注于数组的第 n 个元素或字符串的第 n 个字符(长度为 1 的子字符串)。

在字符串上使用时,只能写入字符串。支持写入不同长度的字符串。

示例:

O.preview(O.at(1), ['a', 'b', 'c'])
// 'b'

O.preview(O.at(1), ['a'])
// undefined

O.set(O.at(1), 123, ['a', 'b', 'c'])
// ['a', 123, 'c']

O.set(O.at(1), 123, ['a'])
// ['a']

O.remove(O.at(1), ['a', 'b', 'c'])
// ['a', 'c']

O.preview(O.at(1), 'abc')
// 'b'

O.preview(O.at(1), 'a')
// undefined

O.set(O.at(1), 'X', 'abc')
// 'aXc'

O.set(O.at(1), 'X', 'a')
// 'a'

O.set(O.at(1), 'XYZ', 'abc')
// 'aXYZc'

O.set(O.at(1), '', 'abc')
// 'ac'

O.remove(O.at(1), 'abc')
// 'ac'

head :: Prism

at(0)的简写。

atKey#

atKey :: (key) => RemovablePrism

仅适用于记录(Record<string, T>)。可移除。

创建一个聚焦于记录键的棱镜。

示例:

O.preview(O.atKey('foo'), { foo: 'bar' })
// 'bar'

O.preview(O.atKey('foo'), { hello: 'world' })
// undefined

O.set(O.atKey('foo'), 123, { foo: 'bar', hello: 'world' })
// { foo: 123, hello: 'world' }

O.set(O.atKey('foo'), 123, { hello: 'world' })
// { hello: 'world' }

O.remove(O.atKey('foo'), { foo: 'bar', hello: 'world' })
// { hello: 'world' }

find#

find :: (fn: (elem) => boolean) => RemovablePrism

仅适用于数组。可移除。

创建一个棱镜,聚焦于数组中第一个匹配谓词 fn 的元素。

示例:

const negativeElem = O.find((x: number) => x < 0)

O.preview(negativeElem, [1, 0, -1, -2])
// -1

O.modify(negativeElem, (x) => -x, [1, 0, -1, -2])
// [1, 0, 1, -2]

O.preview(negativeElem, [0, 2, 1])
// undefined

when#

when :: (fn: (value) => boolean) => Prism

创建一个棱镜,当焦点匹配谓词 fn 时,它会匹配。特别适用于过滤遍历的焦点。

示例:

const longWords = O.compose(
    O.words,
    O.when((s: string) => s.length >= 5)
)
const text = 'Some shorter and some longer words'

O.collect(longWords, text)
// ['shorter', 'longer', 'words']

O.modify(longWords, (s) => s.toUpperCase(), text)
// "Some SHORTER and some LONGER WORDS"

Traversals#

elems#

elems :: Traversal

仅适用于数组。

一个遍历,聚焦于数组的所有元素。

O.collect(O.compose(O.elems, 'foo', 'bar'), [
    { foo: { bar: 1 } },
    { foo: { bar: 2 } },
])
// [1, 2]

Getters#

Getters 是具有单一焦点的只读光学元素。你可以将它们视为单向同构或只读镜头。

to#

to :: (fn: (v) => u) => Getter

创建一个 getter,将函数 fn 应用于其焦点。

Setters#

prependTo, appendTo#

prependTo :: Setter
appendTo :: Setter

仅适用于数组。

prependTo 聚焦于数组的第一个元素之前的部分,appendTo 聚焦于数组的最后一个元素之后的部分。当通过写入时,将值前置或追加到数组。

O.set(O.appendTo, 3, [0, 1, 2])
// [0, 1, 2, 3]

O.set(O.prependTo, 3, [0, 1, 2])
// [3, 0, 1, 2]

String traversals#

chars#

chars :: Traversal

仅适用于字符串。

一个遍历,聚焦于字符串的所有字符。

当通过写入时,可以通过写入空字符串来删除字符,或者更改为更长的字符串。

O.collect(O.chars, 'foo')
// ['f', 'o', 'o']

O.modify(O.chars, (c) => (c == 'o' ? '' : c.toUpperCase()), 'foobar')
// 'FBAR'

words#

words :: Traversal

仅适用于字符串。

一个遍历,聚焦于字符串的所有单词。单词是由空格分隔的子字符串。

当通过写入时,可以通过写入空字符串来删除单词,或者更改为更长或更短的字符串。

O.collect(O.words, 'foo, bar')
// ['foo,', 'bar']

O.modify(O.words, (word) => word.split('').reverse().join(''), 'foo, bar')
// ',oof rab'

Miscellaneous#

eq#

eq :: Equivalence

Equivalence 是一个不做任何事情的空操作。在组合中充当身份。

pipe#

pipe :: (a, f1, f2, ...) => v

将表达式的值(a)传入最多 9 个一元函数(f1f2,...)的管道。

示例:

pipe(
  { foo: 1, bar: { baz: 2 } },
  O.set(O.prop('foo'), 3),
  O.modify(O.compose('bar', 'baz'), (v: number) => -v)
)
// { foo: 3, bar: { baz: -2 } }