Обновить
59

Пользователь

22
Подписчики
Отправить сообщение
> Да и как удобный синтаксис может мне вдруг навредить завтра — тоже не очень ясно.
В общих чертах-то ясно. Плохой код должен выглядеть плохо. То есть идея в том, что глядя на вставку в середину массива, человек должен заподозрить неладное и убедиться, что оно к месту. Скажу за хаскель. Если бы писать там с мутабельными переменными было так же удобно, как без них, хаскель был бы другим. Вопрос только в том, что именно счесть плохим кодом, и вот тут да, мнения разнятся. Время покажет.
Ну, я не согласен, что тут дело в сокрытии переменных. Если убрать внешний int x, ничего не поменяется.
А можно пример, где в C++ undefined behavior от сокрытия переменных? Что-то не могу придумать
А что выглядит странно в его полной речи по второй ссылке?
Почему? Если программисты напишут программы, заменяющие программистов, то программы, заменяющие других людей будут писать уже эти программы, а не сами программисты.
Ну нет, random всё-таки качественнее рандомит, так что random — тоже улучшение
Там либо пусто, либо голова + хвост, так что фраза «представляет собой всегда голову» неточна.
Нет, уж скорее

class List
class Null: List
class Node: List

Ибо ваше на хаскель переводится так:

data List a = List (Maybe a) (Maybe (List a))
Что совсем не то же самое
Кто ж виноват, что в Haskell нет `foreach` :D

Отчего ж, есть и mapM и forM, но можно и так тоже.
Есть даже такое :)
ghci> mapMOf_ (each . _Right . each . _head) putChar [Right ["foo", "baz", "baz"], Left 10, Right ["x", "y", "zoo"]]
fbbxyz

Взять каждый (each) элемент, затем пойти внутрь конструктора Right (_Right) (при этом откинув те, что Left), затем взять каждый элемент (each), и затем первую букву (_head)
И для них выполнить putChar
Ну да ладно, это так, к слову.

Хм, вы имеете ввиду оптимизацию на уровне компилятора? Тогда я не очень понимаю её суть — насколько я понимаю, композитная функция `(f. g)` всё равно транслируется в машинный код как последовательное применение двух функций к каждому элементу. Или здесь можно ещё что-то соптимизировать?

Как я понимаю, суть в том, что вместо того, чтобы применить g, затем сделать, грубо говоря, yield, а затем ещё и f, можно сразу f.g применять. Насчёт чего-то ещё, не знаю, это уже более низкоуровнево. Но это был просто пример, суть которого в том, что даже в своей библиотеке можно задавать такие правила, заменяющие какие-то случаи на более эффективные реализации. Правда, тут всё на совести того, кто такие правила задаёт, хотя теоретически можно было бы ещё и требовать эквивалентности такой замены.

Вот, например, из документации:
So, for example, the following should generate no intermediate lists:
array (1,10) [(i,i*i) | i <- map (+ 1) [0..9]]



К сожалению, глубоко тему не рыл, но вот, например: www.randomhacks.net/2007/02/10/map-fusion-and-haskell-performance
Стоит также отметить, что если в случае итераторов всё понятно, они специально так заделаны, то с двумя подряд foreach так уже сделать нельзя:
for x in xs:
  f(x)
for x in xs:
  g(x)

# Совсем не то же самое, что:
# for x in xs:
#   f(x)
#   g(x)

А если мы хотим, чтоб можно было — придется переезжать на итераторы, тогда как в хаскеле что foreach, что «итераторы» делаются одной конструкцией.

Написал несколько сумбурно, но, надеюсь, донес мысль :)
Нуу, тут хоть и две строчки, но специальных фич Haskell-а целая куча — и ленивые вычисления, и рекурсия через фиксированную точку, и развитая система типов.

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

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

Согласен, приятно было пообщаться.
У вас ужасный, ужасный, ужасный стиль программирования на Python

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

Как я уже говорил, если кто-то применяет функции с побочными эффектами в функциях типа `map`, то ему нужно отрывать руки с корнем, вне зависимости от языка.

Ну вот print — вроде грязная функция? А В Хаскеле можно передавать :)
sequence_ $ map print [1..3] -- 1 2 3
sequence_ $ reverse $ map print [1..3] -- 3 2 1
Может хватит говорить загадками?

Куда уж конкретнее? Параметрический полиморфизм

map возвращает ленивый итератор, а не результат, так что не гарантирует когда функции будут вызваны и будут ли вызваны вообще.

Я специально написал «порядок». Если же map может применять функцию и в обратном порядке, то это уже не оптимизация.

d = {'x':0}
def foo(x):
	d['x'] = d['x'] + 1
	x = x + d['x']
	print(x)
	return x

def foofoo(x):
	foo(x)
	return foo(x)

def test1():
	d['x'] = 0
	return list(map(foo, [0,0,0]))
def test2():
	d['x'] = 0
	return list(map(foo, map(foo, [0,0,0])))
def test3():
	d['x'] = 0
	return list(map(foofoo, [0, 0, 0]))


Что выведет test1()? Что выведет test2()? А что выведет test3()?
Список чего? Или это абстрактный тип такой?

Полиморфный

Преобразовать-то можно и в императивном языке

Разумеется, нельзя, потому что в императивном языке эти две конструкции просто-напросто неэквивалентны.
f потенциально может выводить на экран, g — тоже, а порядок менять негоже.
Понятней не стало :-) Это бесконечный список из Null-ов или что?

