Как объединить TaskT с экземпляром монады Trampoline, чтобы получить асинхронные вычисления без стека?

Trampoline является монадой и добавляет безопасность стека к стеку преобразователя монад. Это достигается за счет использования специального интерпретатора (monadRec), который получает результат монадического вычисления (на самом деле это специализированная версия шаблона свободной монады). По этой причине монада Trampoline должна быть самой внешней монадой, то есть базовой монадой стека преобразователя.

В следующей настройке TaskT (которая, по сути, Cont с совместным использованием) является преобразователем монады и Trampoline базовой монадой:

// TASK

const TaskT = taskt => record(
  TaskT,
  thisify(o => {
    o.taskt = k =>
      taskt(x => {
        o.taskt = k_ => k_(x);
        return k(x);
      });

    return o;
  }));

// Monad

const taskChainT = mmx => fmm =>
  TaskT(k =>
    mmx.taskt(x =>
      fmm(x).taskt(k)));

const taskOfT = x =>
  TaskT(k => k(x));

// Transformer

const taskLiftT = chain => mmx =>
  TaskT(k => chain(mmx) (k));

// auxiliary functions

const taskAndT = mmx => mmy =>
  taskChainT(mmx) (x =>
    taskChainT(mmy) (y =>
      taskOfT([x, y])));

const delayTaskT = f => ms => x =>
  TaskT(k => setTimeout(comp(k) (f), ms, x));

const record = (type, o) => (
  o[Symbol.toStringTag] = type.name || type, o);

const thisify = f => f({});

const log = (...ss) =>
  (console.info(...ss), ss[ss.length - 1]);

// TRAMPOLINE

const monadRec = o => {
  while (o.tag === "Chain")
    o = o.fm(o.chain);

  return o.tag === "Of"
    ? o.of
    : _throw(new TypeError("unknown trampoline tag"));
};

// tags

const Chain = chain => fm =>
  ({tag: "Chain", fm, chain});


const Of = of =>
  ({tag: "Of", of});

// Monad

const recOf = Of;

const recChain = mx => fm =>
  mx.tag === "Chain" ? Chain(mx.chain) (x => recChain(mx.fm(x)) (fm))
    : mx.tag === "Of" ? fm(mx.of)
    : _throw(new TypeError("unknown trampoline tag"));

// MAIN

const foo = x =>
  Chain(delayTaskT(x => x) (0) (x)) (Of);

const bar = taskAndT(
  taskLiftT(recChain) (foo(1)))
    (taskLiftT(recChain) (foo(2))); // yields TaskT

const main = bar.taskt(x => Of(log(x))); // yields Chain({fm, chain: TaskT})

monadRec(main); // yields [TaskT, TaskT] but [1, 2] desired

Это не то, что я хочу, потому что Trampoline принудительно оценивает до того, как цикл событий получит результат асинхронных задач. Мне нужно наоборот, но, как я уже говорил, TrampolineT трансформатора нет. Что мне не хватает?

короткий ответ снизу: используйте taskLiftT(recChain) (Of(1))) вместо taskLiftT(recChain) (foo(1)))

user5536315 08.01.2022 13:39
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
1
150
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В этом фрагменте кода есть несколько проблем.

Проблема №1: для IO (то есть Task) нет монадного преобразователя

Хорошо известно, что для IO не существует преобразователя монад. [1] Ваш тип TaskT смоделирован после ContT, а ContT действительно является преобразователем монад. Однако вы используете TaskT для выполнения асинхронных вычислений, таких как setTimeout, и здесь возникает проблема.

Рассмотрим определение TaskT, которое похоже на ContT.

newtype TaskT r m a = TaskT { taskt :: (a -> m r) -> m r }

Следовательно, delayTaskT должен иметь тип (a -> b) -> Number -> a -> TaskT r m b.

const delayTaskT = f => ms => x =>
  TaskT(k => setTimeout(comp(k) (f), ms, x));

Однако setTimeout(comp(k) (f), ms, x) возвращает идентификатор тайм-аута, который не соответствует типу m r. Обратите внимание, что k => setTimeout(comp(k) (f), ms, x) должен иметь тип (b -> m r) -> m r.

