При использовании функциональной среды, такой как Scala cats-effect
, следует ли моделировать объекты с состоянием с типом эффекта?
// not a value/case class
class Service(s: name)
def withoutEffect(name: String): Service =
new Service(name)
def withEffect[F: Sync](name: String): F[Service] =
F.delay {
new Service(name)
}
Конструкция не подвержена ошибкам, поэтому мы могли бы использовать более слабый класс типов, например Apply
.
// never throws
def withWeakEffect[F: Applicative](name: String): F[Service] =
new Service(name).pure[F]
Я думаю, все это чисто и детерминистично. Просто не ссылочно прозрачно, так как результирующий экземпляр каждый раз отличается. Это хорошее время для использования типа эффекта? Или здесь будет другой функциональный паттерн?
scala
functional-programming
scala-cats
cats-effect
Марк Канлас
источник
источник
delay
и возвращать F [Service] . В качестве примера, см.start
Метод ввода-вывода , он возвращает IO [Fiber [IO,?]] Вместо простого волокна.Ответы:
Если вы уже используете систему эффектов, она, скорее всего, имеет
Ref
тип для безопасной инкапсуляции изменяемого состояния.Поэтому я говорю: моделировать объекты с состоянием с
Ref
. Поскольку создание (а также доступ к ним) уже является результатом, это автоматически сделает создание службы эффективным.Это аккуратно обходит ваш первоначальный вопрос.
Если вы хотите вручную управлять внутренним изменяемым состоянием с помощью обычного,
var
вы должны сами убедиться, что все операции, которые касаются этого состояния, считаются эффектами (и, скорее всего, также сделаны поточно-ориентированными), что утомительно и подвержено ошибкам. Это можно сделать, и я согласен с ответом @ atl, что вам не обязательно делать создание объекта с сохранением состояния эффективным (если вы можете жить с потерей ссылочной целостности), но почему бы не избавить себя от проблем и не принять инструменты вашей системы эффектов полностью?Если ваш вопрос можно перефразировать как
тогда: да, абсолютно .
Чтобы дать пример того, почему это полезно:
Следующее работает нормально, хотя создание сервиса не дает эффекта:
Но если вы выполните рефакторинг, как показано ниже, вы не получите ошибку во время компиляции, но вы изменили поведение и, скорее всего, ввели ошибку. Если бы вы объявили
makeService
эффективный, рефакторинг не будет проверять тип и будет отклонен компилятором.Если присваивать имя методу как
makeService
(и с параметром тоже), должно быть достаточно ясно, что метод делает, и что рефакторинг не безопасен, но «локальные рассуждения» означают, что вам не нужно искать в соглашениях об именах и реализации,makeService
чтобы понять это: любое выражение, которое не может быть механически перемешано (дедуплицировано, сделано ленивым, сделано нетерпеливым, мертвый код удалено, распараллелено, отложено, кэшировано, удалено из кэша и т. д.) без изменения поведения ( т.е. не является "чистым") следует набирать как эффективный.источник
Что означает служба с сохранением состояния в этом случае?
Вы имеете в виду, что он будет выполнять побочный эффект при создании объекта? Для этого лучшей идеей было бы иметь метод, который запускает побочный эффект при запуске приложения. Вместо того, чтобы запустить его во время строительства.
Или, может быть, вы говорите, что он содержит изменяемое состояние внутри службы? Пока внутреннее изменчивое состояние не выставлено, оно должно быть в порядке. Вам просто нужно предоставить чистый (ссылочно прозрачный) метод для связи со службой.
Чтобы расширить мой второй пункт:
Допустим, мы создаем в памяти БД.
IMO, это не должно быть эффективным, так как то же самое происходит, если вы делаете сетевой вызов. Хотя вам нужно убедиться, что существует только один экземпляр этого класса.
Если вы используете
Ref
эффект от кошек, то, что я обычно делаю, этоflatMap
ссылка на точку входа, поэтому ваш класс не должен быть эффективным.OTOH, если вы пишете общую службу или библиотеку, которая зависит от объекта с состоянием (скажем, от нескольких примитивов параллелизма), и вы не хотите, чтобы ваши пользователи заботились о том, что инициализировать.
Тогда, да, это должно быть завернуто в эффект. Вы можете использовать что-то вроде,
Resource[F, MyStatefulService]
чтобы убедиться, что все закрыто правильно. Или простоF[MyStatefulService]
если нечего закрывать.источник
val neverRunningThisButStillMessingUpState = Task.pure(service.changeStateThinkingThisIsPure()).repeat(5)
)pure
что он должен быть прозрачным по ссылкам. Например, рассмотрим пример с Future.val x = Future {... }
иdef x = Future { ... }
означает другое. (Это может укусить вас, когда вы делаете рефакторинг своего кода) Но это не относится к эффектам кошки, monix или zio.