Ввод-вывод и чистые функции
От: AlexRK  
Дата: 23.01.17 19:44
Оценка:
Приветствую, уважаемые форумчане.

Есть два подхода к реализации ИО: как в большинстве мейнстримовых языков (в любой функции может быть чтение файла), или как в меньшинстве функциональных — сигнатура грязных функций "заражена" чем-то, например уникальным типом "мир" или чем-то в этом роде.

Вот возник дурацкий вопрос. Засорение стека вызовов грязных функций специальным параметром — хорошая штука или не очень? Если программа делает сплошной ввод-вывод, то это наверное не очень круто, а если ввода-вывода мало — то наоборот. Вроде как в основном в программах ввода-вывода много. Но мне все равно почему-то не нравится вариант, когда в функции вычисления суммы двух чисел может стоять форматирование жесткого диска, как-то неправильно это...

Вы за какой вариант, с явно видимым в сигнатуре функции вводом-выводом, а-ля Хаскель, или с неявным, а-ля Ц++? Почему?
Re: Ввод-вывод и чистые функции
От: deniok Россия  
Дата: 23.01.17 20:14
Оценка: 11 (1) -1
Здравствуйте, AlexRK, Вы писали:

ARK>Вы за какой вариант, с явно видимым в сигнатуре функции вводом-выводом, а-ля Хаскель, или с неявным, а-ля Ц++? Почему?


В Haskell:
(1) нет стека вызовов;
(2) нет типов в рантайме.
Так что засорять нечего и нечем.
Re[2]: Ввод-вывод и чистые функции
От: AlexRK  
Дата: 23.01.17 21:21
Оценка:
Здравствуйте, deniok, Вы писали:

ARK>>Вы за какой вариант, с явно видимым в сигнатуре функции вводом-выводом, а-ля Хаскель, или с неявным, а-ля Ц++? Почему?


D>В Haskell:

D>(1) нет стека вызовов;
D>(2) нет типов в рантайме.

Хорошо, пусть будет Clean, вопрос совершенно не в этом.

D>Так что засорять нечего и нечем.


Ну да, конечно же нечего и нечем.

https://www.haskell.org/tutorial/io.html

getChar :: IO Char


The return function admits an ordinary value such as a boolean to the realm of I/O actions. What about the other direction? Can we invoke some I/O actions within an ordinary expression? For example, how can we say x + print y in an expression so that y is printed out as the expression evaluates? The answer is that we can't! It is not possible to sneak into the imperative universe while in the midst of purely functional code. Any value `infected' by the imperative world must be tagged as such. A function such as

f :: Int -> Int -> Int

absolutely cannot do any I/O since IO does not appear in the returned type.

Re: Ввод-вывод и чистые функции
От: vsb Казахстан  
Дата: 23.01.17 21:26
Оценка:
Здравствуйте, AlexRK, Вы писали:

ARK>Вы за какой вариант, с явно видимым в сигнатуре функции вводом-выводом, а-ля Хаскель, или с неявным, а-ля Ц++? Почему?


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

Тут подумалось. В плохом Java-коде везде, где есть ввод-вывод, в сигнатурах пестрит throws IOException и он заставляет прокидывать этот throws дальше (ну или ловить, но это не интересный вариант). Может это на самом деле хороший код, который пишут хаскеллисты?
Отредактировано 23.01.2017 21:26 vsb . Предыдущая версия .
Re[2]: Ввод-вывод и чистые функции
От: AlexRK  
Дата: 23.01.17 21:32
Оценка:
Здравствуйте, vsb, Вы писали:

ARK>>Вы за какой вариант, с явно видимым в сигнатуре функции вводом-выводом, а-ля Хаскель, или с неявным, а-ля Ц++? Почему?


vsb>Я за явный, причины очевидны — о чистых функциях легко рассуждать, поэтому их должно быть как можно больше.


Я вот тоже всегда так думал, а сейчас что-то засомневался. Очень уж много вокруг ввода-вывода. Возможно, оптимальнее вариант языка D — по умолчанию функции грязные, но можно указать модификатор Pure? А даст ли это что-то существенное?

vsb>Теоретически (если всё иммутабельное) они также включают разные интересные варианты вроде автоматического распараллеливания, кеширования (мемоизации), но даже сами по себе они ценны.


