Сайд эффекты что это
Перейти к содержимому

Сайд эффекты что это

  • автор:

5 неожиданных побочных эффектов обычных лекарств – рецептурных и ОТС

5 неожиданных побочных эффектов обычных лекарств – рецептурных и ОТС

Некоторые популярные лекарства могут вызвать странные, неожиданные побочные реакции, такие как дальтонизм, ощущение дежавю или даже спровоцировать игроманию.

Даже наиболее изученные, широко назначаемые лекарства могут иметь нежелательные побочные эффекты, такие как расстройство желудка, сонливость и усталость. Это цена, которую мы платим за решение более серьезных медицинских проблем. Некоторые лекарства, тем не менее, могут вызывать неожиданные и необычные побочные эффекты, весьма отличные от тех, которые перечислены в инструкции. Некоторые популярные препараты могут вызвать странные, неожиданные реакции, такие как дальтонизм, ощущение дежавю или даже спровоцировать игроманию. Хотя эти неожиданные побочные эффекты, как правило, редки и не опасны для жизни, о них следует помнить – и фармацевту, и пациенту.

Побочное действие лекарств

Побочные эффекты популярных лекарств

1. Препарат: сильденафил (Viagra). Неожиданный побочный эффект: дальтонизм

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

Считается, что данный внезапный побочный эффект обусловлен прерыванием кровоснабжения в области зрительного нерва. В случае его развития медицинские специалисты рекомендуют решать проблему эректильной дисфункции при помощи других препаратов, например, курса тестостерон-заместительной терапии – она считается идеальной с точки зрения долгосрочного решения.

2. Препарат: дапсон. Неожиданный побочный эффект: симптомы, похожие на отравление угарным газом

Антибиотик, активный в отношении широкого спектра микроорганизмов, часто назначают для предотвращения инфекций людям со слабой иммунной системой, включая пациентов с ВИЧ или лепрой (проказой), а также другими кожными заболеваниями. При этом, согласно статистике, от 1 до 5 процентов, принимающих этот препарат, жалуются на расстройство, известное как приобретенная метгемоглобинемия. Оно включает такие симптомы, как затруднение дыхания, цианоз (серая или синяя кожа), нарушение сердечного ритма, боль в груди и слабость – т.е. типичные проявления отравления угарным газом. Между этими двумя состояниями есть огромная разница – приобретенная метгемоглобинемия не опасна для жизни, как реальная интоксикация оксидом углерода. Что касается нежелательного эффекта дапсона, то его объясняют воздействием на гемоглобин в эритроцитах. В большинстве случаев приобретенной метгемоглобинемии не нужно предпринимать никаких действий, кроме как прекратить использование препарата, заменив его альтернативным антибиотиком.

3. Препарат: лоразепам. Неожиданный побочный эффект: дежавю

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

Согласно отчетам, чуть менее 5% людей, которые принимают это лекарство от тревожности, равно как некоторые другие препараты класса бензодиазепинов, таких как алпразолам, хлордиазепоксид, клоразепат и даже известный диазепам, сталкивается с этим странным побочным эффектом, объяснить который пока не удается. Известно лишь, что прием бензодиазепинов в сочетании с лекарственными средствами, влияющими на выработку нейротрансмиттеров, таких как дофамин, может повысить риск его возникновения или усилить дежавю.

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

В случае развития нежелательных явлений специалисты рекомендуют переключиться на другой класс седативных препаратов, в том числе – рассмотреть натуральные альтернативы, такие как корень валерианы, пассифлора, хмель и пр.

4. Препараты: ропинирол и прамипексол. Неожиданный побочный эффект: игромания

Прамипексол и ропинирол назначают для снижения симптомов болезни Паркинсона, а также при синдроме беспокойных ног или судорогах в ногах. Они оказывает терапевтическое действие путем активации дофаминовых рецепторов у людей с дефицитом моторных нейротрансмиттеров, влияющий как на двигательные функции, так и на настроение. Приблизительно для 5-10% пациентов (преимущественно у молодых мужчин) повышенный синтез дофамина может стимулировать неожиданные и нежелательные реакции, в том числе навязчивое желание играть азартные игры.

5. ОТС-препараты: анальгетики при головной боли

Неожиданные побочные эффекты случаются также при использовании «безобидных» безрецептурных препаратов. Статистика показывает, что более 80% взрослого населения принимают безрецептурные средства (например, ибупрофен, парацетамол и многие другие НПВС) минимум раз месяц. Легко понять почему: они просты в использовании, эффективны и доступны, поэтому неудивительно, что многие пациенты обращаются за помощью к лекарствам, отпускаемым без рецепта (OTC), когда им досаждает простуда или головная боль. Однако некоторые из них – даже самые изученные – могут преподнести сюрприз в виде неожиданного побочного эффекта.

Например, небезобидны болеутоляющие средства класса НПВС. Чрезмерное \ неконтролируемое использование болеутоляющих лекарств может фактически привести к обратному эффекту – спровоцировать или усугубить головную боль. В группе риска – люди, принимающие обезболивающие (OTC или рецептурные) более 2-3 раз в неделю или более 10 дней в месяц.

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

  • Теги:
  • фальсификация лекарств
  • аптечный бизнес
  • обучение
  • антибиотик

[thoughts] Сайд-эффекты: можно ли лучше чем redux-saga и ngrx/effects?

Я некоторое время работал с redux-saga и ngrx/effects. От этого у меня слегка припекло и я начал раздумывать о более простой модели сайд-эффектов.

Критиковать эти инструменты — утомительно. Если вкратце, то они приносят больше сложности, чем необходимо для решения задач. Но все же, я попробую критику превратить в “чего хочется”.

У redux-saga одна из проблем заключается в том, что там слишком легко пропустить событие (см. “Non-blocking calls”). Другая проблема — использование генераторов, что порождает трудности дебага и типизации. С другой стороны, redux-saga позволяет описать понятным образом сайд-эффекты (если не обращать внимание на нейминг).

Проблему ngrx/effects сформулировать трудно. Если быть точнее, то проблема заключается в сложности модели rxjs, заточенной под все-подряд. Сходите на документацию ngrx/effects, проскролльте до конца и убедитесь сами в том, что оно ебанутое. С другой стороны, фишки вроде возможности сделать filter, map, debounce на потоке событий — крутая штука.

Чутка терминологии

Дальше в статье сайд-эффекты будут упоминаться только в такой трактовке:

Сайд-эффект — это логика, которая делает что-то в ответ на изменение модели (стора) или в ответ на событие, связаное с моделью (стором).

Любые иные сайд-эффекты рассматриваться не будут — ими пускай занимается соответствующий слой.

Дисклеймер

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

