Подтвердить что ты не робот

Состав функции JavaScript путем цепочки

Я проверил возможность дублирования вопроса, и не может найти точное решение.

Я написал код функциональной цепочки в JavaScript, как показано ниже, и отлично работает.

var log = function(args)
{
  console.log(args)

  return function(f)
  {
    return f;
  };
};

(log('1'))(log('2'))(log('3'))(log('4'));

//1
//2
//3
//4

Я хочу сделать эту ленивую оценку.

Или для создания функции.

var log = function(args)
{
  var f0 = function()
  {
    return console.log(args);
  };

  return function(f1)
  {
    return function()
    {
      f0();
      return f1;
    };
  };
};

var world = (log('1'))(log('2'))(log('3'))(log('4'));
console.log(world);
//should be just a function,
// but in fact
//1
//[function]

world();
//should be
//1
//2
//3
//4

// but in fact
// 2

Что-то очень не так. Вы можете исправить это?

Спасибо.

Этот вопрос разрешен, но есть еще

проблема async, как показано в обсуждении комментариев

Когда мы имеем

// unit :: a -> IO a
var unit = function(x)
{
  return function()
  {
    return x;
  };
};

// bind :: IO a -> (a -> IO b) -> IO b
var bind = function(x, y)
{
  return function()
  {
    return y(x())();
  };
};

// seq :: IO a -> IO b -> IO b
var seq = function(x, y)
{
  return function()
  {
    return x(), y();
  };
};

var action = function(x)
{
  return function(y)
  {
    return y ? action(seq(x, y)) : x();
  };
};

var wrap = function(f)
{
  return function(x)
  {
    return action(function()
    {
      return f(x);
    });
  };
};

var log = wrap(console.log);



// -- runtime -- 
// HACK: when `world` is modified by passing a function,
//       the function will be executed.

Object.defineProperties(window,
{
  world:
  {
    set: function(w)
    {
      return w();
    }
  }
});

Мы также часто нуждаемся в асинхронных цепных реакциях.

var asyncF = function(callback)
{
  setTimeout(function()
  {
    for (var i = 0; i < 1000000000; i++)
    {

    };

    callback("async process Done!");
  }, 0);
};

var async = wrap(asyncF(function(msg)
{
  world = log(msg);

  return msg;
}));

Теперь,

world = (log(1))(async)(log(3));
//1
//3
//async process Done!

До сих пор приятно и плавно, теперь мы пытаемся использовать bind

world = (log(1))
  (bind((async), (log(x))));

//should be
//1
//async process Done!
//3

//in fact
//ReferenceError: x is not defined

Не могли бы вы изменить эту работу, пожалуйста?

еще один < <26 > несколько значений

Я не понимаю

  // seq :: IO a -> IO b -> IO b
    var seq = function(x, y)
    {
      return function()
      {
        return x(), y();
      };
    };

как автор библиотеки упоминает

Обратите внимание, что это невозможно в Haskell, потому что одна функция не может вернуть два результата. Кроме того, по моему скромному мнению, это выглядит уродливо.

Я согласен и не знаю, что это за

return x(), y();

многократное возвращаемое значение.

Я искал Google и искал здесь, но не смог найти ответ.

Что это такое?

(на всякий случай, я выберу этот хак для синтаксиса)

Спасибо!

4b9b3361

Ответ 1

Итак, если я правильно понял вопрос, вы хотите связать действия IO в JavaScript. Для этого вам сначала нужно определить, что такое операция ввода-вывода. Один из способов думать о IO-действии состоит в том, что это просто функция, которая не принимает аргументов. Например:

// log :: a -> IO b

function log(x) {
    return function () {       // IO action
        return console.log(x);
    };
}

Одним из преимуществ представления IO-действия как функции без аргументов является то, что это то же представление для thunks (неоценимые выражения). Thanks - это то, что позволяет ленивую оценку на таких языках, как Haskell. Следовательно, вы получаете ленту бесплатно.

