Проблема реализации функтора в машинописи
Я читал серию блогов Тома Хардинга по спецификации фэнтези-лэнд, и сегодня днем я играл с реализацией функтора в машинописи.
class Just<T> {
private x: T
constructor (x: T) {
this.x = x
}
map <R>(f: (a: T) => R): Just<R> {
return new Just(f(this.x))
}
}
class Nothing<T> {
private x: T
constructor (_: T) {
// noop
}
map <R>(_: (a: T) => R): Nothing<R> {
return new Nothing<R>(undefined)
}
}
type Maybe<T> = Just<T> | Nothing<T>
const map1 = <T, R>(f: (a: T) => R, a: Maybe<T>): Maybe<R> => {
return a.map(f)
}
К несчастью, a.map(f)
выше приводит к следующей ошибке времени компиляции:
[ts] Cannot invoke an expression whose type lacks a call signature.
Type '(<R>(f: (a: T) => R) => Just<R>) | (<R>(_: (a: T) => R) => Nothing<R>)'
has no compatible call signatures.
Я чувствую, что это должно работать, хотя... Я сделал более простой пример, который не является функтором, но который использует дженерики в большинстве случаев:
class A<T> {
private x: T
constructor (x: T) {
this.x = x
}
func <R>(f: (a: T) => R): R { // this is returning R not A<R>
return f(this.x)
}
}
class B<T> {
constructor (_: T) {
//
}
func <R>(_: (a: T) => R): R {
return undefined
}
}
type C<T> = A<T> | B<T>
const func = <T, R>(f: (a: T) => R, a: C<T>): R => {
return a.func(f)
}
Этот код компилируется просто отлично. Если я изменю типы возвращаемых функций на A<R>
а также B<R>
(т.е. если карта вернется C<R>
) тогда я получаю ту же ошибку, что и выше.
Вот мне и интересно: что случилось? Это какая-то сумасшедшая вещь контравариантности / ковариантности? Это ожидаемое поведение машинописи? Это я что-то упустил? (или это ничто ^o^//).
РЕДАКТИРОВАТЬ: я попытался повторно реализовать вышеизложенное с наследованием вместо объединения:
abstract class Maybe<T> {
protected x: T
constructor (x: T) {
this.x = x
}
abstract map <R>(f: (a: T) => R): Maybe<R>
}
class Just<T> extends Maybe<T> {
constructor (x: T) {
super(x)
}
map <R>(f: (a: T) => R): Just<R> {
return new Just(f(this.x))
}
}
class Nothing<T> extends Maybe<T> {
constructor (_: T) {
super(undefined)
}
map <R>(f: (a: T) => R): Nothing<R> {
return new Nothing(undefined)
}
}
const map2 = <T, R>(f: (a: T) => R, a: Maybe<T>): Maybe<R> => {
return a.map(f)
}
Работает просто отлично! Что дает, машинопись!?
Даже думал, что вышесказанное работает, это не то, что я хочу. Just
не "расширяется" Maybe
, Maybe
это союз Just
а также Nothing
, Это мой путь к шоссе
РЕДАКТИРОВАТЬ: РЕДАКТИРОВАТЬ: я попробовал это снова, используя интерфейс для Functor
// tslint:disable-next-line:interface-name
interface Functor<T> {
map: <R>(f: (a: T) => R) => Functor<R>
}
class Just<T> implements Functor<T> {
private readonly x: T
constructor (x: T) {
this.x = x
}
map <R>(f: (a: T) => R): Just<R> {
return new Just(f(this.x))
}
}
class Nothing<T> implements Functor<T> {
constructor (_: T) {
// nop
}
map <R>(_: (a: T) => R): Nothing<R> {
return new Nothing(undefined)
}
}
type Maybe<T> = Functor<T>
const map3 = <T, R>(f: (a: T) => R, a: Maybe<T>): Maybe<R> => {
return a.map(f)
}
Это тоже работает, и я согласен с тем, что Just реализует интерфейс Functor, потому что, ну... так и есть. Я все еще не доволен type Maybe<T> = Functor<T>
потому что он переопределяет тип (членов Functor больше, чем Maybe?)
Кроме того, я предполагаю, что карта должна не просто возвращать Functor, но и тот же Functor, как карта Maybe возвращает значение Maybe (а не просто любой Functor). Я начинаю понимать, почему нам нужны типы с более высоким родом, чтобы представлять подобные вещи?
РЕДАКТИРОВАТЬ: Добавление второго универсального для учета вида функтора, кажется, работает. Теперь карта Just должна вернуть Just.
// tslint:disable-next-line:interface-name
interface Functor<K, T> {
map: <R>(f: (a: T) => R) => Functor<K, R>
}
И тогда я определяю, может быть, так:
type Maybe<T> = Functor<Just<T> | Nothing<T>, T>
Скрещенные пальцы.