Большое спасибо за такой подробный ответ. Возможно, стоит реализовать этот преобразователь как способ развития понимания. :)

ли в функциональном программировании стандартная конструкция для этой логики?

const passAround = (f) => (x) => {
  f(x);

  return x;
};

Это позволяет мне составлять функции, которые имеют побочные эффекты и не имеют возвращаемых значений, напримерconsole.log, Это не как Задача, потому что я не хочу представлять состояние побочного эффекта.

 PeterMader08 сент. 2017 г., 15:38
Укороченная версия будет:f => x => (f(x), x).
 Aadit M Shah08 сент. 2017 г., 18:11
@PeterMader Еще более короткая версия будетSK в исчислении комбинатора SKI.

Ответы на вопрос(3)

Решение Вопроса

SKI комбинатор исчисление может вас заинтересовать Давайте притворимся, чтоf всегда чистая функция:

const S = g => f => x => g(x)(f(x)); // S combinator of SKI combinator calculus
const K = x => y => x;               // K combinator of SKI combinator calculus

const passAround = S(K);             // Yes, the passAround function is just SK

console.log(passAround(console.log)(10) + 20);

В любом случае, причина, по которой я привожу исчисление комбинаторов SKI, заключается в том, что я хочу познакомить вас с концепциейАппликативные функторы, В частности,Reader аппликативный функторэквивалент к исчислению комбинатора SKI.S комбинатор эквивалентенap методReader иK комбинатор эквивалентенpure методReader.

В JavaScript эквивалентReader являетсяFunction, Следовательно, мы можем определитьap а такжеpure для функций в JavaScript следующим образом:

Function.prototype.ap = function (f) {
    return x => this(x)(f(x));
};

Function.pure = x => y => x;

const print = Function.pure.ap(console.log);

console.log(print(10) + 20);

Но подождите, с аппликативными функторами можно сделать гораздо больше. Каждый аппликативный функтор также является функтором. Это означает, что аппликативные функторы также должны иметьmap метод. ЗаReader map метод простофункциональная композиция, Это эквивалентноB комбинатор. С помощьюmap Вы можете сделать действительно интересные вещи, такие как:

Function.prototype.ap = function (f) {
    return x => this(x)(f(x));
};

Function.pure = x => y => x;

const id = x => x; // I combinator of SKI combinator calculus

Function.prototype.map = function (f) {
    return x => this(f(x));
};

Function.prototype.seq = function (g) {
    return Function.pure(id).map(this).ap(g);
};

const result = console.log.seq(x => x + 20);

console.log(result(10));

seq функция на самом деле эквивалентна(*>) Метод Аппликативного класса. Это позволяет функциональный стильметод каскадирования.

 Babakness01 мар. 2018 г., 19:29
Вау, спасибо большое, я очень ценю это. Пожалуйста, напишите книгу для FP в JS, сейчас самое подходящее время для этого.
 user63318310 сент. 2017 г., 02:21
Отличная презентация работы, Аадит - мне еще предстоит погрузиться в применение комбинаторов SKI, и я уже чувствую, что благодаря этому посту я знаю очень практичный пример использования.
 maxhallinan09 сент. 2017 г., 12:49
Спасибо за такие конкретные примеры кода. Они сделали эти концепции намного проще для понимания.

то вам нужно оспорить эту отправную точку:

функции, которые имеют побочные эффекты и не имеют возвращаемых значений

В функциональном программированиинет такой вещи, Каждая функция определяется как преобразование некоторого ввода в некоторый вывод.

Таким образом, очевидный вопрос, как бы вы представлялиconsole.log без побочного эффекта? Чтобы ответить, нам нужно оспорить другое предположение в вашем вопросе:

Я не хочу представлять состояние побочного эффекта

Именно так функциональное программирование представляет проблему: рассматривайте ваши входные и выходные данные как «состояние мира». Другими словами, учитывая состояние мира до функции, вернуть состояние мира после функции. В этом случае вы представляете состояние консоли: если дана консоль с x строками вывода, верните консоль с x + 1 строками вывода. Грубо, вы могли бы написать что-то вроде этого:

(x, console) => { return [x, console.withExtraLine(x)]; }

Более мощный механизм, обычно используемый для представления этого, называется «монадой» - особый вид объекта, который заключает в себе ряд шагов вместе с некоторым дополнительным значением. В случае сIO Монада, каждый шаг оборачивается действием, которое изменит состояние мира. (I / O - только одно из многих полезных применений концепции монады.)

Вы записываете шаги как функции, которые знают только о «развернутом» значении некоторой части этого состояния (например, параметра, который в конечном итоге поступил из пользовательского ввода), и монада обрабатывает грязные детали фактического выполнения этоговне сферы функциональной программы, Поэтому вместо того, чтобы думать о своих входных и выходных данных как о «состоянии мира», вы будете думать о своих входных данных как о «цепочке вычислений», а о своих выходных данных как «немного более длинной цепочке вычислений».

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

Смотрите также,этот ответ, этот вопроси, возможно, многие другие в боковой панели «Связанные» автоматически создаются при просмотре этого вопроса.

 IMSoP08 сент. 2017 г., 18:55
@ftor Но что это значит дляputStrLn «вернуть действие IO», если выне затем связать это с остальнымиIO действия? Конечно, вы должны в какой-то момент дать этоIO действие на нефункциональную среду выполнения, чтобы фактически заставить действие произойти? В этот момент вы собираетесь каким-то образом объединить все свои действия, за исключением тривиального случая программы с ровно одним действием ввода-вывода.
 user644553308 сент. 2017 г., 18:07