По идее, такие вещи можно отслеживать компилятором и без сигнатур (по-моему, в C# подобный анализ имеется). Сигнатуры функций — они больше для программиста, ИМХО.

vsb>Тут подумалось. В плохом Java-коде везде, где есть ввод-вывод, в сигнатурах пестрит throws IOException и он заставляет прокидывать этот throws дальше (ну или ловить, но это не интересный вариант). Может это на самом деле хороший код, который пишут хаскеллисты?


Хз, может в хаскелле это и считается хорошим кодом, но в жабе это выглядит как говно.
Re: Ввод-вывод и чистые функции
От: WolfHound  
Дата: 24.01.17 00:25
Оценка: 1 (1)
Здравствуйте, AlexRK, Вы писали:

ARK>Вот возник дурацкий вопрос. Засорение стека вызовов грязных функций специальным параметром — хорошая штука или не очень?

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

ARK>Если программа делает сплошной ввод-вывод, то это наверное не очень круто, а если ввода-вывода мало — то наоборот. Вроде как в основном в программах ввода-вывода много.

В программах, которые ты пишешь.
А в программах, которые пишу я ввод/вывод занимает ничтожную часть кода.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[2]: Ввод-вывод и чистые функции
От: AlexRK  
Дата: 24.01.17 06:40
Оценка:
Здравствуйте, WolfHound, Вы писали:

ARK>>Вот возник дурацкий вопрос. Засорение стека вызовов грязных функций специальным параметром — хорошая штука или не очень?

WH>Эти типы не имеют осмысленных рантайм значений. Так что компилятор не выделяет под них память и не генерирует для них код.
WH>Те эти типы дают проверки времени компиляции, не влияя на исполнение.

Это я знаю. Вопрос не в этом. Вопрос таков — засорение самого кода программы ухудшает читабельность или все-таки что-то дает?

ARK>>Если программа делает сплошной ввод-вывод, то это наверное не очень круто, а если ввода-вывода мало — то наоборот. Вроде как в основном в программах ввода-вывода много.

WH>В программах, которые ты пишешь.
WH>А в программах, которые пишу я ввод/вывод занимает ничтожную часть кода.

Вот меня и интересует, а каких программ больше — каких пишу я или каких пишете вы. По-моему, первое.
Re[2]: Ввод-вывод и чистые функции
От: Слава  
Дата: 24.01.17 08:34
Оценка:
Здравствуйте, WolfHound, Вы писали:

ARK>>Если программа делает сплошной ввод-вывод, то это наверное не очень круто, а если ввода-вывода мало — то наоборот. Вроде как в основном в программах ввода-вывода много.

WH>В программах, которые ты пишешь.
WH>А в программах, которые пишу я ввод/вывод занимает ничтожную часть кода.

Тогда можно задать вопрос, а существуют ли хорошие и современные языки для программ, занимающихся в основном вводом-выводом.

INB4: си!
Re[3]: Ввод-вывод и чистые функции
От: deniok Россия  
Дата: 24.01.17 09:00
Оценка:
Здравствуйте, AlexRK, Вы писали:

D>>Так что засорять нечего и нечем.


ARK>Ну да, конечно же нечего и нечем.


ARK>https://www.haskell.org/tutorial/io.html


ARK>

getChar :: IO Char


ARK>

The return function admits an ordinary value such as a boolean to the realm of I/O actions. What about the other direction? Can we invoke some I/O actions within an ordinary expression? For example, how can we say x + print y in an expression so that y is printed out as the expression evaluates? The answer is that we can't! It is not possible to sneak into the imperative universe while in the midst of purely functional code. Any value `infected' by the imperative world must be tagged as such. A function such as

ARK>f :: Int -> Int -> Int

ARK>absolutely cannot do any I/O since IO does not appear in the returned type.


Так это же не метка IO засоряет программу! Ее засоряет сам факт ввода-вывода. А монада IO как раз приводит хаос ввода-вывода к стандартному для Хаскеля монадическому интерфейсу работы с эффектами.

Если же просто использовать такую метку без механизма, дающего какой-то разумный набор гарантий, то толку от нее действительно мало.
Re[4]: Ввод-вывод и чистые функции
От: AlexRK  
Дата: 24.01.17 09:11
Оценка:
Здравствуйте, deniok, Вы писали:

D>Так это же не метка IO засоряет программу! Ее засоряет сам факт ввода-вывода. А монада IO как раз приводит хаос ввода-вывода к стандартному для Хаскеля монадическому интерфейсу работы с эффектами.


Ну хорошо, пусть изначальной причиной является сам факт ввода-вывода, но, тем не менее, "метка IO" код тоже засоряет, причем всю иерархию функций, снизу доверху.

D>Если же просто использовать такую метку без механизма, дающего какой-то разумный набор гарантий, то толку от нее действительно мало.


Разумеется, речь идет о предоставлении гарантий. Только вот существует куча языков, в которых и без этой метки всё работает.

В данной теме я как раз поднял вопрос, а как "правильно"-то? С меткой или без? Кто что думает по этому поводу? То, что в природе успешно существуют оба варианта, я в курсе.
Re: Ввод-вывод и чистые функции
От: novitk США  
Дата: 27.01.17 18:22
Оценка:
Здравствуйте, AlexRK, Вы писали:

ARK>Вы за какой вариант, с явно видимым в сигнатуре функции вводом-выводом, а-ля Хаскель, или с неявным, а-ля Ц++? Почему?


Проблема имхо не в сигнатуре функции, тут как раз все хорошо, а в самом коде где нужно явно делать лифтинг в монаду, через все эти liftM, foldM, etc. Это сильно засоряет код и мешает пониманию. Я не спец в системах типов, но возможно в будущем можно сделать что-то для автоматизации процесса. Например, если есть

foobar: IO a
pure: a -> b

можно будет просто написать "pure.foobar", как в нечистых языках или близко к тому, и компилятор сам подставит нужный lift.
Re[2]: Ввод-вывод и чистые функции
От: deniok Россия  
Дата: 27.01.17 18:31
Оценка:
Здравствуйте, novitk, Вы писали:


N>foobar: IO a

N>pure: a -> b

N>можно будет просто написать "pure.foobar", как в нечистых языках или близко к тому, и компилятор сам подставит нужный lift.


pure <$> foobar
Re[3]: Ввод-вывод и чистые функции
От: novitk США  
Дата: 27.01.17 18:42
Оценка:
Здравствуйте, deniok, Вы писали:

D>
D>pure <$> foobar
D>


Я как бы в курсе, но хотелось бы избавиться, то есть чтобы компилятор это сам выводил.
Re[4]: Ввод-вывод и чистые функции
От: deniok Россия  
Дата: 27.01.17 19:07
Оценка:
Здравствуйте, novitk, Вы писали:

N>Здравствуйте, deniok, Вы писали:


D>>
D>>pure <$> foobar
D>>


N>Я как бы в курсе, но хотелось бы избавиться, то есть чтобы компилятор это сам выводил.


Это плохо совместимо с общим механизмом вывода типов в Хаскеле. Что он должен вывести для const foobar, если не сможет различать вызовы c лифтингом и без
> {foobar::IO a; foobar = undefined}
> :t const foobar
const foobar :: b -> IO a
> :t const <$> foobar
const <$> foobar :: IO (b -> a)
Re[5]: Ввод-вывод и чистые функции
От: novitk США  
Дата: 27.01.17 19:40
Оценка:
Здравствуйте, deniok, Вы писали:

D>Это плохо совместимо с общим механизмом вывода типов в Хаскеле. Что он должен вывести для const foobar, если не сможет различать вызовы c лифтингом и без


Согласен, на существующей системе оно неоднозначно, но помечтать то не возбраняется. То есть я, как стратег, задачу ставлю, а вы тактики ее решайте
Если серьезно, то у меня нет идей и подготовки, что можно с этим сделать. Сам по себе <$> может и ок, но вот весь зоопарк в Functor/Applicative/Monad сильно интрузивен.
Re[2]: Ввод-вывод и чистые функции
От: AlexRK  
Дата: 27.01.17 20:07
Оценка:
Здравствуйте, novitk, Вы писали:

ARK>>Вы за какой вариант, с явно видимым в сигнатуре функции вводом-выводом, а-ля Хаскель, или с неявным, а-ля Ц++? Почему?


N>Проблема имхо не в сигнатуре функции, тут как раз все хорошо, а в самом коде где нужно явно делать лифтинг в монаду, через все эти liftM, foldM, etc.


Ну, в языке D есть pure-функции, но при этом никаких лифтингов ни в какие монады там делать не надо.
Re[2]: Ввод-вывод и чистые функции
От: WolfHound  
Дата: 28.01.17 04:15
Оценка:
Здравствуйте, novitk, Вы писали:

N>Проблема имхо не в сигнатуре функции, тут как раз все хорошо, а в самом коде где нужно явно делать лифтинг в монаду, через все эти liftM, foldM, etc. Это сильно засоряет код и мешает пониманию. Я не спец в системах типов, но возможно в будущем можно сделать что-то для автоматизации процесса. Например, если есть

Такое?
https://koka-lang.github.io/koka/doc/kokaspec.html#sec-polymorphic-effects
Эффекты это считай обобщение монад.
Разница в том, что монада одна, а эффектов может быть много. Или вообще не быть.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[3]: Ввод-вывод и чистые функции
От: WolfHound  
Дата: 28.01.17 04:22
Оценка: +3
Здравствуйте, AlexRK, Вы писали:

ARK>Это я знаю. Вопрос не в этом. Вопрос таков — засорение самого кода программы ухудшает читабельность или все-таки что-то дает?

1)Ловит ошибки.
2)Улучшает читаемость кода.
3)Развязывает руки оптимизатору.

