LINUX.ORG.RU

Можно ли без промежуточных переменных?

 ,


0

5

Если в обычном языке есть выражение типа f1() + f2() + f3(), то в Хаскелле, если эти функции имеют побочные эффекты, приходится писать

do
  tmp1 <- f1
  tmp2 <- f2
  tmp3 <- f3
  return tmp1+tmp2+tmp3

Можно ли как-нибудь написать то же самое без промежуточных переменных?

★★★★★

Если в обычном языке есть выражение типа f1() + f2() + f3(), то в Хаскелле, если эти функции имеют побочные эффекты, приходится писать...

Это в каком, обычном? В самом обычном языке тоже нельзя писать одним выражением.

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

let (++) = liftM2 (+) in f1 ++ f2 ++ f3

Или можно как-то ещё проще?

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

В любом не чистом, где есть арифметические выражения.

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

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

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

> pure (\x y z -> x + y + z) <*> [1, 2, 3] <*> [30, 40] <*> [100, 500, 200, 100]
[131,531,231,131,141,541,241,141,132,532,232,132,142,542,242,142,133,533,233,133,143,543,243,143]
nezamudich ★★
()
Последнее исправление: nezamudich (всего исправлений: 1)

В return tmp1+tmp2+tmp3, кстати, ошибка. Это парсится как ((return tmp1) + tmp2) + tmp3 так как применение функции имеет наивысший приоритет, а оператор (+) объявлен левоассоциативным. Подразумевалось явно return $ tmp1 + tmp2 + tmp3.

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

Подразумевалось явно return $ tmp1 + tmp2 + tmp3.

Это да. Кстати, а можно как-нибудь написать

f1 `liftM2 (+)` f2

чтобы оно работало? Или только через let?

monk ★★★★★
() автор топика
Ответ на: комментарий от quantum-troll

Хаскель явно не предназначен для программирования без переменных.

Ну почему же? sum3 <*> [1, 2, 3] <*> [30, 40] <*> [100, 500, 200, 100] на других языках, как правило, придётся делать через переменные цикла. А здесь красиво.

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

Не, оператором можно сделать только готовый баиндинг. В нём можно отсылаться в другие модуля, например: (^ 2) `Prelude.map` [1..10], но засовывать expression в оператор нельзя.

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

В общем случае pointfree ужасен. Зачастую выглядит, как издевательство над читающим. Кто же сразу догадается, что (. (+)) . (.) . (+) — это \a b c -> a + b + c?

nezamudich ★★
()
Последнее исправление: nezamudich (всего исправлений: 1)
Ответ на: комментарий от quantum-troll

конкатенативном языке

А бывают статически типизированные конкатенативные языки?

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

Кстати, разве нет стандартных функций типа

build2 f1 f2 x1 x2 x3 = x1 `f1` x2 `f2` x3
build3 f1 f2 f3 x1 x2 x3 x4 = x1 `f1` x2 `f2` x3 `f3` x4
...
?

И если нет, то почему? fold есть, а здесь та же идея, но на фиксированном количестве аргументов.

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

Встроенного такого нет, но можно определить свои операторы

(|-) = (,)
(-|) x (f, y) = f x y

прописать им подходящие приоритеты, после чего можно будет делать что-то вроде

f1 -| liftM2 (+) |- f2
Miguel ★★★★★
()
Ответ на: комментарий от nezamudich

Надо же меру знать, ну. Если есть желание поупражняться в комбинаторах, конечно, то на: (. (+)) . (.) . (+) :)

за такое испражнение полагается расстрел на месте

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

Да, в свое время таковые водились на fprog, но мода угасла, и fprog тоже

dave ★★★★★
()

liftA3 (\a b c -> a+b+c) f1 f2 G3

или с <$> и <*> тоже самое

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

Хаскель явно не предназначен для программирования без переменных.

То есть ты утверждаешь, что использование do-нотации не превращает код в лапшу?

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

В лапшу код превращает недостаточная проработанность архитектуры программы.

P.S. Или это была попытка развязать холивар за баинд против ду?

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

P.S. Или это была попытка развязать холивар за баинд против ду?

Я не против самой do-нотации, я против её чрезмерного применения.

Понятно, что есть сто способов записать любую цепочку вычислений. Скилл написания хороших программ на хацкеле, на мой взгляд, во многом состоит в умении выбрать среди этих способов наиболее понятный/читаемый. И do-нотация в этом плане норм, если блоки размером не больше 10-15 строк.

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

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

И всё-таки do-нотация гораздо чаще встречается во всяких учебниках и глупых вопросах на stackoverflow, чем, допустим в коде библиотек. Повод задуматься :)

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

do-нотация гораздо чаще встречается во всяких учебниках и глупых вопросах на stackoverflow, чем, допустим в коде библиотек

Просто библиотеки должны быть специфичны. Вот открыл parsec и vector.mutable — там вполне себе do-do-do.

Так-то да, зачем активно использовать монады, например, для реализации fingertree? Логично, что незачем.

nezamudich ★★
()

Вы должны указать порядок команд f1, f2, f3 (значений типа m a где Monad m или Applicative m). Конечно, можно написать функции, где порядок заложен (например, сначала левая команда-аргумент, потом правая). Но это смешивает две разные вещи: порядок переменных в алгебраическом выражении и порядок выполнения команд. То есть это плохой стиль. Если вы хотите писать в таком стиле, используйте япы типа ML или Scheme.

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

Вы должны указать порядок команд f1, f2, f3 ... Но это смешивает две разные вещи: порядок переменных в алгебраическом выражении и порядок выполнения команд. То есть это плохой стиль.

f1 -| liftM2 (+) |- f2 -| liftM2 (+) |- f3

Так делать плохо? А чем это принципиально отличается от g f1 >>= g2 f2 >> f3 ?

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

Разница в том случае, если операция, которая соединяет tmp1, tmp2, tmp3 не является ассоциативной и коммутативной. Допустим, у вас было выражение return (f tmp1 tmp2 tmp3). Вы поняли, что правильное выражение — return (f tmp3 tmp1 tmp2). Если вы используете do, вы переставляете аргументы f, и всё в порядке. Если вы используете liftM3 f, вы переставляете аргументы этого выражения, и вдруг меняется порядок выполнения команд, чего вы не хотели. В спешке легко об этом забыть.

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

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

Я слишком много писал на Scheme и слишком мало на Haskell, чтобы ожидать, что перестановка аргументов не должна менять порядок вызовов.

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

l = [1, 2, -3, 4]
main = do
    return $ map (\x -> if x < 0 then error "Bad" else x) l
    print l

Приходится явно делать Maybe и unless.

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

Конкретно это выражение я сам вывел. Иначе бы там не было ошибки :)

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

А почему переключились на Haskell, если не секрет?

Не то, чтобы совсем переключился. Racket (Scheme) и Haskell имеют чуть-чуть разную сферу применения. Haskell быстрее, типизация позволяет писать большие программы с меньшим числом тестов, комбинаторы позволяют эффективней использовать библиотеки. Но скрипты или функции с ограничением по данным, а не по типам проще писать на Racket. Фактически, для меня Haskell занимает нишу C++.

monk ★★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.