Объедините несколько универсальных типов в hacklang

Я пытаюсь реализовать функцию сокращения из подчеркивания в хаке. В подчеркивании функция сокращения ведет себя следующим образом:

Если при начальном вызове редукции не передается заметка, итерация не вызывается для первого элемента списка. Вместо этого первый элемент передается как памятка при вызове итерации для следующего элемента в списке.

Моя попытка реализовать функцию:

function reduce<T, Tresult>(
  Iterable<T> $iterable,
  (function(?Tresult, T):Tresult) $fn,
  ?Tresult $memo=null):?Tresult {
    if (is_null($memo)) {
      $memo = $iterable->firstValue();
      $iterable = $iterable->skip(1);
    }

    foreach ($iterable as $value) {
      $memo = $fn($memo, $value);
    }

    return $memo;
}

Это приводит к ошибке:

Invalid return type (Typing[4110])  
  This is a value of generic type Tresult  
  It is incompatible with a value of generic type T  
    via this generic Tv

Как сообщить программе проверки типов, что T == Tresult когда is_null($memo)


person nwarp    schedule 15.06.2016    source источник


Ответы (1)


отмечу, что строка

$memo = $iterable->firstValue();

присваивает значение типа T $memo. Это кажется неправильным; $memo задается как тип ?Tresult в объявлении, и здесь ему присвоено значение типа Tresult:

$memo = $fn($memo, $value);

Можете ли вы объяснить, почему $memo в первую очередь присваивается значение типа T? Откуда вы знаете, что T и Tresult одинаковы? Я не вижу никаких доказательств того, что эти два типа когда-либо вынуждены быть одним и тем же. Средство проверки типов выдает здесь ошибку, потому что эта программа не является безопасной для типов; если T — это Animal, а Tresult — это Fruit, и кто-то передает нулевой плод, нет никакого способа получить плод из последовательности.

Кроме того, мне кажется странным, что reduce возвращает результат, допускающий значение NULL; конечно, он должен возвращать результат данного типа результата, нет?

Если вы хотите, чтобы эта функция имела два разных поведения в зависимости от недействительности аргумента, то почему бы вместо этого просто не использовать две функции?

function reduce1<T, Tresult>(
  Iterable<T> $iterable,
  (function(Tresult, T):Tresult) $fn,
  Tresult $memo): Tresult {
    foreach ($iterable as $value) {
      $memo = $fn($memo, $value);
    }
    return $memo;
}

function reduce2<T>(
  Iterable<T> $iterable,
  (function(T, T):T) $fn): T {
    return reduce1($iterable->skip(1), $fn, $iterable->firstValue());
}

Итак, теперь у нас есть две разные формы сокращения, и обе они типобезопасны.

person Eric Lippert    schedule 15.06.2016
comment
Когда памятка опущена, в качестве памятки используется первое значение в Iterable. В этом случае T совпадает с Tresult. Это поведение функции reduce() в underscore.js иногда полезно для коммутативных операций. Например: _.reduce([1,2,3], (x,y) => x+y) - person nwarp; 15.06.2016
comment
@nwarp: Ты не следишь за мной. Что вынуждает использовать одинаковые типы в вашей программе Hack? Что мешает мне передать нуль ?Fruit как $memo и последовательность Giraffe для последовательности? Система типов говорит вам, что вы не предоставили никаких доказательств того, что это не может произойти, и, следовательно, может произойти. - person Eric Lippert; 15.06.2016
comment
@nwarp: Ваш вопрос заключается в том, как мне сообщить средству проверки типов, что эти два типа одинаковы, когда это значение равно нулю? когда они не обязательно совпадают, когда значение равно null. Вы не можете сообщить об этом программе проверки типов, потому что это ложь. - person Eric Lippert; 15.06.2016
comment
Моя интерпретация заключается в том, что для того, чтобы делать то, что я хочу, мне понадобится: 1. перегрузка функций 2. какое-то утверждение типа для дженериков, например invariant(T == Tresult). Поскольку обе эти функции недоступны в hacklang, желаемое поведение не может быть реализовано типобезопасным способом. - person nwarp; 16.06.2016
comment
@nwarp: я не понимаю, как помогает перегрузка функций. Все, что дает вам перегрузка функций, — это возможность иметь две разные функции с одним и тем же именем; почему трудно иметь две разные функции с разными именами? Два требуемых поведения — это разные функции; один требует проекции на произвольный тип, а другой требует проекции на тип элемента; это разные вещи, поэтому у вас должно быть две функции. - person Eric Lippert; 16.06.2016
comment
Перегрузка функций позволит мне называть и reduce1, и reduce2 одним и тем же именем, тем самым получая желаемое поведение. Я понимаю ваши доводы в пользу того, почему эти должны быть двумя разными функциями. Как уже упоминалось, я пытаюсь воспроизвести поведение широко используемой библиотеки, и что нужно сделать, не имеет значения в этом сценарии. - person nwarp; 16.06.2016
comment
@nwarp: я в ужасе от последнего предложения. Попытка имитировать синтаксис библиотеки — это функция с затратами и преимуществами. Не отвергайте слепо возможность того, что затраты превышают выгоды. - person Brian; 16.06.2016
comment
@ Брайан Думаю, мне следует перефразировать. Речь идет не о том, чтобы найти лучший/правильный способ что-то сделать. Речь идет об изучении и тестировании ограничений системы типов Hack. - person nwarp; 17.06.2016
comment
@nwarp: Еще одно соображение: ваше дизайнерское предложение объединяет ноль с отсутствующим. Предположим, вы хотели, чтобы начальное значение аккумулятора было нулевым; в предложенном вами методе начальное значение null имеет особое значение. - person Eric Lippert; 17.06.2016