Чего хочется

  • Я хочу легко диспатчить ноль/один/бесконечно экшенов в ответ на случившийся экшен (ngrx/effects плох)
  • Я хочу знать легко знать стейт до экшена и стейт после экшена (ngrx/effects плох)
  • Я хочу легко дебажить и понятные стектрейсы (ngrx/effects плох)
  • Я не хочу ощущать себя ракетостроителем (redux-saga и ngrx/effects плохи)
  • Я хочу понятный код, совместимый с основными концептами typescript (redux-saga и ngrx/effects плохи)
  • Я хочу, чтобы пропустить событие было трудно (redux-saga плох)
  • Я хочу иметь гипотетическую возможность реагировать на последовательность экшенов (ngrx/effects плох)
  • Я хочу, чтобы простые сайд-эффекты описывались простым кодом, а сложные — соизмеримым по сложности (ngrx/effects плох)

Истоки проблем

Почти вся история развития redux-saga, rxjs и прочего говна основана на том, что в языках программирования нету средств для удобной работы со стримами.

Соответственно, люди придумывают различные костыли для работы с ними. Подобную историю вы можете вспомнить насчет концепции Promise/Task. Одни ребята улучшали убожество промисов добавлением новых методов на все случаи жизни. Другие ребята пытались с помощью yield приделать императивный вид асинхронному коду. А потом разработчики языков проснулись и убрали нужду в костылях.

А разве у нас реально сейчас есть нужда в костылях?

Улучшаем redux-saga

Итак, для начала просто. Нам нужно убрать к черту генераторы. По-сути, нужда в генераторах не так сильна, если убрать часть фич (которые мне не нужны).

Для начала ограничимся функциями take, select, put, call, fork. А функционал отмены тасков и коммуникацию через каналы — уберем.

Мы можем с легкостью заменить все вызовы yield smth(. ) на вызовы await smth(. ) или даже smth(…) . Но, нужно будет передавать эти функции внутрь описываемого эффекта (чтобы была привязка к стору).

Так, например, функция на redux-saga:

function* watchAndLog() while (true) const action = yield take('*') 
const state = yield select()
console.log('action', action)
console.log('state after', state)
>
>

Превратится в такую функцию:

async function watchAndLog(< waitFor, getState >) while (true) const action = await waitFor('*') 
const state = getState()
console.log('action', action)
console.log('state after', state)
>
>

Я переименовал take -> waitFor , select -> getState для большей ясности.

Как должна работать функция waitFor? Мы где-то будем хранить буффер всех произошедших экшенов, но еще необработанных экшенов. Вызов waitFor будет выдавать следующий произошедший экшен и убирать его из буффера. Если экшенов нет — будет ждать пока появятся. Придется произвести такие же манипуляции с getState для консистентности. Прим.: оригинальная функция take работает не так — она не хранит буффер, просто ждет новый таск.

Нужда в функции call (и apply) пропадает — сейчас можно просто вызвать функцию и не капать себе на мозг.

Функция fork будет создавать для подсаги независимый буффер экшенов/стейта — чтобы независимые саги не конкурировали за события.

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

При наличии функции fork, реализовать функции takeEvery, takeLatest, takeLeading является тривиальной задачей (это всего-лишь сахар). Без fork уже нетривиально, но все еще реально.

Функционал throttle и debounce тоже можно реализовать, но есть риск, что сильно изменится синтаксис.

Улучшаем ngrx/effects

Мое предложение такое: а давайте больше не будем выебываться, и будем использовать ныне нативный AsyncIterable вместо Observable.

Для начала, мы можем написать конвертер Observable → AsyncIterable. Этот конвертер должен буфферизировать все поступающие события и выдавать их по требованию.

Также вместо потока “экшенов” мы будем работать с потоком “экшен + + новое состояние”. Можно еще добавить даже предыдущие состояния, но это чтобы совсем все засахарилось.

Это даст нам возможность писать следующий код:

async function watchAndLog(< events >) for await (const event of events) console.log('action', event.action) 
console.log('state after', event.stateAfter)
>
>

Окей, что насчет filter/map? Ну гипотетически, мы можем их добавить к нашему AsyncIterable, как и остальные необходимые нам методы. Ну это если нам очень нужно: ведь внутри сайд-эффектов нет большой необходимости в этом.

Либо еще проще: передавать внутрь сайд-эффекта Observable, а сайд-эффект сам пускай конвертирует его в AsyncIterable, предварительно вызывая любые необходимые операторы типа filter/map.

Гипотетически мы можем написать функцию pipe, которая приводит AsyncIterable к Observable, применяет оператор, и конвертирует обратно. Ну так, чисто в порядке бреда.

Если мы хочем дожидаться цепочку ивентов, то нам нужно, чтобы events были не просто AsyncIterable, а AsyncIterableIterator. Тогда мы сможем написать такой код:

async function watchAndLog(< events >) for await (const event of events) console.log('action', event.action) 
console.log('state after', event.stateAfter)
console.log('next action', await getNextEvent(events));
>
>
async function getNextEvent(events) return (await events[Symbol.asyncIterator]().next()).value;
>

Мы можем пойти чуть дальше, и сделать так, чтобы нашему потоку событий нельзя было сказать “горшочек, не вари”. Для этого придется убедиться, что функция return у нашего AsyncIterator делает ничего (или отсутствует). Тогда мы сможем использовать несколько for await.

В частности, это сделает возможным реализацию подобного кода (это переделанный пример из документации redux-saga):

async function loginFlow(< events >) while (true) const loginEvent = await getNextEvent(events, 'LOGIN_REQUEST'); 
const token = await getToken(loginEvent.action);
if (token) storeToken(token);
const logoutEvent = await getNextEvent(events, 'LOGOUT');
clearToken(token);
>
>
>
async function getNextEvent(events, actionType) for await (const event of events) if (event.action.type === actionType) return event;
>
>
>

И да, в подобной модели придется явно диспатчить экшены, но это к лучшему.

Возможно вас заинтересуют другие мои статьи:

Apple Swift, с тобой интересно!

Интересные особенности языка программирования Swift для разработки iOS приложений

Apple Swift, с тобой интересно!

Побочные эффекты (Side Effects).

Posted on February 1, 2019 by tatiana.kornilova@gmail.com

Это перевод статьи-эпизода “Side Effects”, размещенной на сайте pointfree.co.
Код для этого фрагмента можно найти здесь.

“Побочные эффекты” – это то, без чего не можем жить; не можем писать программы. Давайте исследуем некоторого рода “побочные эффекты”, с которыми мы сталкиваемся каждый день. Давайте выясним, почему они делают код трудным для тестирования, и как мы можем управлять ими, не теряя при этом возможности “композиции” (composition).

Введение

