2017年11月28日更新:
fp-ts
是流动静态土地的继承者。fp ts是同一作者开发的一个较新的库。它支持Flow和Typescript。
有一个图书馆,
flow-static-land
,这与您正在尝试的操作非常相似。通过查看代码并阅读附带的
blog posts
正如@ftor所提到的,如果您想要能够在一组开放的集合类型上工作的多态函数,那么您需要更高级的类型(HKT)。高级类型是采用类型参数的类型,但其中一个或多个参数未指定。例如,流中的数组采用类型参数来指定数组中元素的类型(
Array<V>
),地图也是如此(
Map<K, V>
). 有时,您希望能够引用参数化类型,而不指定其所有类型参数。例如
map
function map<K, A, B, M: Array<_> | Map<K, _>>(M<A>, fn: A => B): M<B>
在这种情况下
M
M
将其作为一级类型,并在不同时间用不同的类型填充其类型参数。Flow本身不支持HKT,因此上面的语法不起作用。但有可能用某种类型别名间接伪造HKT,这就是flow static land所做的。博客帖子中有详细信息,
Higher kinded types with Flow
.
获取的完全多态版本
地图
,flow static land模拟Haskell类型的类(依赖于HKT)。
地图
是类型类的定义特性,称为
Functor
; 流动静态土地的定义如下:
函子
(来自
Functor.js
export interface Functor<F> {
map<A, B>(f: (a: A) => B, fa: HKT<F, A>): HKT<F, B>
}
这个
HKT
类型是flow-static land的工作区,用于实现更高类型的类型。实际更高级的类型是
F
,你可以认为是代表
Array
或
Map
或任何可以实现
地图
. 表达式如下
HKT<F, A>
可以被认为是
F<A>
更高级的类型
F
已应用于类型参数
A
. (我在这里挥手。)-
F
实际上是一个类型级标记。但简化的视图在某种程度上起作用。)
您可以创建
函子
适用于任何类型。但有一个陷阱:你需要根据
香港电讯
因此,它可以作为一种更高级的类型。模块中的流动静态区域
Arr.js
我们看到了数组类型的更高版本:
class IsArr {} // type-level tag, not used at runtime
export type ArrV<A> = Array<A>; // used internally
export type Arr<A> = HKT<IsArr, A>; // the HKT-compatible array type
如果您不想使用
Arr<A>
代替
Array<A>
然后,您需要使用
inj: (a: Array<A>) => Arr<A>
和
prj: (fa: Arr<A>) => Array<A>
inj
和
prj
是类型级转换器-在运行时,这两个函数都只返回其输入,因此它们很可能由JIT内联。两者在运行时没有区别
Arr<A>
和
阵列(<);A>
.
A.
函子
实施
Arr
看起来像这样:
const arrFunctor: Functor<IsArr> = {
function map<A, B>(f: (a: A) => B, fa: Arr<A>): Arr<B> {
const plainArray = prj(f)
const mapped = plainArray.map(f)
return inj(mapped)
}
}
事实上整个
Arr.js
模块是一个
Arr公司
实施
函子
,
Foldable
,
Traversable
,以及其他有用的类型类。将该实现与多态代码一起使用如下所示:
import * as Arr from 'flow-static-land/lib/Arr'
import { type Foldable } from 'flow-static-land/lib/Foldable'
import { type Functor } from 'flow-static-land/lib/Functor'
import { type HKT } from 'flow-static-land/lib/HKT'
type Order = { items: string[], total: number }
// this code is polymorphic in that it is agnostic of the collection kind
// that is given
function computeTotal<F> (
f: Foldable<F> & Functor<F>,
orders: HKT<F, Order>
): number {
const totals = f.map(order => order.total, orders)
return f.reduce((sum, total) => sum + total, 0, totals)
}
// calling the code with an `Arr<Order>` collection
const orders = Arr.inj([{ items: ['foo', 'bar'], total: 23.6 }])
const t = computeTotal(Arr, orders)
computeTotal
需要申请
地图
和
reduce
它的输入。不是将输入约束到给定的集合类型,
计算机总数
使用其第一个参数将其输入约束为同时实现这两个参数的类型
可折叠的
函子
:
f: Foldable<F> & Functor<F>
. 在类型级别,参数
f
充当“见证人”,以证明给定的集合类型实现了
地图
和
减少
. 在运行时
f
提供对特定实现的参考
地图
和
减少
待使用。在多态代码的入口点(其中
计算机总数
可折叠的
&
函子
实现作为参数给出
Arr公司
. 因为Javascript不是为类型类设计的
Arr公司
必须明确给出;但如果您尝试使用与所使用的集合类型不兼容的实现,则流至少会引发错误。
这里是一个多态函数的示例,
allItems
,它接受一个集合,并返回相同类型的集合。
allItems公司
不知道其操作的特定集合类型:
import { type Monad } from 'flow-static-land/lib/Monad'
import { type Monoid, concatAll } from 'flow-static-land/lib/Monoid'
import { type Pointed } from 'flow-static-land/lib/Pointed'
// accepts any collection type that implements `Monad` & `Monoid`, returns
// a collection of the same kind but containing `string` values instead of
// `Order` values
function allItems<F> (f: Monad<F> & Monoid<*>, orders: HKT<F, Order>): HKT<F, string> {
return f.chain(order => fromArray(f, order.items), orders)
}
function fromArray<F, A> (f: Pointed<F> & Monoid<*>, xs: A[]): HKT<F, A> {
return concatAll(f, xs.map(f.of))
}
// called with an `Arr<Order>` collection
const is = allItems(Arr, orders)
chain
是流静态土地的版本
flatMap
. 对于集合中的每个元素,
链条
运行回调,该回调必须生成相同类型的集合(但可以包含不同的值类型)。这有效地产生了一个集合。
链条
然后为您将其展平到单个级别。所以
链条
基本上是
和
flatten
.
我包括在内
fromArray
因为给的回调
链条
必须返回与
allItems公司
接受并返回-返回
大堆
从
链条
Pointed
中的约束
fromArray
获得
of
函数,它将单个值放入适当类型的集合中。
指出
不会出现在的约束中
allItems公司
因为
有一个
Monad
约束,以及
单子
实施也是
指出
,
Chain
,
函子
,以及其他一些。
我个人是流静之地的粉丝。HKT的功能风格和使用使得代码的类型安全性优于使用面向对象风格的鸭子类型。但也有缺点。当使用类型联合时,来自流的错误消息可能变得非常冗长,例如
Foldable<F> & Functor<F>
. 而且代码风格需要额外的训练——对于不熟悉Haskell的程序员来说,这似乎太奇怪了。