Теперь состав. Как вы создаете два действия ввода-вывода в JavaScript? В Haskell вы используете оператор >> для последовательности операций ввода-вывода, который обычно определяется в терминах >>= (a.k.a. bind) следующим образом:

(>>=) :: Monad m => m a -> (a -> m b) -> m b

(>>) :: Monad m => m a -> m b -> m b
x >> y = x >>= \_ -> y

Легко написать эквивалентную функцию bind для наших действий IO в JavaScript:

// bind :: IO a -> (a -> IO b) -> IO b

function bind(x, y) {
    return function () {
        return y(x())();
    };
}

Предположим, что у вас есть действие IO x :: IO a. Поскольку это просто функция без аргументов, когда вы ее называете, она эквивалентна оценке действия ввода-вывода. Следовательно, x() :: a. Подача этого результата на функцию y :: a -> IO b приводит к действию ввода-вывода y(x()) :: IO b. Обратите внимание, что вся операция завершена в излишнюю функцию для лень.

Аналогично, так же просто реализовать оператор >>. Позвольте называть его seq как в "последовательности".

// seq :: IO a -> IO b -> IO b

function seq(x, y) {
    return function () {
        return x(), y();
    };
}

Здесь мы оцениваем выражение IO x, не заботимся о его результате, а затем возвращаем выражение IO y. Это именно то, что делает оператор >> в Haskell. Обратите внимание, что вся операция завершена в излишнюю функцию для лень.

Haskell также имеет функцию return, которая возвращает значение в монадический контекст. Поскольку return является ключевым словом в JavaScript, мы будем называть его unit вместо:

// unit :: a -> IO a

function unit(x) {
    return function () {
        return x;
    };
}

Как оказалось, в Haskell также есть оператор sequence, который последовательно выполняет монадические значения в списке. Он может быть реализован в JavaScript для операций ввода-вывода следующим образом:

// sequence :: [IO a] -> IO [a]

function sequence(array) {
    return function () {
        var list   = array;
        var length = list.length;
        var result = new Array(length);
        var index  = 0;

        while (index < length)
            result[index] = list[index++]();
        return result;
    };
}

Это все, что нам нужно. Теперь мы можем написать:

var world = sequence([log("1"), log("2"), log("3"), log("4")]);

world();

// 1
// 2
// 3
// 4

Надеюсь, что это поможет.


Да, действительно возможно связать действия IO с помощью вашего синтаксиса. Однако нам нужно переопределить, что такое действие ввода-вывода:

function action(x) {
    return function (y) {
        return y ? action(seq(x, y)) : x();
    };
}

Понятно, что делает функция action с помощью примера:

// log :: a -> IO b
// log :: a -> IO r -> IO r

function log(x) {
    return action(function () {
        return console.log(x);
    });
}

Теперь вы можете сделать:

log("1")();         // :: b
log("1")(log("2")); // :: IO r

В первом случае мы оценили действие IO log("1"). Во втором случае мы упорядочили действия IO log("1") и log("2").

Это позволяет:

var world = (log("1"))(log("2"))(log("3"))(log("4"));

world();

// 1
// 2
// 3
// 4

Кроме того, вы также можете:

var newWorld = (world)(log("5"));

newWorld();

// 1
// 2
// 3
// 4
// 5

И так далее....

Все остальное остается прежним. Обратите внимание, что это невозможно в Haskell, потому что одна функция не может вернуть два результата. Кроме того, по моему скромному мнению, это выглядит уродливо. Вместо этого я предпочитаю использовать sequence. Однако это то, что вы хотели.

Ответ 2

Посмотрим, что здесь происходит:

var log = function(args)
{
  var f0 = function()
  {
    return console.log(args);
  };

  return function(f1)
  {
    return function()
    {
      f0();
      return f1;
    };
  };
};

И в строке немного:

var log = function(args) {
  return function(f1) {
    return function() {
      console.log(args);
      return f1;
    };
  };
};