У нас был целый эпизод, посвященный исключительно функциям, в котором мы сделали акцент на важности ТИПОВ входов и выходов функций для того, чтобы понять, как можно применить к ним “композицию” (compose). Но есть множество других вещей, которые могут делать функции и которые нельзя “поймать” исключительно их сигнатурой. Эти вещи называются “побочные эффекты” (“side effects“).

“Побочные эффекты” – это один из главных источников сложности кода и хуже того, источник усложнения тестирования. Они также не способствуют “композиции” функций. Из прошлого эпизода мы видели, что можно извлечь множество преимуществ из “композиции” функций, но “побочные эффекты” “вставляют нам палку в колеса” в этом.
В данном эпизоде мы рассмотрим некоторые типы “побочных эффектов”, покажем, почему так трудно их тестировать, почему они мешают “композиции” и попытаемся эффективно разрешить эти проблемы.

“Побочные эффекты” – это немного перегруженный термин, и для того, чтобы его определить, давайте сначала посмотрим на функцию, у которой нет “побочных эффектов”:

func compute(_ x: Int) -> Int

Если мы вызовем эту функцию, то получим результат:

compute(2) // 5

Действительно замечательным свойством функций без “побочного эффекта” является то, что не имеет значения, сколько раз мы вызываем эту функцию с одним и тем же входом, мы ВСЕГДА получим один и тот же выход:

compute(2) // 5 compute(2) // 5 compute(2) // 5

Такая предсказуемость позволяет писать очень простые тесты для них.

assertEqual(5, compute(2))


Если мы пишем тест с неправильным ожидаемым результатом или неправильный вход с правильным ожидаемым результатом, то оба теста будут всегда проваливаться.

assertEqual(4, compute(2))
assertEqual(5, compute(3))


Давайте добавим “побочный эффект” в нашу функцию.

func computeWithEffect(_ x: Int) -> Int < let computation = x * x + 1 print("Computed \(computation)") return computation

Мы вставили предложение print прямо в середину.
Если мы как и раньше вызываем функцию computeWithEffect с одним и тем же входом, то мы получаем тот же самый выход:

computeWithEffect(2) // 5

Но если мы посмотрим на нашу консоль, то увидим там дополнительный выход.

Computed 5

Если мы сравним сигнатуру функции computeWithEffect с сигнатурой функции compute , то она окажется той же самой, но при сравнении выполненной функциями работы мы должны принимать во внимание не только сигнатуру этих функций. Функция print вышла во внешний МИР и произвела там изменения, в нашем случае, напечатала что-то на консоли. “Побочные эффекты” требуют понимания внутреннего устройства функции, чтобы знать, что там скрывается.
Давайте напишем тест для этой функции:

assertEqual(5, computeWithEffect(2))


Тест прошел успешно! Но теперь у нас появилась дополнительная строка на консоли.

Computed 5 Computed 5

Это как раз то “поведение”, которое мы не можем тестировать. Да, в нашем случае мы просто печатаем на консоли, и это, в конце концов, не так уж и важно, но если мы вместо печати используем другой “побочный эффект”, например, запись на диск или создание API запроса или будем отслеживать какую-то аналитику, мы начнем больше беспокоиться об этом “поведении” и о том, чтобы его тестировать.

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

[2, 10].map(compute).map(compute) // [26, 10202] [2, 10].map(compute >>> compute) // [26, 10202]

Давайте посмотрим, работает ли это правило для computeWithEffect :

[2, 10].map(computeWithEffect).map(computeWithEffect) // [26, 10202] [2, 10].map(computeWithEffect >>> computeWithEffect) // [26, 10202]

Возвращаемые значения совпадают, но если мы посмотрим на консоль, то эти “поведения” не совпадают!

Computed 5 Computed 101 Computed 26 Computed 10202 -- Computed 5 Computed 26 Computed 101 Computed 10202

Мы больше не можем воспользоваться преимуществами этого правила без рассмотрения “побочных эффектов”. Наша способность выполнить такого рода рефакторинг – это реальная оптимизация производительности: вместо того, чтобы обходить наш массив дважды, мы обходим его один раз. Но если ваша функция имеет “побочные эффекты”, порядок выполнения не тот же самый, то есть мы становимся зависимыми от порядка выполнения функций! Выполнение такого рода оптимизации производительности в МИРЕ “побочных эффектов” может разрушить ваш код!

Скрытые выходы

Давайте рассмотрим простейший способ управления этим “побочным эффектом”. Вместо того, чтобы выполнять “побочный эффект” в теле функции, мы можем вернуть дополнительное значение, которое описывает, что нам необходимо напечатать. Функция в теории могла бы распечатывать множество вещей, ток что для моделирования этой ситуации мы используем массивом строк [String] .

func computeAndPrint(_ x: Int) -> (Int, [String]) < let computation = x * x + 1 return (computation, ["Computed \(computation)"]) >computeAndPrint(2) // (5, ["Computed 5"])

Мы получаем не только результат вычислений, но и массив логов (logs), которые мы хотим распечатать.
Давайте напишем тест:

assertEqual( (5, ["Computed 5"]), computeAndPrint(2) )


Теперь мы получили не только результат вычисления, но и сам “побочный эффект”, который мы хотим выполнить! Наш тест проваливается, если “побочный эффект” не отвечает ожидаемому формату:

assertEqual( (5, ["Computed 3"]), computeAndPrint(2) )


Здесь данные очень простые, но мы должны помнить, что потенциально они могут быть гораздо сложее, описывая API запроса или событие аналитики, и мы должны иметь возможность написать assertions , которые подготавливают этот “побочный эффект” к сравнению с тем, что мы ожидаем от него.
Рассматривая такого рода “побочные эффекты”, мы понимаем, что “побочный эффект”, который делает изменения во внешнем МИРЕ, есть ничто иное, как скрытый неявный выход этой функции. Неявность – обычно не очень хорошая вещь в программировании.
Теперь вы можете спросить: “Ну хорошо, а кто будет выполнять этот “побочный эффект”?” Выталкивая “побочный эффект” наружу в возвращаемый ТИП, мы перекладываем ответственность за этот “побочный эффект” на того, кто вызывает эту функцию.
Например:

let (computation, logs) = computeAndPrint(2) logs.forEach

Возможно, вызывающий метод тоже не захочет иметь дело с этими “побочными эффектами” и передаст их дальше. Возможно, что последующий вызывающий метод также не захочет иметь с ними дело и так далее! Это похоже на неразрешимую проблему, но есть ряд способов ее решить, хотя нам необходимо детально понять ее.
Может показаться, что мы решили нашу проблему “побочных эффектов”, но мы просто заменили ее описанием выхода нашей функции. И, к сожалению, сломали одну из наиболее важных возможностей функций: их “композицию” (composition).

