declarative
Dec. 25th, 2012 01:34 pmПериодически встречаю словосочетание "декларативное программирование", которое противопоставляют императивному, сопровождая формулой "описывается, что должно быть получено, а не как оно должно быть получено". Известный пример декларативного языка - SQL. Еще к декларативщине частенько относят хаскель и другие ФЯ, но вот с ними проблема: сколько кода я на них видел, (практически) везде было явное описание процесса вычисления, т.е. код был все же императивным по сути (создать список такой-то, свернуть его так-то, построить дерево такое-то и т.д. - это все про "как"). Вопрос к залу: покажите максимально декларативное решение (на любом языке, в том числе еще не существующем и чисто гипотетическом) следующей задачи.
Есть последовательность байтов (фиксированный массив/список или потенциально бесконечный поток - на ваш выбор), нужно их сжать RLE методом по мотивам формата PCX: в выходном потоке байт Х <= 192 обозначает сам себя, а байт Х > 192 означает Х-192 повторений следующего за ним байта. Т.е. последовательность
1, 1, 2, 3, 2, 2, 2, 200, 0, 200, 200, 200, 200
должна превращаться в
194, 1, 2, 3, 195, 2, 193, 200, 0, 196, 200
Есть последовательность байтов (фиксированный массив/список или потенциально бесконечный поток - на ваш выбор), нужно их сжать RLE методом по мотивам формата PCX: в выходном потоке байт Х <= 192 обозначает сам себя, а байт Х > 192 означает Х-192 повторений следующего за ним байта. Т.е. последовательность
1, 1, 2, 3, 2, 2, 2, 200, 0, 200, 200, 200, 200
должна превращаться в
194, 1, 2, 3, 195, 2, 193, 200, 0, 196, 200
no subject
Date: 2012-12-25 07:20 am (UTC)======
Есть последовательность байтов (фиксированный массив/список или потенциально бесконечный поток - на ваш выбор).
В выходном потоке байт Х <= 192 обозначает сам себя, а байт Х > 192 означает Х-192 повторений следующего за ним байта.
Последовательность
1, 1, 2, 3, 2, 2, 2, 200, 0, 200, 200, 200, 200
должна превращаться в
194, 1, 2, 3, 195, 2, 193, 200, 0, 196, 200.
======
В-общем, спецификации декларативны. (Может, ещё что-то декларативно, я не знаю).
Язык тестов, например, очень близок к языку спецификаций, т.е. декларативен.
Идея декларативности в том, что для большинства задач "условие" проще "решения".
no subject
Date: 2012-12-25 07:30 am (UTC)И как бы выглядела достаточная спецификация на формализованном языке? Процитированная как минимум неполна - не описана связь входа и выхода.
(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2012-12-25 07:30 am (UTC)deftask Есть последовательность байтов, нужно их сжать: в выходном потоке байт Х <= 192 обозначает сам себя, а байт Х > 192 означает Х-192 повторений следующего за ним байта.
deftest последовательность
1, 1, 2, 3, 2, 2, 2, 200, 0, 200, 200, 200, 200
должна превращаться в
194, 1, 2, 3, 195, 2, 193, 200, 0, 196, 200
no subject
Date: 2012-12-25 07:47 am (UTC)rle [1, 1, 2, 3, 2, 2, 2, 200, 0, 200, 200, 200, 200]
Декларативное просто говорит сделай мне.
no subject
Date: 2012-12-25 07:58 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2012-12-25 08:03 am (UTC)no subject
Date: 2012-12-25 07:49 am (UTC)> построить дерево такое-то и т.д. - это все про "как"
Это не обязательно императивно, и не обязательно "как". "Императивность" означает (имхо) всего лишь необходимость работать в темпоральной логике при доказательстве свойств программ. Императивный код вполне может быть декларативным - например, многие спецификации пишутся в псевдокоде.
"Как" (имхо) всего лишь означает, что мы жестко прибиваем гвоздями способ решения.
Гипотетическая запись fold foo . map bar . unfold baz может означать как конкретный способ решения, так и множество возможных решений, в зависимости от спецификации языка, т.е. быть как декларативной, так и нет.
Более того, чем более зрелый у языка компилятор - тем более декларативно можно на нём писать. Подсказки компилятору - антидекларативны.
no subject
Date: 2012-12-25 08:01 am (UTC)no subject
Date: 2012-12-25 07:52 am (UTC)no subject
Date: 2012-12-25 07:57 am (UTC)(no subject)
From:(no subject)
From:no subject
Date: 2012-12-25 08:36 am (UTC)isequal :: int list -> bool foo isequal ([x..] as l) = case l.count of | 1 -> if x <= 192 then [x] else [193,x] | count -> [192 + count,x] foo :: [int] -> [int] result = [1,1,2,3] |> patternMap foofoo по умолчанию жадный
На рефале вроде можно что то похожее сделать, но давно это было, не уверен.
no subject
Date: 2012-12-25 10:28 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2012-12-25 10:26 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2012-12-25 09:27 am (UTC)1, 1, 2, 3, 195, 2, 193, 200, 0, 196, 200?
no subject
Date: 2012-12-25 09:30 am (UTC)193, 1, 193, 1, 193, 2, 193, 3, 193, 2, 193, 2, 193, 2, 193, 200, 193, 0, 193, 200, 193, 200, 193, 200, 193, 200?
(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2012-12-25 09:29 am (UTC)1) для Лист1 и Лист2 проверять, является ли Лист2 ужатнадо явым Лист1
2) для Лист2, который ужат, востанавливать Лист1, который его оригинал.
К сожалению из обычный лист Лист1 он не ужимает, потому что с его точки зрения, Лист1 является валидной сжатием самого себя.
А заставить его ужимать уже сложнее, надо явно считать одинаковые элементы, изымать их, специальным образом обрабатывать элементы больше 192, короче не очень декларативно пока получается
no subject
Date: 2012-12-25 10:11 am (UTC)Не должон - одиночные значения больше 192 должны раздуваться при таком кодировании.
(no subject)
From:no subject
Date: 2012-12-25 09:32 am (UTC)Первое, с чем мы столкнемся при написании rle, это нечеткость постановки задачи. Исходно сказано было: сделать по формату. А подразумевается: сделать оптимально.
Неоптимальное решение - тупо снабжать каждый символ префиксом 193, и оно очень легко декларируется.
Эрго, мы или не вводим в декларацию проверку оптимальности, и на выходе получаем что попало в формате рле, или вводим и получаем тотальный перебор решений. (пролог это сумеет).
Значит, придется тащить эвристики. Например, рассмотреть несколько видов контекста (головы списка), которые очевидно (программисту) надо кодирлвать именно таким способом, а не другим. А это уже императив...
Да и на таком декларативном языке, как пролог, можно схитрить и написать программу, которая выдаст лучшее решение сразу, но если кому хочется доказать, что оно лучшее, то переберет и все остальные пути. Выглядеть будет как декларативное, но мы-то знаем :-)
no subject
Date: 2012-12-25 10:14 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2012-12-25 09:45 am (UTC)unrle [] = [] unrle (x:xs) | x <= 192 = x : unrle xs | otherwise = replicate (x - 192) (head xs) ++ unrle (tail xs)Пример же даёт возможность самому догадаться об обратной операции, но спецификации нет. Я попытался догадаться, и получил это:
rle = concatMap rle' . group where rle' [x] | x <= 192 = [x] | otherwise = [193, x] rle' xs = [192 + length xs, head xs]Единую спецификацию я пока даже на словах не придумал.
no subject
Date: 2012-12-25 10:04 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2012-12-25 10:01 am (UTC)Видимо, это некоторая логика. Причём, такая, чтобы описание в ней было сильно более краткое, по сравнению с операционной семантикой. И возможно, что ещё бы и сильно более другое описание.
Например, интуиционистская логика, видимо, не подходит. Или критерии другие. А что подходит?
Видимо, формулировки теорем могут восприниматься, как декларативное. А их доказательство, как решение.
no subject
Date: 2012-12-25 12:32 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2012-12-25 11:14 am (UTC)unrle = stream head x where x ≤ 192 → [x] elems [x, y] where x > 192 → replicate (x - 192) y rle = compress unrleno subject
Date: 2012-12-25 12:34 pm (UTC)Второе и третье (обсуждаемое там, естественно, первое) определения декларативности из википедии, например, не такие плохие:
2) Any programming language that lacks side effects (or more specifically, is referentially transparent)
3) A language with a clear correspondence to mathematical logic.
Второе, впрочем, испорчено "сайд-эффектами", так что лучше бы они ограничились тем, что после "or more specifically", да и третье не без недостатков.
no subject
Date: 2012-12-25 03:30 pm (UTC)no subject
Date: 2012-12-25 04:43 pm (UTC)Если язык позволяет записать проблему так, что пользуясь формальными преобразованиями можно прийти от описания к любому алгоритму решения - язык декларативен.
no subject
Date: 2012-12-25 07:27 pm (UTC)===
упрощение([X,X|T],[194,X|T]).
упрощение([A,X,X|T],[B,X|T]):-A>192,B is A+1.
упростить([],[]).
упростить(L,R):-упрощение(L,M),упростить(M,R).
упростить([H|T],[H|R]):-упростить(T,R).
===
Тут, правда, простоты ради не учтено возможное наличие чисел больше 192 в исходных данных. Это легко лечится, но "внешним" по отношению к содержательной части образом.
no subject
Date: 2012-12-26 04:54 am (UTC)no subject
Date: 2012-12-26 04:50 am (UTC)Программа при этом, конечно же, одна и та же.
То есть вы можете рассуждать о map f как об утверждении "аргумент - некоторый список, результат - список того же размера, элементы которого являются результатом применения f к элементам аргумента, причем результат применения f к i-тому элементу аргумента имеет i-тую позицию". А можете как о последовательности шагов: "разделить входной список на голову и хвост, применить f к голове, map f к хвосту, вернуть список, полученный из новых головы и хвоста".
Аналогично в вашим rle:
decodeBytes bytes = concat $ map decodeByte bytes
where
decodeByte x =
| x <= 192 = [x]
| x > 192 = replicate (x-192) x
можно воспринимать этот код как описание свойств decode, а можно - как указание вычислителю на действия, которые он должен выполнить.
no subject
Date: 2012-12-26 05:33 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2012-12-26 07:51 am (UTC)Вот достаточным образом описать декларативно оптимизационные задачи (ну, чтобы сгененированный алгоритм работал не две вечности, а разумное время) - это уже ого-го.
no subject
Date: 2012-12-26 07:59 am (UTC)В самый раз задача. Сложная людей напугает, совсем тривиальная не запустит работу мысли. А эта вон спровоцировала множество интересных ответов.
no subject
Date: 2012-12-26 09:43 am (UTC)where
uncompress = fun(stream) {
x << stream, return x if x <= 192;
byte << stream, return (x-192).times(byte)
}
no subject
Date: 2012-12-26 10:00 am (UTC)(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2012-12-27 03:13 pm (UTC)http://okertanov.github.com/2012/04/14/declarative-factorial
no subject
Date: 2012-12-28 03:19 pm (UTC)no subject
Date: 2013-01-03 04:59 pm (UTC)У меня такая вот спецификация решения:
Аксиома. Список RLE байт включает либо значения X <= 192, либо счётчик+192, за которым следует значение X.
Отсюда спецификация cons для списка RLE: Чтобы включить X в список, нужно либо скопировать X, если X <= 192, либо записать счётчик 192+1, за которым поставить X.
Но в итоге нас интересует сжатие.
Гипотеза. Сжатие происходит, когда список RLE байт включает X только если за ним до сжатия следует байт Y != X, или цепочка байт Y == X со счётчиком, равным максимальному значению 255. В противном случае за X до сжатия следует цепочка байтов Y == X, у которых после сжатия счётчик, который можно увеличить, либо Y <= 192 в этой цепочке встречается лишь один раз.
rlehead (x:xs) = if x <= 192 then x else head xs rletail (x:xs) = if x <= 192 then xs else if x == 193 then tail xs else (x-1):xs rlecons x ys | null ys || head ys == 255 || rlehead ys /= x = if x <= 192 then (x : ys) -- спецификация cons говорит нам, как именно вставить X else (193:x:ys) | otherwise = let y = head ys in if y <= 192 then (194:x: tail ys) -- спецификация решения говорит нам, что когда rlehead ys == x, то либо y == счётчик, который можно увеличить, либо y == собственно байт, а счётчик у цепочки == 1. else (y+1): tail ys rle [] = [] rle (x:xs) = rlecons x $ rle xsno subject
Date: 2013-01-03 05:10 pm (UTC)(no subject)
From:(no subject)
From:(no subject)
From:no subject
Date: 2013-01-04 10:18 am (UTC)RleStream - это поток байт, где каждому байту соответствует счётчик. (очевидно, map (193,) bs)
Два RleStream эквивалентны, если из одного можно получить другой, сложив или разбив счётчики подряд идущих одинаковых байт. Это свойство транзитивно (т.е. два RleStream эквивалентны, если есть общий RleStream, которому равны оба). (очевидно, для построения эквивалентных списков нужна функция, смотрящая на два подряд идущих счётчика для одинаковых байт)
Rle сжатие - это построение RleStream с как можно меньшим количеством счётчиков.
Представление RleStream. Счётчик записывается в виде байта со значением счётчик+192, за которым следует байт, который в исходном потоке повтоялся указанное счётчиком число раз. Счётчик равный 1 можно опускать для значений байт X <= 192. По условию задачи счётчик должен помещаться в 1 байт, а потому не должен превышать значение 255-192. Это ограничение на степень сжатия, накладываемое представлением RleStream.
В таком виде похоже, что изначальное условие - это лишь Представление RleStream, а для решения нужны абстракции вроде рассуждений в комментах - какие решения удовлетворяют задаче. Из них вытекает определение RleStream, эквивалентность и способ сжатия.
Остаётся вопрос поиска минимального RleStream. Тривиально строится RleStream, который меньше, чем канонический. Он-то и минимальный, но из спецификации этого не следует.
rle = compress . map (193,) where -- конвертируем в канонический RleStream пар "счётчик-байт" compress [] = [] compress ((c,x):ys) {- для построения эквивалентного RleStream, нужно смотреть на два подряд идущих элемента, но последнего элемента может не быть, поэтому воспользуемся head и null -} | null ys || x /= snd $ head ys || c == 255 = if c == 193 && x <= 192 then x : compress ys {- обнаружена (максимально длинная) цепочка повторов -} else c: x: compress ys | otherwise = compress $ (c-192 + (fst $ head ys), x):(tail ys) {- построение эквивалентного RleStream путём сложения значений счётчиков по спецификации -}Очевидно, compress может быть обобщён до сжатия произвольных RleStream (за счёт более общего способа построения для произвольных c >= 255).