Просто для пояснения: не каждая монада связана с IO. В Haskell IO это полиморфный типIO a который реализует класс типа монады (вид набора перегруженных функций). То есть вы можете выполнять IO без монадической цепочки. Например,putStrLn :: String -> IO () или жеgetChar :: IO Char, Чтобы получить последовательность действий ввода-вывода, вам нужны функторные / монадические операции, потому что Haskell лениво оценивается.
 user644553308 сент. 2017 г., 18:53
Позвольте мне выразить это по-другому (и, кроме того, я учусь на ФП): эффект отIO monad не выполняет IO, но вы можете выполнять несколько действий IO в последовательном порядке. Это может показаться странным для разработчика, пришедшего из строго оцененного языка, такого как Javascript. Но в Haskell лексический порядок действий в вашем коде может отличаться от порядка их фактической оценки. Таким образом, вам не нужна монада для выполнения ввода-вывода, но вам нужна монада для последовательного выполнения нескольких операций ввода-вывода.
 IMSoP08 сент. 2017 г., 18:29
@ Боюсь, я не понимаю, что ты говоришь. Вы имеете в виду, что вы можете лечитьIO как "состояние мира", а не использовать абстракцию монады? Я, конечно, не эксперт по этому вопросу, поэтому счастлив учиться, но я изо всех сил пытаюсь понять, чтоString -> IO () значит - откуда результат "откуда"?
 Aadit M Shah08 сент. 2017 г., 18:12
Я согласен. Монады не требуются для IO. Вы должны добавить это в качестве отказа от ответственности в ваш ответ.

passAround :: Monad m => (a -> m b) -> a -> m a
passAround f x = do
   f x
   return x

Прочитайте подпись типа как «passAround берет на себя функциюf :: a -> m b, чей результатмонадическое действие (то есть то, что может иметь побочные эффекты, которые могут быть упорядочены в четко определенном порядке, таким образом,Monad m ограничение) с произвольным типом результатаbи значениеa пройти эту функцию. Это дает монадическое действие с типом результатаa«.

Чтобы увидеть, какой «функциональной конструкции программирования» это может соответствовать, давайте сначала развернем этот синтаксис. В Хаскелеdo нотация секвенирования является просто синтаксическим сахаром длямонадные комбинаторыа именно

     do
      foo
      bar

это сахар дляfoo >> bar, (Это немного тривиально, все становится действительно интересным, когда вы также привязываете локальные результаты к переменным.)

Так,

passAround f x = f x >> return x

>> сам является сокращением для общего оператора монадической цепочки, а именно

passAround f x = f x >>= const (return x)

или же

passAround f x = f x >>= \y -> return x

(Эта обратная косая черта обозначает лямбда-функцию, в JavaScript она будет читатьf(x) >>= (y)=>return x.)

Теперь, для чего вы действительно хотите всего этого,формирование цепочки несколько действий. В Javascript вы бы написалиg(passAround(f, x))в Haskell это не просто аргумент функции, потому что это все еще монадическое действие, поэтому вам нужен еще один оператор монадической цепочки:g =<< passAround f x или же

passAround f x >>= g

Если мы расширимpassAround здесь мы получаем

(f x >>= \y -> return x) >>= g

Теперь здесь мы можем применитьзаконы монадыа именно закон ассоциативности, дающий нам

f x >>= (\y -> return x >>= g)

а теперь закон левого блока

f x >>= (\y -> g x)

IOW, вся композиция рушится доf x >> g x, что также может быть написано

 do
  f x
  g x

... что-то вродеДух, Что из всего этого? Что ж, хорошо то, что мы можем абстрагироваться от этой перемотки монады с помощьюмонадный трансформатор, В Хаскеле это называетсяReaderT, Что бы вы сделали, если бы знали этоf а такжеg оба используют переменнуюxможно обменять

f :: a -> m b
g :: a -> m c

с участием

f' :: ReaderT a m b
f' = ReaderT f
g' :: ReaderT a m c
g' = ReaderT g

ReaderT Значение конструктора концептуально соответствует вашемуpassAround функция.

Обратите внимание, чтоReaderT a m c имеет форму(ReaderT a m) c или, игнорируя детали,m' c, гдеm' являетсяопять монада! И, используяdo Синтаксис этой монады, вы можете просто написать

 runReaderT (do
     f'
     g'
  ) x

который будет выглядеть в JavaScript,теоретически, любить

 runReaderT (() => {
      f';
      g';
    }, x)

К сожалению, вы не можете написать это таким образом, потому что в отличие от Haskell, императивные языки всегда используют одну и ту же монаду для упорядочения своих операций (что примерно соответствуетIO монада). Кстати, это одно из стандартных описанийчто такое монада: этоперегруженный оператор с запятой.

Что тыМожно однако, безусловно, реализуем монадный преобразователь динамических типов в функциональной части языка JavaScript. Я просто не уверен, стоит ли это усилий.

 leftaroundabout08 сент. 2017 г., 20:12
Конечно это недовольно точный, потому что эти языки также имеют «неявную последовательность», позволяя вызывать побочные функции в аргументах других функций.
 user644553308 сент. 2017 г., 19:20
... императивные языки всегда используют одну и ту же монаду для упорядочения своих операций (что примерно соответствует монаде Haskell IO). Это интересный способ думать об этом.
 maxhallinan09 сент. 2017 г., 13:00
Большое спасибо за такой подробный ответ. Возможно, стоит реализовать этот преобразователь как способ развития понимания. :)

Ваш ответ на вопрос