Наша функция compute – совершенно замечательная, потому что может составлять “прямую композицию” (forward-composition) сама с собой.

compute >>> compute // (Int) -> Int

И другая наша функция computeWithEffect – также замечательная, так как также может составлять “прямую композицию” (forward-composition) сама с собой.

computeWithEffect >>> computeWithEffect // (Int) -> In

Мы можем “протаскивать” в них значения через “конвейерный” оператор |> и получать результаты.

2 |> compute >>> compute // 26 2 |> computeWithEffect >>> computeWithEffect // 26

Конечно, теперь мы опять возвращаемся к тому, что у нас функция computeWithEffect печатает на консоли.

Computed 5 Computed 26

Межде тем, наша попытка решить эту проблему с помощью функции computeAndPrint не позволяют использовать “композицию”.

computeAndPrint >>> computeAndPrint // Cannot convert value of type '(Int) -> (Int, [String])' // to expected argument type '((Int, [String])) -> (Int, [String])' // (не могу преобразовать значение ТИПА '(Int) -> (Int, [String])' // в ожидаемый ТИП аргумента '((Int, [String])) -> (Int, [String])'

Выходом функции computeAndPrint является кортеж (tuple) (Int, [String]) , а входом исключительно целое число Int .
Мы видели до этого и видим опять: каждый раз, когда у нас есть функция, которой необходимо выполнять “побочные эффекты”, мы расширяем ТИП возвращаемого значения для того, чтобы описать “побочный эффект”, и мы “разрываем” возможность “композиции” функций. Теперь наша работа будет состоять в том, чтобы найти некоторый способ усиления “композиционности” такого рода функций.
В нашем случае, когда функция возвращает кортеж (tuple), мы можем поправить “композиционность” простым замечательным способом. Мы будем также поступать и в более общем случае, чем просто функция computeAndPrint. Давайте определим функцию compose , чья работа будет заключаеться исключительно в том, чтобы выполнять “композицию” такого рода функций.

func compose( _ f: @escaping (A) -> (B, [String]), _ g: @escaping (B) -> (C, [String]) ) -> (A) -> (C, [String]) < // … >

Это выглядит очень знакомо. Сигнатура этой функции очень похожа на сигнатуру нашей функции >>> : мы имеем (A) -> B , (B) -> C и (A) -> C , но там присутствует еще дополнительная информация.
Мы можем реализовать эту функцию, смотря на ТИП функции и значения, которые имеются в нашем распоряжении.

func compose( _ f: @escaping (A) -> (B, [String]), _ g: @escaping (B) -> (C, [String]) ) -> (A) -> (C, [String])

Мы знаем, что мы возвращаем функцию, так что мы начинаем с того, что открываем фигурную скобку и “привязываем” a . У нас есть также функция f , которая берет на входе A s, так что давайте передадим ей a и “привяжем” возвращаемые значения, в этом случае это будет b и некоторый массив строк logs . Теперь, когда у нас есть b , можем передать его в функцию g , и вернуть c и некоторый массив строк moreLogs . Теперь у нас есть c , и мы можем вернуть его в качестве возвращаемого значения наряду с массивом строк logs . Мы могли бы вернуть logs или moreLogs , то в нашем случае имеет смысл вернуть конкатенацию обоих.

Так что давайте проведем “композицию” наших функций!

compose(computeAndPrint, computeAndPrint) // (Int) -> (Int, [String])

Мы создали совершенно новую функцию, которая вызывает computeAndPrint дважды. Если вы дадите ей данные, то получите не только результат вычислений, но и logs каждого этапа ее выполнения.

2 |> compose(computeAndPrint, computeAndPrint) // (26, ["Computed 5", "Computed 26"])

Представление оператора >=>

Кажется, что мы разрешили все “композиционные” проблемы, но все опять начинает запутываться, если мы подвергаем “композиции” более, чем 2 функции.

2 |> compose(compose(computeAndPrint, computeAndPrint), computeAndPrint)

Еще хуже то, что существуют два различных способа “композиции” тех же самых функций:

2 |> compose(compose(computeAndPrint, computeAndPrint), computeAndPrint) 2 |> compose(computeAndPrint, compose(computeAndPrint, computeAndPrint))

Круглые скобки – всегда “враги” “композиции”.
А кто “враги” круглых скобок?
Инфиксные операторы.

Мы знаем, что мы всегда хотим применять “композицию” многократно в одной строке и мы хотим иметь возможность подать с помощью “конвейерного” оператора |> значение на эти композиции, так что давайте определим ассоциативную приоритетную группу precedencegroup с приоритетом выше, чем приоритет “конвейерного” оператора |> .

precedencegroup EffectfulComposition

Теперь мы можем определить инфиксный оператор >=> , который выглядит очень знакомым.

infix operator >=>: EffectfulComposition

Этот оператор очень похож на оператор >>> , но мы заменили среднюю стрелку “ > ” на символ равенства “ = “, напоминающий “трубу”. Этот оператор иногда называют забавным именем “fish” (рыба).

Теперь мы можем переименовать нашу функцию compose и “склеить” наши функции с “побочными эффектами” вместе без всякого постороннего “шума” и необходимости думать о круглых скобках.

func >=> ( _ f: @escaping (A) -> (B, [String]), _ g: @escaping (B) -> (C, [String]) ) -> (A) -> (C, [String]) < return < a in let (b, logs) = f(a) let (c, moreLogs) = g(b) return (c, logs + moreLogs) >> computeAndPrint >=> computeAndPrint >=> computeAndPrint // (Int) -> (Int, [String])

Мы можем подать через конвейерный оператор |> значение на эту многостроковую “композицию” и создать “конвейерную” обработку, которая прекрасно читается сверху донизу.

2 |> computeAndPrint >=> computeAndPrint >=> computeAndPrint

Другая замечательная вещь относительно того, что мы перевели нашу “композицию” в МИР операторов, состоит в том, что наш новый оператор >=> может прекрасно взаимодействовать с уже существующими операторами наподобие >>> .

2 |> computeAndPrint >=> (incr >>> computeAndPrint) >=> (square >>> computeAndPrint)

Здесь мы можем взять результат функций с “побочными эффектами” и применить их к функциям, которые не имеют “побочных эффектов”, и все это с помощью “композиции”. У нас появились новые проблемы с круглыми скобками, но мы можем их разрешить! Группа операторов ForwardСomposition , возможно, наиболее сильная форма композиции, приводящая к появлению круглых скобок, согласовывая при этом входные и выходные типы. В результате, мы приходим к заключению, что группа операторов Forward Сomposition ВСЕГДА имеет более высокий приоритет выполнения операторов. Поэтому нам необходимо модифицировать приоритет для группы операций EffectfulComposition .