ARK>Вот меня и интересует, а каких программ больше — каких пишу я или каких пишете вы. По-моему, первое.

Есть конечно программы, которые сильно тяготеют в одну или другую сторону. Но во многих случаях зависит от того как у разработчиков программы мозги устроены.
Я в любом случае стараюсь минимизировать и изолировать ИО. Так программу легче понимать.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[3]: Ввод-вывод и чистые функции
От: Nick Linker Россия lj://_lcr_
Дата: 28.01.17 09:29
Оценка:
AlexRK,

ARK>>>Вы за какой вариант, с явно видимым в сигнатуре функции вводом-выводом, а-ля Хаскель, или с неявным, а-ля Ц++? Почему?


N>>Проблема имхо не в сигнатуре функции, тут как раз все хорошо, а в самом коде где нужно явно делать лифтинг в монаду, через все эти liftM, foldM, etc.


ARK>Ну, в языке D есть pure-функции, но при этом никаких лифтингов ни в какие монады там делать не надо.


Но тогда скажем функция `map` в D чистая или нет? Как D мне запретит туда передать лямбду `println(_)`? А если запрещает, как мне тогда всё же передать `prinln()`?

В хаскеле связь между монадами и грязными функциями напрямую отсутствует, непосредственно io-эффекты несёт с собой только монада IO, но при этом сам хаскель про это не знает.
Вот, например, это чистая функция, хотя я могу определить её в монаде Maybe:
fold2 :: Maybe a -> Maybe b -> (a -> b -> c) -> Maybe c
fold2 mba mbb f = do
  a <- mba
  b <- mbb
  return $ f a b

