LINUX.ORG.RU

Чистые функции и внешние данные.

 ,


0

3

Имеется такая ситуация. Пишем прогу на хаскеле, по всем функциональным канонам стараясь максимально выносить код в чистые функции. В один прекрасный момент становится ясно, что некоей функции f очень нужны внешние данные (из файла, сети и т.д.). Как правильно поступить?

1. Заюзать unsafePerformIO - быстро, просто, но не кошерно.

2. Добавить еще один аргумент, те самые внешние данные, получить которые теперь задача вызывающего.

3. Завернуть функцию в IO и переделать вызов в монадический.

4. Есть еще варианты?


Ответ на: комментарий от anonymous

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

anonymous
()
Ответ на: комментарий от anonymous

translateWithFileLookup :: FilePath -> IO (String -> Maybe String)

А вот это - хороший ответ. Спасибо, именно это было нужно.

chuzhoi
() автор топика
Ответ на: комментарий от qnikst

изначальная постановка задачи изменяет интерфейс.

В контексте хаскеля да. В нечистых языках нет.

A1
()
Ответ на: комментарий от monk

а ты от неё хочешь возврата действия, меняющего мир.

Где я этого хочу?

A1
()
Ответ на: комментарий от anonymous

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

В GNU-диалекте C и C++ можно навесить на любую функцию соответствующий __attribute__

annulen ★★★★★
()
Ответ на: комментарий от monk

а ты от неё хочешь возврата действия, меняющего мир.

Чтение файла, строго говоря, не меняет мир. Это мир меняет результат функции, делая ее нечистой. Но это такое...

chuzhoi
() автор топика
Ответ на: комментарий от annulen

В GNU-диалекте C и C++ можно навесить на любую функцию соответствующий __attribute__

Угу. «Мамой клянусь, это чистая функция». Контроля-то нет.

monk ★★★★★
()
Ответ на: комментарий от chuzhoi

Чтение файла, строго говоря, не меняет мир.

Меняет. access timestamp.

monk ★★★★★
()
Ответ на: комментарий от chuzhoi

не меняет мир

меняет мир

Тут вы чо,все миры уже поменяли? А то чип и дейл с брюсом виллисом уже спешат на помощь.

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

anonymous
()
Ответ на: комментарий от anonymous

анонiмус, создай уже новый логин для зобана.

anonymous
()
Ответ на: комментарий от chuzhoi

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

qnikst ★★★★★
()
Ответ на: комментарий от annulen

Я подозреваю разговор про __pure__ и ещё какой-то близкий? я честно не помню ограничений, но разговор не только про чистые/нечистые функции, а так же и про описание других эффектов, в т.ч. разрешение/запрещение/определённых действий и добавлении соотв. тега комбинации операций, статические регионы, вот это все. В целом я верю, что с достаточным уровнем страдания это должно выражаться на C++, но почему-то думаю, что проще сразу взять язык это умеющий, если оно нужно. Та же ситуация с dependent type в Haskell при желании что-то выстрадать можно, но проще взять язык получше для этого предназначенный.

qnikst ★★★★★
()

по всем функциональным канонам стараясь максимально выносить код в чистые функции

Отказаться от этого бреда и не делать из ввода данных глобальную проблему.

templarrr ★★★★★
()
Ответ на: комментарий от qnikst

Ну в сях, можно, как тут предлагали, понавешать __pure__ тоже неожиданно поменяется.

Ты не с того краю смотришь на проблему. Моими глазами: в хаскеле нужно наперед думать о возможности, что какой-то интерфейс вдруг станет нечистым, чтоб потом не было больного рефакторинга.

Посему интересны энтерпрайзные практики и кулстори на больших кодовых базах.

A1
()
Ответ на: комментарий от A1

Ты не с того краю смотришь на проблему. Моими глазами: в хаскеле нужно наперед думать о возможности, что какой-то интерфейс вдруг станет нечистым, чтоб потом не было больного рефакторинга.

ну.. я смотрю с того краю, с которого мне ближе. И если честно, я не вспомню случая (наверяка был, но не вспоминается), когда мне нужно было переделывать чистый код (даже не интерфейсы) на нечистые, тем более я не проблемой. Тем более мне сложно представить, где эта проблема не будет решена просто.

Посему интересны энтерпрайзные практики и кулстори на больших кодовых базах.

вон service pattern можешь посмотреть, которым например better пользовались, или то, что Miguel предложил, не знаешь, что делать - запили свою монадку. Отсылки к другим проектами приводить не буду, а то начнутся бесполезные споры про энтерпрайзомсть и что считать большой кодовой базой, что нет.

qnikst ★★★★★
()
Ответ на: комментарий от qnikst

Ну, вообще-то, я предложил запилить тайпкласс (подкласс Monad) и реализовать его для Reader и IO, а не делать свою монаду.

Miguel ★★★★★
()

Вижу два самых очевидных варианта:

1) передавать данные как аргумент функции явно;

2) передавать данные неявно, завернув вычисление в свою монаду - для начала можно отталкиваться от Reader или State.

Крайне не советую использовать unsafePerformIO. Это не только некрасиво, но и это может привести к странным особенностям поведения, поскольку по-умолчанию предполагается, что код является ссылочно-прозрачным, а потому unsafePerformIO может скомпилироваться совсем не так, как вы рассчитываете.

dave ★★★★★
()
Ответ на: комментарий от dave

unsafePerformIO подразумевает что программист добавляет NOINLINE, собственно в этом случае неожиданности могут быть только от полиморфизма (можно случайно unsafeCoerce изобрести, или просто отсутствие шаринга)

qnikst ★★★★★
()
Ответ на: комментарий от qnikst

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

chuzhoi
() автор топика

Хочу еще добавить, что те, кто пишет учебники для Haskell, на мой взгляд порою сильно перегибают палку с монадой IO. Это - одна из монад, и она вполне цельно вписываются в экосистему языка Haskell. Без unsafePerformIO вычисления IO являются ссылочно-прозрачными, а потому это равноправный участник в системе типов языка Haskell. В общем, IO - это не так и плохо, только злоупотреблять им не надо.

Если затрагивать некоторые темы, которых коснулись здесь в топике, то по своему опыту могу сказать так. Когда первую версию одного своего продукта писал на F#, то все время искал пути оптимизации. Как потом оказалось, неправильные пути. Когда переписал все на Haskell, окончательный дизайн сильно улучшился. Haskell помог увидеть главное, помог правильно спроектировать код, правильно подобрать модель прикладной области. Опыт использования Haskell был крайне положительным.

dave ★★★★★
()
Ответ на: комментарий от chuzhoi
class Monad m => MonadData m where getData :: m String
instance MonadData IO where
  getData =
    do putStr "Enter data:"
       readLine
instance Monad m => MonadData (ReaderT String m) where getData = ask

myCoolFunction :: MonadData m => SomeInput -> m Whatever
myCoolFunction input =
  do userData <- getData
     return (someCalculation userData input)
Miguel ★★★★★
()
Последнее исправление: Miguel (всего исправлений: 1)
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.