precedencegroup EffectfulComposition

У нас нет необходимости использовать круглые скобки и мы можем передать дальше по “конвейеру” (pipeline) нашу “композицию”.

2 |> computeAndPrint >=> incr >>> computeAndPrint >=> square >>> computeAndPrint

Каждая строка в этом выражении теперь аннотируется оператором, который придает ей определенное значение. Строки с префиксом >>> имеют дело с результатом функции, которая не имеет “побочных эффектов”, в то время как строки с префиксом >=> имеют дело с результатом вычислений, имеющих “побочный эффект”.

Итак, мы ввели новый оператор >=> , и пришло время установить законность его добавления в наш код.

  1. Этот оператор уже существует в Swift? Нет. Нет никакого шанса “перегрузить” (overload) уже существующий оператор.
  2. Есть ли этот оператор у языков программирования – ПРОТОТИПОВ и имеет ли он информативную форму? Да! Оператор “fish” встроен в Haskell и PureScript, а многие другие языки ввели его в свои функциональные библиотеки. Форма >=> этого оператора совершенно замечательная, особенно вместе с оператором >>> . Оператор >=> чуть-чуть отличается от оператора >>> , что является индикатором того, что что-то еще здесь происходит.
  3. Является ли этот оператор универсальныи или он служит только каким-то локальным очень специфическим целям? Способ, каким мы его определили в данный момент, является достаточно специфическим и работает только на кортежах (tuples), но форма, которую он описывает, постоянно появляется в программировании. Мы можем даже определить этот оператор на паре других ТИПОВ Swift:
func >=> ( _ f: @escaping (A) -> B?, _ g: @escaping (B) -> C? ) -> ((A) -> C?) < return < a in fatalError() // упражнение для читателя >>

Мы заменили наши кортежи (tuples) Optionals и получили оператор, который помогает выполнять “композицию” функций, которые возвращают Optionals . Теперь мы можем создать цепочку из пары “проваливающихся” (failable) инициализаторов, соединенных вместе:

String.init(utf8String:) >=> URL.init(string:) // (UnsafePointer) -> URL?

В результате мы получили совершенно новый “проваливающийся” инициализатор без каких-либо усилий!
Мы могли бы также использовать этот оператор для улучшения “композиционных” способностей функций, возвращающих массив Array :

func >=> ( _ f: @escaping (A) -> [B], _ g: @escaping (B) -> [C] ) -> ((A) -> [C]) < return < a in fatalError() // упражнение для читателя >>

И если бы мы использовали ТИПЫ Promise или Future , то также могли бы использовать этот оператор для “композиции” функций, которые возвращают Promise :

func >=> ( _ f: @escaping (A) -> Promise, _ g: @escaping (B) -> Promise ) -> ((A) -> Promise) < return < a in fatalError() // an exercise for the viewer >>

Мы видим эту форму оператора снова и снова. В некоторых языках с очень мощной системой ТИПОВ возможно определить этот оператор один раз и получить все его реализации незамедлительно. Swift пока еще не обладает такой способностью, так что мы должны заново определять его для всех новых ТИПОВ. Мы можем создать такого рода оператор на интуитивном уровне для многих ТИПОВ. Если мы видим оператор >=> , то мы должны знать, что это цепочка некоторого рода “побочных эффектов”.

Скрытые входы.

Мы рассмотрели “побочный эффект”, который приводит к скрытому выходу и показали, как этим можно управлять, если сделать это выход явным в наших функциях, сохранив при этом их “композиционность”. Есть и другого рода “побочный эффект”, который является более хитроумным.
Давайте взглянем на простую функцию, которая создает приветствие пользователю.

func greetWithEffect(_ name: String) -> String < let seconds = Int(Date().timeIntervalSince1970) % 60 return "Hello \(name)! It's \(seconds) seconds past the minute." >greetWithEffect("Blob") // "Hello Blob! It's 14 seconds past the minute."

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

assertEqual( "Hello Blob! It's 32 seconds past the minute.", greetWithEffect("Blob") )


Это особенно плохой “боковой эффект”. В предыдущем случае мы могли хотя бы написать тестовые утверждения assertions для проверки хоть каких-то выходов, хотя, конечно, мы не могли тестировать их все. В этом случае мы не можем написать никаких тестовых утверждений потому, что выход функции все время меняется.

Нашим предыдущим “побочным эфектом” была печать с помощью функции print , которая берет некоторый вход и не возвращает никакого значения. В этом случае у нас есть функция Date , которая также является функцией, но в отличие от print она возвращает значение, не беря ничего на входе.

Давайте посмотрим, сможем ли мы использовать то же самое решение для этого “побочного эффекта”. Ранее мы преобразовали “побочный эффект” print в ЯВНО возвращаемое значение функции compute , а здесь мы можем преобразовать “побочный эффект” Date ЯВНО в аргумент функции.

func greetWithEffect(_ name: String) -> String < let seconds = Int(Date().timeIntervalSince1970) % 60 return "Hello \(name)! It's \(seconds) seconds past the minute." >greetWithEffect("Blob") // "Hello Blob! It's 14 seconds past the minute."

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

assertEqual( "Hello Blob! It's 39 seconds past the minute.", greet(at: Date(timeIntervalSince1970: 39), name: "Blob") )


Мы восстановили тестируемость с помощью некоторого шаблона. Тот, кто будет вызывать эту функцию, должен передать дату date ЯВНО, что может оказаться необязательным за пределами наших тестов. Мы можем соблазниться и скрыть эту деталь реализации, определяя аргументу date значение по умолчанию и ввести зависимость от текущей даты Date() в нашу функцию, чтобы убрать небходимость явного указания аргумента date .

func greet(at date: Date = Date(), name: String) -> String < let s = Int(date.timeIntervalSince1970) % 60 return "Hello \(name)! It's \(s) seconds past the minute." >greet(name: "Blob")

Это читается прекрасно, но у нас появилась большая проблема: мы опять сломали “композиционность”.

Наша первая функция greetWithEffect имела прекрасную форму (String) -> String и могла “композироваться” с другими функциями, которые возвращают строку String и функциями, которые имеют в качестве входа String .

Давайте создадим простейшую функцию uppercased , которая делает символы всей строки заглавными:

func uppercased(_ string: String) -> String

Она прекрасно “композируется” с функцией greetWithEffect по обе стороны.

uppercased >>> greetWithEffect greetWithEffect >>> uppercased

Мы можем через “конвейер” |> подать имя name на эти композиции и получить различное поведение этих “композиций”.