Итак, мы возвращаем функцию f, которая принимает функцию f1, и возвращает функцию g, которая выполняет логику и возвращает f1. Довольно глоток! Ваш вопрос в том, почему

(log('1'))(log('2'))(log('3'));

Журнал 1. Я покончил с log('4'), так как для перехода к 3 достаточно показать ваш описанный случай. Ответьте, что разрешите играть в компилятор и сделайте игровую игру!

(log('1'))(log('2'))(log('3'))
// =>
(
  function (f1) {
    return function () {
      console.log('1');
      return f1;
    }
  }
)(
  function (f1) {
    return function () {
      console.log('2');
      return f1;
    }
  }
)(
  function (f1) {
    return function () {
      console.log('3');
      return f1;
    }
  }
)

Простая подстановка. Я взял каждый экземпляр log(something), заменил его содержимым функции, заменил аргумент на переданное значение. Позвольте сделать это снова!

(
  function () {
    console.log('1');
    return function (f1) {
      return function () {
        console.log('2');
        return f1;
      }
    };
  }
)(
  function (f1) {
    return function () {
      console.log('3');
      return f1;
    }
  }
)

Это немного сложнее: я расширил первый вызов функции. Самая верхняя функция получила аргумент f1, на который мы только что указали значение, поэтому я вошел в функцию и заменил каждое вхождение f1 на заданное значение (результат log('2')), точно так же, как с log.

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

Теперь вы можете понять, почему был вызван log('1'). Следующее, что нам, компилятор, нужно сделать, это позаботиться о следующем вызове функции. И, как известно, первая строка в этой функции - это console.log! Лучше сделайте это!

Что мы можем сделать??

Я не знаю Haskell или IO Monad, но, как вы сейчас планировали, я не думаю, что вы можете делать то, что хотите, с помощью основных функций, а не так. Если вы можете сказать, какую проблему вы хотите решить, используя этот... erm... шаблон, возможно, мы сможем помочь!

Ответ 3

Это потому, что вы просто возвращаетесь и возвращаете все...

На выходе выводятся три вещи:

1
 function ()
    {
      f0();
      return f1;
    }

2

1) первый вывод: 1

это потому, что: console.log(args) выполняется только один раз в вашей цепочке, так как f0 выполняется только один раз в последний раз, когда он находит args равным 1 (из-за возврата каждой вложенной функции какое значение вы возвращаете на last - это функция f1, которая выполняет f0(), когда значение args равно 1. затем он печатает 1 на консоли.

2) второй вывод функции f1

the return f1; (который возвращается функции, когда вы передавали args как 1), выполненный при последних возвратах

  function ()
            {
              f0();
              return f1;
            }

вернуться в мир переменных, поэтому на консоль печатается только внутренняя вложенная функция.

3) Третий выход: 2

то при выполнении функции world(),

Опять функция f1 выполняется непосредственно (см. только небольшое различие между world и world()), но на этот раз возвращенная функция, когда вы передали args как 2.

Причина: мир выведет только функцию, world() выполнит эту функцию.
Когда вы пишете world(), в последний раз, когда возвращается function f1, значение для args равно 2. Выполняется непосредственно.

Я знаю, что я ужасно сформулировал ответ... но надеюсь, что это поможет (надеюсь, вы поймете)

Ответ 4

При выполнении

var world = (log('1'))(log('2'))(log('3'))(log('4'));

(log ('1')) выполняется первым, который возвращает функцию, которая принимает (log ('2')).

Эта анонимная функция запускается, но не принимает никаких аргументов. log ('3') пренебрегают. Это можно проверить с помощью

if(typeof(arguments[0]) == 'function'){
    console.log("Got a neglected argument");
    console.log(arguments[0]());
}

После выполнения f0(); (который печатает 1 на экран), мы возвращаем f1, который указывает на функцию, возвращаемую log ('2'), это принимает log ('4');

Это можно проверить, выполнив: world()()()

эти выходы:

2
4
undefined