На самом деле невозможно вызвать значение типа m r, когда продолжение k вызывается асинхронно. Преобразователь монад ContT работает только для синхронных вычислений.

Тем не менее, мы можем определить Task как специализированную версию Cont.

newtype Task a = Task { task :: (a -> ()) -> () } -- Task = Cont ()

Таким образом, всякий раз, когда Task присутствует в стеке преобразования монад, он всегда будет в основе, как и IO.

Если вы хотите сделать стек монад Task безопасным, прочитайте следующий ответ.

Проблема № 2: Функция foo имеет неправильный тип возвращаемого значения

Предположим на мгновение, что delayTaskT имеет правильный тип. Следующая проблема, как вы уже заметили, заключается в том, что foo имеет неправильный тип возвращаемого значения.

Проблема, по-видимому, заключается в том, что foo возвращает TaskT, завернутый в Chain, и этот завернутый TaskT полностью отделен от цепочки TaskT и, таким образом, никогда не оценивается/не запускается.

Я предполагаю, что ожидаемый тип fooa -> TaskT r Trampoline a. Однако фактический тип foo — это a -> Trampoline (TaskT r m a). К счастью, это легко исправить.

const foo = delayTaskT(x => x) (0);

Тип foo такой же, как taskOfT, то есть a -> TaskT r m a. Мы можем специализироваться m = Trampoline.

Проблема № 3: вы неправильно используете taskLiftT

Функция taskLiftT поднимает базовое монадическое вычисление на уровень TaskT.

taskLiftT :: (forall a b. m a -> (a -> m b) -> m b) -> m a -> TaskT r m a

taskLiftT(recChain) :: Trampoline a -> TaskT r Trampoline a

Теперь вы применяете taskLiftT(recChain) к foo(1) и foo(2).

foo :: a -> Trampoline (TaskT r m a) -- incorrect definition of foo

foo(1) :: Trampoline (TaskT r m Number)
foo(2) :: Trampoline (TaskT r m Number)

taskLiftT(recChain) (foo(1)) :: TaskT r Trampoline (TaskT r m Number)
taskLiftT(recChain) (foo(2)) :: TaskT r Trampoline (TaskT r m Number)

Однако, если мы используем правильное определение foo, то типы даже не будут совпадать.

foo :: a -> TaskT r Trampoline a -- correct definition of foo

foo(1) :: TaskT r Trampoline Number
foo(2) :: TaskT r Trampoline Number

-- Can't apply taskLiftT(recChain) to foo(1) or foo(2)