"Blob" |> uppercased >>> greetWithEffect // "Hello BLOB! It's 56 seconds past the minute." "Blob" |> greetWithEffect >>> uppercased // "HELLO BLOB! IT'S 56 SECONDS PAST THE MINUTE."

Тем не менее наша функция greet не может участвовать в “композиции” функций.

"Blob" |> uppercased >>> greet "Blob" |> greet >>> uppercased // Cannot convert value of type '(Date, String) -> String' // to expected argument type '(_) -> _' // Не могу преобразовать значение ТИПА '(Date, String) -> String' // в ожидаемый ТИП аргумента '(_) -> _'

Она берет два входа, так что нет никакого способа составить “композицию” выхода функции с ее входом. Если мы проигнорируем вход Date , мы можем увидеть, что эта функция имеет форму (String) -> String . Фактически, это небольшая хитрость, с помощью которой мы можем “вытолкнуть” Date за пределы этой сигнатуры: мы можем переписать greet таким образом, что она берет Date как вход, а возвращает совершенно новую функцию (String) -> String , которая управляет действительной логикой приветствия:

func greet(at date: Date) -> (String) -> String < return < name in let s = Int(date.timeIntervalSince1970) % 60 return "Hello \(name)! It's \(s) seconds past the minute." >>

Теперь мы можем вызывать нашу функцию greet с определенной датой date и получать совершенно новую функцию (String) -> String .

greet(at: Date()) // (String) -> String

Эта функция может участвовать в “композиции”!

uppercased >>> greet(at: Date()) // (String) -> String greet(at: Date()) >>> uppercased // (String) -> String

И мы также можем подавать на них значения с помощью “конвейерного” оператора |> !

"Blob" |> uppercased >>> greet(at: Date()) // "Hello BLOB! It's 37 seconds past the minute." "Blob" |> greet(at: Date()) >>> uppercased // "HELLO BLOB! IT'S 37 SECONDS PAST THE MINUTE."

Мы восстановили “композиционность” функции и продолжаем сохранять ее тестируемость.

assertEqual( "Hello Blob! It's 37 seconds past the minute.", "Blob" |> greet(at: Date(timeIntervalSince1970: 37)) )

Итак, мы столкнулись с “побочным эффектом”, который не позволяет нам проводить тестирование, но смогли контролировать его, переместив этот контекст на вход функции, что явилось схожей версией “побочного эффекта”, через который мы прошли ранее. Наш первый “побочный эффект” достигал ВНЕШНИЙ МИР и производил там изменения, что трактовалось нами как СКРЫТЫЙ ВЫХОД, в то же время наш второй “побочный эффект” зависил от некоторого состояния ВНЕШНЕГО МИРА, который трактовался нами как СКРЫТЫЙ ВХОД! Все “побочные эффекты” проявляются таким образом.

Изменчивость (Mutation)

Давайте посмотрим на очень специфический “побочный эффект” и проанализируем его: “изменчивость” (mutation). Нам всем приходится иметь дело с “изменчивостью” (mutation) в коде и это приводит к существенной сложности. К счастью, Swift обеспечивает нас на уровне ТИПОВ некоторой возможностью контроля этой “изменчивости” (mutation) и правильным пониманием того, как и где это может происходить.

Вот пример того, как “изменчивость” (mutation) может выйти из под контроля. Этот пример вдохновлен реальным кодом, который мы писали в прошлом, он реально очень опасен, с ним очень трудно работать до тех пор, пока вы не перепишите его заново, чтобы контролировать “изменчивость” (mutation).

let formatter = NumberFormatter() func decimalStyle(_ format: NumberFormatter) < format.numberStyle = .decimal format.maximumFractionDigits = 2 >func currencyStyle(_ format: NumberFormatter) < format.numberStyle = .currency format.roundingMode = .down >func wholeStyle(_ format: NumberFormatter)

У нас имеется NumberFormatter из фреймворка Foundation и несколько функций, которые конфигурируют числовые форматоры специальными стилями. Для использования этих стилизующих функций мы можем просто напрямую применить их к нашему форматору formatter .

decimalStyle(formatter) wholeStyle(formatter) formatter.string(for: 1234.6) // "1,235" currencyStyle(formatter) formatter.string(for: 1234.6) // "$1,234"

Если еще раз применить первое множество форматеров, то у нас возникнет проблема:

decimalStyle(formatter) wholeStyle(formatter) formatter.string(for: 1234.6) // "1,234"

Выход изменился с “1,235” на “1,234“. В чем причина? “Изменчивость” (mutation). Изменения, производимые функцией currencyStyle , затрагивают использование других наших форматоров, приводя к ошибке, которую в большом контексте очень трудно отследить.

Это пример того, почему “изменчивость” (mutation) так трудно отследить. Невозможно узнать, в какой строке это происходит до тех пор, пока мы не изучим каждую строку кода, выполняемую до этой критической строки. “Изменчивость” (mutation) – это проявление обоих “побочных эффектов”, с которыми мы сталкивались до этого, когда изменяемые передаваемые между функциями данные являются одновременно и СКРЫТЫМ ВХОДОМ и СКРЫТЫМ ВЫХОДОМ!

Причина, по которой мы рассматриваем именно эту “изменчивость” (mutation), заключается в том, что NumberFormatter – это “Reference” ТИП. В Swift классы classes являются “Reference” ТИПами. экземпляр “Reference” ТИПа – это единственный объект, который может изменяться любой частью кода, содержащего на него ссылку. Нет легкого способа определить, какая часть кода, имеющая ссылку на один и тот же объект, могла привести к множеству противоречий, если имеет место “изменчивость” (mutation). Если наш код использовался бы в приложении и на его основе была бы написана какая-нибудь новая функциональность, опирающаяся на этот formatter , то такие незаметные ошибки могли бы расползтись и по всему новому коду.

У Swift есть также “Value” ТИПЫ. Это и есть ответ Swift на управление “изменчивостью” (mutation). Когда вы присваиваете значение “Value” ТИПу, то вы получаете совершенно новую копию для работы внутри заданного контекста. Все “изменения” (mutations) являются локальными и если есть что-то еще ссылающееся на то же самое значение “выше по течению”, то оно не “видит” этих изменений.

Давайте сделаем рефакторинг кода и используем “Value” ТИПЫ.

Мы начнем со структуры struct , которая является “оберткой” вокруг конфигурации, которую мы делаем для NumberFormatter .

struct NumberFormatterConfig < var numberStyle: NumberFormatter.Style = .none var roundingMode: NumberFormatter.RoundingMode = .up var maximumFractionDigits: Int = 0 var formatter: NumberFormatter < let result = NumberFormatter() result.numberStyle = self.numberStyle result.roundingMode = self.roundingMode result.maximumFractionDigits = self.maximumFractionDigits return result >>