--test it
λ> fold2 (Just 10) (Just 20) (+)
Just 30


Или, например, есть функция `mapM :: (Monad m, Traversable t) => (a -> m b) -> t a -> m (t b)`, которая внезапно одновременно и чистая, и грязная:
λ> mapM print [1,2,3]
1
2
3
[(),(),()]

λ> mapM Just [1,2,3]
Just [1,2,3]


А вот ещё пример чистой (!) функции, которая читает из и пишет в KV-сторадж и даже делает это в транзакции:
type Ops = Free OpsF

foo :: Ops (Map Key Val)
foo = do
  let k1 = "foo1"
  let k2 = "foo2"
  put k1 "value1"
  put k2 "value2"
  transact $ do
    (vt1, vt2) <- transact $ do
      v1 <- get k1
      v2 <- get k2
      return (v1, v2)
    transact $ do
      put k1 vt2
      put k2 vt1
  -- get one by one, not in transaction
  vn1 <- get k1
  vn2 <- get k2
  return $
    insert k1 vn1 $
    insert k2 vn2 empty


Парадокс?
quicksort =: (($:@(<#[),(=#[),$:@(>#[)) ({~ ?@#)) ^: (1<#)
Re[4]: Ввод-вывод и чистые функции
От: AlexRK  
Дата: 28.01.17 10:45
Оценка:
Здравствуйте, Nick Linker, Вы писали:

NL>Но тогда скажем функция `map` в D чистая или нет?


Понятия не имею, возможно есть два варианта этой функции.

NL>Как D мне запретит туда передать лямбду `println(_)`?


Не скомпилирует код.

NL>А если запрещает, как мне тогда всё же передать `prinln()`?


Никак. Это все равно что задать вопрос "как мне передать в функцию строку, в то время как функция требует число".

NL>Парадокс?


Просто другой подход, а вот лучше он или хуже — вопрос остается открытым.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.