Если мы используем правильное определение foo, то есть два способа определить bar. Обратите внимание, что невозможно правильно определить foo с помощью setTimeout. Следовательно, я переопределил foo как taskOfT.

  1. Используйте foo и не используйте taskLiftT.

    const bar = taskAndT(foo(1))(foo(2)); // yields TaskT
    

    // TASK
    
    const TaskT = taskt => record(
      TaskT,
      thisify(o => {
        o.taskt = k =>
          taskt(x => {
            o.taskt = k_ => k_(x);
            return k(x);
          });
    
        return o;
      }));
    
    // Monad
    
    const taskChainT = mmx => fmm =>
      TaskT(k =>
        mmx.taskt(x =>
          fmm(x).taskt(k)));
    
    const taskOfT = x =>
      TaskT(k => k(x));
    
    // Transformer
    
    const taskLiftT = chain => mmx =>
      TaskT(k => chain(mmx) (k));
    
    // auxiliary functions
    
    const taskAndT = mmx => mmy =>
      taskChainT(mmx) (x =>
        taskChainT(mmy) (y =>
          taskOfT([x, y])));
    
    const delayTaskT = f => ms => x =>
      TaskT(k => setTimeout(comp(k) (f), ms, x));
    
    const record = (type, o) => (
      o[Symbol.toStringTag] = type.name || type, o);
    
    const thisify = f => f({});
    
    const log = (...ss) =>
      (console.info(...ss), ss[ss.length - 1]);
    
    // TRAMPOLINE
    
    const monadRec = o => {
      while (o.tag === "Chain")
        o = o.fm(o.chain);
    
      return o.tag === "Of"
        ? o.of
        : _throw(new TypeError("unknown trampoline tag"));
    };
    
    // tags
    
    const Chain = chain => fm =>
      ({tag: "Chain", fm, chain});
    
    
    const Of = of =>
      ({tag: "Of", of});
    
    // Monad
    
    const recOf = Of;
    
    const recChain = mx => fm =>
      mx.tag === "Chain" ? Chain(mx.chain) (x => recChain(mx.fm(x)) (fm))
        : mx.tag === "Of" ? fm(mx.of)
        : _throw(new TypeError("unknown trampoline tag"));
    
    // MAIN
    
    const foo = taskOfT;
    
    const bar = taskAndT(foo(1))(foo(2)); // yields TaskT
    
    const main = bar.taskt(x => Of(log(x))); // yields Chain({fm, chain: TaskT})
    
    monadRec(main); // yields [TaskT, TaskT] but [1, 2] desired
  2. Не используйте foo и используйте taskLiftT.

    const bar = taskAndT(
      taskLiftT(recChain) (Of(1)))
        (taskLiftT(recChain) (Of(2))); // yields TaskT
    

    // TASK
    
    const TaskT = taskt => record(
      TaskT,
      thisify(o => {
        o.taskt = k =>
          taskt(x => {
            o.taskt = k_ => k_(x);
            return k(x);
          });
    
        return o;
      }));
    
    // Monad
    
    const taskChainT = mmx => fmm =>
      TaskT(k =>
        mmx.taskt(x =>
          fmm(x).taskt(k)));
    
    const taskOfT = x =>
      TaskT(k => k(x));
    
    // Transformer
    
    const taskLiftT = chain => mmx =>
      TaskT(k => chain(mmx) (k));
    
    // auxiliary functions
    
    const taskAndT = mmx => mmy =>
      taskChainT(mmx) (x =>
        taskChainT(mmy) (y =>
          taskOfT([x, y])));
    
    const delayTaskT = f => ms => x =>
      TaskT(k => setTimeout(comp(k) (f), ms, x));
    
    const record = (type, o) => (
      o[Symbol.toStringTag] = type.name || type, o);
    
    const thisify = f => f({});
    
    const log = (...ss) =>
      (console.info(...ss), ss[ss.length - 1]);
    
    // TRAMPOLINE
    
    const monadRec = o => {
      while (o.tag === "Chain")
        o = o.fm(o.chain);
    
      return o.tag === "Of"
        ? o.of
        : _throw(new TypeError("unknown trampoline tag"));
    };
    
    // tags
    
    const Chain = chain => fm =>
      ({tag: "Chain", fm, chain});
    
    
    const Of = of =>
      ({tag: "Of", of});
    
    // Monad
    
    const recOf = Of;
    
    const recChain = mx => fm =>
      mx.tag === "Chain" ? Chain(mx.chain) (x => recChain(mx.fm(x)) (fm))
        : mx.tag === "Of" ? fm(mx.of)
        : _throw(new TypeError("unknown trampoline tag"));
    
    // MAIN
    
    const foo = taskOfT;
    
    const bar = taskAndT(
      taskLiftT(recChain) (Of(1)))
        (taskLiftT(recChain) (Of(2))); // yields TaskT
    
    const main = bar.taskt(x => Of(log(x))); // yields Chain({fm, chain: TaskT})
    
    monadRec(main); // yields [TaskT, TaskT] but [1, 2] desired

[1]Почему в Haskell нет преобразователя ввода-вывода?

Связь между непрозрачным типом IO и асинхронностью была недостающим звеном. Дефункционализация вместе с батутом, который вы демонстрируете в связанном ответе, вносит большую сложность, но все же очень полезна. Я думаю, что я также изучу подход интерпретатора (специализированная свободная монада) и посмотрю, может ли лень быть полезной для обеспечения безопасности стека. Я приму ваш ответ, как только смогу присудить награду. Спасибо!

user5536315 14.12.2020 11:08

Другие вопросы по теме