У этой структуры есть прекрасные значения по умолчанию и вычисляемая переменная formatter , которую можно использовать для получения новых “настоящих” NumberFormatters . На что будут похожи обновления наших “стилизирующих” функций, если вместо класса NumberFormatter использовать структуру NumberFormatterConfig ?

func decimalStyle(_ format: NumberFormatterConfig) -> NumberFormatterConfig < var format = format format.numberStyle = .decimal format.maximumFractionDigits = 2 return format >func currencyStyle(_ format: NumberFormatterConfig) -> NumberFormatterConfig < var format = format format.numberStyle = .currency format.roundingMode = .down return format >func wholeStyle(_ format: NumberFormatterConfig) -> NumberFormatterConfig

Каждая “стилизирующая” функция берет NumberFormatterConfig , копирует его, используя ключевое слово var и изменяет локальную копию прежде, чем вернуть ее вызвавшей функции или методу.
Использование этих “стилизирующих” функций выглядит немного по-другому.

let config = NumberFormatterConfig() wholeStyle(decimalStyle(config)) .formatter .string(for: 1234.6) // "1,235" currencyStyle(config) .formatter .string(for: 1234.6) // "$1,234" wholeStyle(decimalStyle(config)) .formatter .string(for: 1234.6) // "1,235"

В этом случае, каждый раз, когда мы передаем config в “стилизующую” функцию, мы получаем ее совершенно новую копию, и наша ошибка уходит!

Мы могли бы сделать что-то подобное и с ссылочными (“Reference“) ТИПАМИ, используя метод copy для классов, которые реализуют протокол NSCopying и ЯВНО возвращают эту копию:

func decimalStyle(_ format: NumberFormatter) -> NumberFormatter

К сожалению,в этой ситуации компилятор не дает нам гарантий, что мы не изменяем оригинальный format . Более того, вызывающая функция (caller) ожидая копию, может свободно проводить дальнейшие изменения и сложность начнет возрастать именно отсюда!

Из-за того, что “Reference” ТИПЫ не копируются автоматически, они имеют некоторые преимущества в производительности. К счастью, Swift снабжает нас прекрасным семантическим способом изменения значений “по месту” с помощью ключевого слова inout .

Давайте модифицируем наши “стилизующие” функции с использованием inout .

func inoutDecimalStyle(_ format: inout NumberFormatterConfig) < format.numberStyle = .decimal format.maximumFractionDigits = 2 >func inoutCurrencyStyle(_ format: inout NumberFormatterConfig) < format.numberStyle = .currency format.roundingMode = .down >func inoutWholeStyle(_ format: inout NumberFormatterConfig)

Это очень похоже на наши первоначальные функции с NumberFormatter . Мы можем напрямую выполнять изменения и не беспокоиться о копировании значений или об их возврате. Давайте попытаемся использовать эти “стилизующие” функции тем же самым способом, каким мы использовали наши оригинальные функции.

let config = NumberFormatterConfig() inoutDecimalStyle(config) inoutWholeStyle(config) config.formatter.string(from: 1234.6)

Мы получаем ошибку компиляции!

Cannot pass immutable value as inout argument: 'config' is a 'let' constant (Не могу передать неизменяемое значение в качестве inout аргумента: 'config' - это 'let' константа)

Swift даже предлагает исправить эту ошибку, заменяя let на var . Но этого недостаточно. И мы опять получаем ошибку компиляции, но другую!

Passing value of type 'NumberFormatterConfig' to an inout parameter requires explicit '&' (Передача значения ТИПА 'NumberFormatterConfig' inout параметру требует ЯВНОГО '&')

Swift требует от нас аннотации inout параметра с помощью символа ‘ & ‘ при вызове, что говорит о том, что мы согласны на изменение этих данных.

inoutDecimalStyle(&config) inoutWholeStyle(&config) config.formatter.string(from: 1234.6) // "1,235"

Мы можем продолжать вызывать наши изменяющие “стилизующие” функции тем же самым способом.

inoutCurrencyStyle(&config) config.formatter.string(from: 1234.6) // "$1,234" inoutDecimalStyle(&config) inoutWholeStyle(&config) config.formatter.string(from: 1234.6) // "1,234"

И наша ошибка опять возвращается, но на этот раз наш код просто “кричит”: “Изменение (mutation)”, и такого рода ошибку теперь уже значительно легче обнаружить.

Это здорово, что Swift снабжает нас решением проблем “изменчивости” (mutation) на уровне ТИПОВ, которая управляет тем, когда может происходить и как далеко может распространяться “изменчивость” (mutation). Но у нас все еще есть проблемы, которые мы должны решить, если хотим и дальше использовать этот механизм.

“Стилизирующие” функции, которые мы использовали ранее и которые возвращают совершенно новые копии, имеют замечательную форму:

(NumberFormatterConfig) -> NumberFormatterConfig

У них один и тот же вход и выход, а это означет, что они могут составлять “композиции” друг с другом и с любыми другими функциями, которые возвращают или берут в качестве входа NumberFormatterConfig !

decimalStyle >>> currencyStyle // (NumberFormatterConfig) -> NumberFormatterConfig

Теперь у нас есть совершенно новая “стилизующая” функция, которую мы можем составить из отдельных маленьких кусочков.

Между тем, наши inout функции не имеют эту форму: их входы и выходы не подходят друг к другу и они не могут составлять “композиции” со многими другими похожими функциями. Однако эти функции имеют ту же самую логику, так что должен существовать мост между МИРОМ inout и МИРОМ обычных функций.

Оказывается, мы можем определить функцию с именем toInout , которая преобразует функцию с одним и тем же ТИПОМ входа и выхода в inout функцию.

func toInout( _ f: @escaping (A) -> A ) -> ((inout A) -> Void) < return < a in a = f(a) >>

Мы можем также определить дуальную функцию, fromInout , которая выполнят обратное преобразование.

func fromInout( _ f: @escaping (inout A) -> Void ) -> ((A) -> A) < return < a in var copy = a f(&copy) return copy >>

То, что мы здесь видим, – это естественное соответсвие между (A) -> A функциями и (inout A) -> Void функциями. Функции из МИРА (A) -> A “композируются” очень хорошо, так что, имея это соответствие, мы можем надеяться, что функции из МИРА (inout A) -> Void могли бы также хорошо разделять их “композиционные” способности.

Представление оператора <> (“бриллиантовый” оператор).

Хотя мы видим, что (A) -> A функции “композируются” с использованием оператора >>> , нам не следует повторно использовать этот оператор, потому что у него слишком много степеней свободы. Мы рассмотрим более ограниченную “композицию” ТИПА самого с собой. Давайте определим новый оператор и начнем с группы приоритета precedencegroup SingleTypeComposion .