Это просто список, т.е. либо пустой (Null), либо голова + хвост.

Значит его и фиг оптимизируешь толком

Ну как-то умудряются же, и чистота этому только способствует, потому что, например, map f . map g можно преобразовать в map (f . g) вне зависимости от того, что там за f и g.
Я не силён в Хаскеле, что тут происходит?

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

Почему?

Потому что в моём коде это не так.

Ну так и язык не функциональный, понятно, что сложные функциональные штуки типа каррирования и композиции функции в нём будут хотя бы чуть-чуть, но сложнее синтаксически :D

Чуть-чуть — это мягко сказано :)

Python по-умолчанию считает своих разработчиков достаточно вменяемыми

Да Python вообще динамический :) Так что Bondage & Discipline явно не в его стиле, конечно

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

Речь не о том, можно или нет. В любом Тьюринг-полном можно много чего. Вопрос в том, чего это стоит.

-- | Дерево
data TreeT a t = Empty | Node a [t]
type Tree a = Fix (TreeT a)

-- | Бесконечное дерево, в узле n, поддеревья: genTree (n + 1) и genTree (n + 2)
genTree n = Fix (Node n $ map genTree [n + 1, n + 2])

-- | Свернём в список, первый элемент - узел, а затем идут свернутые поддеревья, которые обходятся в обратном порядке (т.е. сначала сворачивается последнее поддерево, затем предпоследнее, и т.п.)
foo = cata f (genTree 0) where
  f Empty = []
  f (Node x xs) = x : concat (reverse xs) -- reverse - это вот обратный порядок уже обойденных поддеревьев, мы лишь склеиваем "готовые" списки

-- | Берём первые 10 элементов
test = take 10 foo -- [0,2,4,6,8,10,12,14,16,18]


Вот здесь дерево ленивое — две строки.
foo — обходит поддеревья в обратном порядке
test — берёт только первые 10 от «бесконечного» дерева
В foo можно было не брать библиотечную cata (катаморфизм), а обходить ручками, если хочется более полный контроль, хотя помимо cata там есть и другие вещи, к которым и так сводится множество обходов и обработок.
Спасибо.

> Да, забыл сказать, что map, filter и take возвращают те же ленивые итераторы, что и в хаскеле.
Вот только в хаскеле эти ленивые итераторы определяются так:
data List a = Null | Cons a (List a)

И так для любого своего типа данных.

> Да, g должна быть выводима во время компиляции
А так — нечестно :)

Я веду к тому, что необходимость «ленивых итераторов» очевидна, и везде так или иначе реализуется. Но в Хаскеле не нужно делать это как-то особенно, там оно делается по умолчанию. А чистота даёт больший просто для высокоуровневых оптимизаций.
Хотя, конечно, стоит отметить, что иногда излишняя ленивость становится источником проблем, когда неясно, где и в какой момент что-то вычисляется, а это может порой неслабо повлиять на поведение программы. Вот в этих случаях анализировать сложнее, но не так, чтобы очень.
Уж лучше идиоматично через list comprehensions, а то так и нечитаемее, и тормознее. А если идиоматично, то уже bar оттуда не вычленить так просто, композиционность страдает.

> Я ничего не знаю про Rewrite rules, но ни одна из перечисленных функций не обладает побочными эффектами.
Как же это не обладает, когда _может_ обладать (у меня там недаром f и g — произвольные функции), а это для оптимизатора весьма важно.

> По деревьям вы тоже итерируете последовательно?
Можно даже бесконечные деревья (как и списки) строить. Необязательно последовательно. Те же самые filter, map, что-либо ещё.

> Тогда добавьте интерфейс итератора к вашему объекту (переопределите некоторые специальные функции) и сможете итерировать по ним.
Итератор ходит вперёд. По дереву я могу своей функцией идти в том направлении, в котором мне надо. Это уже другой тип итератора совсем. Ходить вперёд — частый, хотя и распространённый, случай.

Понятно, что я не буду утверждать, что вот Haskell-way — самый лучший, я просто к тому, что дьявол в деталях. В придуманных примерах важнее одно, в реальных задачах — другое, причём в каждой что-то своё.
Я с D знаком весьма поверхностно, потому у меня есть ещё вопросы.
Если я верно помню, то! — это применение шаблона.
Значит ли, что g обязана быть известна в момент компиляции?
В коде, который я написал не вам, чуть ниже, но на основе моего:
foo = take n
bar = filter f
baz = map g
quux = foo . bar . baz

Проход по списку будет один. Верно ли будет это и в вашем примере, если аналогично дописать остальные функции, а потом их скомпоновать? Верно ли для обычного списка, или для только для итераторов?
> А вас какая часть этого выражения интересует? Функции работы со списками, каррирование или композиция функций?
Композиции и возможность записывать куски композиции отдельно, т.е. типа такого:
foo = take n
bar = filter f
baz = map g
quux = foo . bar . baz


> т.е. теми же самыми ленивыми списками
Да. При этом в чистом языке можно подряд идущие filter f . map g сворачивать в использование одной функции (Rewrite rules), потому что ни f, ни g не производят побочных эффектов.
А что если у нас не списки, а деревья? Как сделать всё то же, но для своего типа данных?

Информация

В рейтинге
Не участвует
Откуда
Россия
Дата рождения
Зарегистрирован
Активность