precedencegroup SingleTypeComposition

Теперь давайте определим наш новый оператор <> .

infix operator <>: SingleTypeComposition

Забавное имя для этого оператора <> – “diamond” (“бриллиантовый”) оператор.

Мы можем определить оператор <> для сигнатуры (A) -> A достаточно просто:

func <> ( f: @escaping (A) -> A, g: @escaping (A) -> A) -> ((A) -> A) < return f >>> g >

Это может показаться глупым: простое “оборачивание” одного оператора другим оператором, но мы ограничили его применение одним и тем же ТИПОМ для входа и выхода и перекодировали его значение: теперь, если вы видите оператор <>, то понимаете, что имеете дело с единственным ТИПОМ!

Давайте определим оператор <> для inout функций:

func <> ( f: @escaping (inout A) -> Void, g: @escaping (inout A) -> Void) -> ((inout A) -> Void) < return < a in f(&a) g(&a) >>

Наша предыдущая “композиция” работает.

decimalStyle <> currencyStyle

Более того, мы можем выполнять “композицию” наших inout “стилизирующих” функций!

inoutDecimalStyle <> inoutCurrencyStyle

Что произойдет, если мы начнем с помощью “конвейера” |> подавать значения на эти “композиции”?

config |> decimalStyle <> currencyStyle config |> inoutDecimalStyle <> inoutCurrencyStyle

Наша inout версия выдаст ошибку.

Cannot convert value of type '(inout Int) -> ()' to expected argument type '(_) -> _' (Не могу преобразовать значение ТИПА '(inout Int) -> ()' в ожидаемый аргумент ТИПА '(_) -> _' )

Ошибка произошла потому, что оператор |> пока не работает в МИРЕ inout , но мы можем определить его “перегрузку” (overload), которая будет это делать.

func |> (a: inout A, f: (inout A) -> Void) -> Void

Теперь мы можем свободно с помощью “конвейерного оператора” |> подавать значения внутрь этих изменяемых “композиций”.

config |> inoutDecimalStyle <> inoutCurrencyStyle

Замечательно! Нам не нужно жертвовать “композиционностью” для того, чтобы воспользоваться преимуществом некоторых замечательных возможностей Swift.

Мы решили эту проблему ценой еще одного дополнительного оператора <> , так что пришло время проверить его, удовлетворяет ли он необходимым требованиям.

  1. Существует этот оператор в Swift? Нет, так что нет никаких потенциальных возможностей для конфликта.
  2. Существует ли этот ператор в других языках – прототипах? Да. Он существует в языках Haskell, PureScript и других языках с сильным функциональным сообществом, которое его приняла. У него замечательная форма, которая указывает в обе стороны и является своего рода сигналом объединения вместе.
  3. Является ли этот оператор универсальным или служит исключительно местным специфическим проблемам? До сих пор мы определили этот оператор только для функций с сигнатурой (A) -> A и (inout A) -> Void , но, оказывается, оператор <> в самом общем случае используется для комбинации двух вещей того же самого ТИПА в одну, что является наиболее фундаментальным элементов вычислений вообще . Мы будем сталкиваться с этим оператором повсюду.

В чем смысл?

Пришло время спросить себя: “В чем смысл?” Мы столкнулись со множеством эффектов, которые усложняют наш код и делают его трудным для тестирования. Мы решили исправить эту ситуацию, выполнив некоторую предварительную работу по ЯВНОМУ представлению “побочных эффектов” в ТИПАХ, как входных, так и выходных данных, но сломали при этом “композиционность” этих ТИПОВ. Затем мы ввели операторы, которые помогли нам восстановить “композиционность”, специальным образом выполняя “композицию” “побочных эффектов”. Стоило ли это делать?

Мы говорим, что да! Мы смогли “подтянуть” наш код с “побочными эффектами”, который был нетестируемым и трудным для изоляции, до МИРА, где “побочные эффекты” представлены ЯВНО и где мы можем тестировать и понимать каждую строку без необходимости понимания предшествующих строк. Мы сделали все это, не сломав при этом “композиционность” этих функций. И это действительно мощно!

Между тем, наша дополнительная заблаговременная работа должна сохранить нам неимоверное количество времени, которое мы бы затратили на отладку кода со сложной паутиной “изменчивости” (mutation), времени на исправление ошибок в “побочных эффектах”, которые разносятся повсюду, времени перепрыгивания через обручи, чтобы сделать код тестируемым.
“Побочные эффекты” – это громадная тема, и мы лишь слегка ее коснулись.

Leave a Reply Cancel reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

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

Изучаю React + Redux. Объясняют, что side эффекты должны находится в middleware. Не могу понять, что такое side эффекты? Объясните, пожалуйста, простым языком. И почему их нужно использовать именно в middleware?
Например приводится пример добавления нового комментария к статье. Автор говорит, что генерацию id комментария нужно производить в middleware. Но ведь мы могли это сделать в reducer. Почему так?

  • Вопрос задан более трёх лет назад
  • 13581 просмотр

Комментировать
Решения вопроса 1

maxfarseer

Максим @maxfarseer
https://maxpfrontend.ru, обучаю реакту и компании

Sideeffect - это что-то, что может повлиять на "чистоту" вашей функции. Редьюсер же - функция. Чистая функция, это значит такая, что если ей на вход подать одни и те же параметры, то результат будет всегда один и тот же.

Пример: есть у вас в localStorage имя пользователя. И вы в коде пишите, что-нибудь такое:

case SET_DISPLAY_NAME: < return < . state, name: window.localStorage.getItem('name') ? window.localStorage.getItem('name') : action.payload, >>

Следовательно, если вы подадите на вход функции, имя Вася, то оно вам вернет Васю только если "в sideeffect локал_сторадже" нет ничего. Здесь вы не можете быть уверены, что если подать Васю, вам всегда вернется Вася.

По примеру с комментариями - не думаю что хороший пример. Айдишники генерировать будет бэкэнд ваш. Вы добавляете новый комментарий путем отправки его на сервер, с сервера приходит статус "ОК" и ваш комментарий уже с айдишником.

Бывает, что айдишники нужно генерировать самому, тогда они отлично генерятся в acftionCreator'ax. Например, делаете вы систему уведомлений, и у каждого уведомления должен быть свой id (например, тут сервер вам не нужен, вы ничего туда не отправляете, просто визуальная часть). В таком случае, я бы не стал генерировать id через middleware, а просто делал бы это в "экшенах".

Тем не менее, с генерацией айди все тоже самое, что и с localStorage. Вы не уверены, что подав на вход: имя, текст комментария и почту - получите ТОТ же результат, что и в прошлый раз с такими же входными параметрами (айдишники то разные будут!)

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *