Хабр Курсы для всех
РЕКЛАМА
Практикум, Хекслет, SkyPro, авторские курсы — собрали всех и попросили скидки. Осталось выбрать!
К слову, самый первый пример возможен и без всяких javascript. Банальная функция на C++
#define var autoуменьшают количество способов написать ужас-ужас.
если есть вероятность того, что какая-нибудь неприятность может случиться, то она обязательно произойдёт
Если четыре причины возможных неприятностей заранее устранены, то всегда найдётся пятая.
обязательно произойдёт
Четыре известных могут увеличивать время разработки, только если разработчик упорно игнорирует эти проблемы.
а по опыту скажу, что это значит что серьёзно никто эти не занимался, и у каждого есть шанс стать первопроходцем.
Парадокс ФЯ конечно в том, что выучив этот подход, можно потом свободно применять его и в с++ и в с# и на java.
А вы на самом деле используете в работе F#?
In this post, I'll show you some of the issues that these design decisions cause, and suggest some ways to improve the language to avoid them.
Of course, there is a huge amount of advice out there on how to do just this: naming guidelines, formatting rules, design patterns, etc., etc.
But can your programming language by itself help your code to be more reasonable, more predictable? I think the answer is yes, but I'll let you judge for yourself.
TransactionManager.TransferMoney(account1, account2, sum);
А юнит-тесты так вообще упрощают этот самый reasoning.
Статический же анализатор надо еще запустить
class Book { String isbn; }
class IsbnType { String value; }
class Book { IsbnType isbn; }
class IsbnType extends String;
class Book { IsbnType isbn; }
В теории реляционных баз данных таблица представляет собой изначально неупорядоченный набор записей. Единственный способ идентифицировать определённую запись в этой таблице — это указать набор значений одного или нескольких полей, который был бы уникальным для этой записи.
a = 5
b = a
do
a <- newMVar 5
b <- readMVar a >>= newMVar
MVar Int, и вот они не идентичны (a == b ≡ False), но их значения — равны. Вот тут у нас и впрямь переменные, что и выражено в их типах.var x = 2;
DoSomething(x);
// Каково значение y?
var y = x - 1;
// обработать оба случая
if (customerOrError.IsCustomer)
Console.WriteLine(customerOrError.Customer.Id);
if (customerOrError.IsError)
Console.WriteLine(customerOrError.ErrorMessage);
int? length = customer?.Length;
int? first = (customers != null) ? customer.Length : null;
__kernel void sum(__global const float *a,
__global const float *b,
__global float *c) // выходной буфер
{
int gid = get_global_id(0);
c[gid] = a[gid] + b[gid];
}
Входные буферы константны и не пишутся, а выходной буфер пишется, но не читается. Т.е. это чисто функциональный подход.
gemm('N', 'T', alpha, A, B, beta, C)
Однако это детали реализации абстракции «каждый раз новый буфер».
buffer = putpixel(buffer, x, y, red);
if (IsBufferFilled(buffer)) { buffer = putpixel(buffer, x+1, y, red); }buffer = putpixel(buffer, x, y, red);
(flag, buffer) = IsBufferFilled(buffer);
if (flag) { buffer = putpixel(buffer, x+1, y, red); }void filter2D(Mat src, Mat dst);
Можно вручную, а можно предусмотреть и автоматическую сборку мусора (которая, можно сказать, из FP родом).
А я говорю, что скорость выделения может быть очень высока и этим можно пренебречь.
# импортируем BLAS
julia> using Base.LinAlg.BLAS
# создаём два исходных массива A и B плюс один буфер C
julia> A = rand(10000, 100); B = rand(100, 10000); C = zeros(10000, 10000);
# перемножение матриц с выделением памяти
julia> @time for i=1:10 C = gemm('N', 'N', 1.0, A, B) end
8.822950 seconds (13.02 k allocations: 7.451 GB, 6.15% gc time)
# перемножение матриц с использованием преаллоцированного буфера
julia> @time for i=1:10 gemm!('N', 'N', 1.0, A, B, 0.0, C) end
4.607092 seconds
void filter2D(Mat src, Mat dst)
Mat filter2D(Mat src)
Уж не напоминаю, что в самой статье тема производительности тоже затрагивалась, причём с однозначным ответом на этот вопрос.
Внутри MemoryManager.GetMat может быть, например, кольцевой буфер.
Или дерево с ссылками на свободные chunkи памяти.
Или что угодно, что может оперировать с _предвыделенными_ областями памяти.
Единственная задача — предоставить абстракцию, чтобы в этот логический блок памяти записывались данные только для матрицы dst, и не записывались никакие другие.
Принципиальная реализация не важна.
Вы лучше скажите почему «выделение» — это всегда выделение.
И ещё было бы интересно понять почему ни один из описанных аллокаторов не решает задачу.
По поводу выделения гигабайта или 100 Мб — как поступают в реализации std::vector?
То же и про кольцевой буфер.
Про бесплатный сыр никто не говорил и не ожидал.
мутабельность и прочие плюшки тоже не бесплатны — увеличивают «стоимость» разработки.
take n . filter f . map g и типа [a] -> [a] никаких списков не создаётся, к слову, и более того, даже обход будет — один, а не три (в ленивом языке), и обходить будет не весь список (в конце ж у нас take n); причём map g, например, может быть оформлено отдельной функцией, которая якобы принимает список и возвращает «новый» список.Только сложно убедить того, кто не пользовался этим.
take n . filter f . map g, как это переписать на императивный?foo = map g
auto g( Item )( Item a ) {
return a * 2;
}
auto foo( Item )( Item a ) {
return a.map!g;
}
foo = take n
bar = filter f
baz = map g
quux = foo . bar . baz
data List a = Null | Cons a (List a)
Я не силён в Хаскеле, что тут происходит?
Почему?
Понятней не стало :-) Это бесконечный список из Null-ов или что?
Значит его и фиг оптимизируешь толком
map f . map g можно преобразовать в map (f . g) вне зависимости от того, что там за f и g.Список чего? Или это абстрактный тип такой?
Преобразовать-то можно и в императивном языке
Может хватит говорить загадками?
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()?Да, ленивые вычисления могут выполняться в любом порядке.
У вас ужасный, ужасный, ужасный стиль программирования на Python
Как я уже говорил, если кто-то применяет функции с побочными эффектами в функциях типа `map`, то ему нужно отрывать руки с корнем, вне зависимости от языка.
sequence_ $ map print [1..3] -- 1 2 3
sequence_ $ reverse $ map print [1..3] -- 3 2 1
Ну вот print — вроде грязная функция? А В Хаскеле можно передавать :)
Это не «стиль», это пример, показывающий, что в императивном языке такая замена неэквивалентна, а потому она не является оптимизацией, она является деталью реализации, о которой программисты должны знать, чтобы не совать в map грязные функции.
rdd.map(g).map(f)
rdd.map(compose(f, g))
Кто ж виноват, что в Haskell нет `foreach` :D
ghci> mapMOf_ (each . _Right . each . _head) putChar [Right ["foo", "baz", "baz"], Left 10, Right ["x", "y", "zoo"]]
fbbxyz
Хм, вы имеете ввиду оптимизацию на уровне компилятора? Тогда я не очень понимаю её суть — насколько я понимаю, композитная функция `(f. g)` всё равно транслируется в машинный код как последовательное применение двух функций к каждому элементу. Или здесь можно ещё что-то соптимизировать?
So, for example, the following should generate no intermediate lists:
array (1,10) [(i,i*i) | i <- map (+ 1) [0..9]]
for x in xs:
f(x)
for x in xs:
g(x)
# Совсем не то же самое, что:
# for x in xs:
# f(x)
# g(x)
Я там выше упоминал take n. filter f. map g, как это переписать на императивный?
[g(x) for x in a if f(x)][:n]
from itertools import *
islice(ifilter(f, imap(g, a)), n)
from itertools import *
r = imap(g, a)
r = ifilter(f, r)
r = islice(r, n)
foo = take n
bar = filter f
baz = map g
quux = foo . bar . baz
filter f . map g сворачивать в использование одной функции (Rewrite rules), потому что ни f, ни g не производят побочных эффектов.import functools
from functools import *
from itertools import *
def itake(n, coll): return islice(coll, n)
foo = partial(itake, 3)
bar = partial(ifilter, lambda x: x > 2)
baz = partial(imap, lambda x: x + 1)
def compose(*functions):
return functools.reduce(lambda f, g: lambda x: f(g(x)), functions, lambda x: x)
quux = compose(foo, bar, baz)
list(quux([1, 2, 3, 4, 5, 6])) # ==> [3, 4, 5]
Да. При этом в чистом языке можно подряд идущие filter f. map g сворачивать в использование одной функции (Rewrite rules), потому что ни f, ни g не производят побочных эффектов.
А что если у нас не списки, а деревья? Как сделать всё то же, но для своего типа данных?
Уж лучше идиоматично через list comprehensions, а то так и нечитаемее, и тормознее. А если идиоматично, то уже bar оттуда не вычленить так просто, композиционность страдает.
Как же это не обладает, когда _может_ обладать (у меня там недаром f и g — произвольные функции), а это для оптимизатора весьма важно.
Можно даже бесконечные деревья (как и списки) строить. Необязательно последовательно. Те же самые filter, map, что-либо ещё.
Итератор ходит вперёд. По дереву я могу своей функцией идти в том направлении, в котором мне надо. Это уже другой тип итератора совсем. Ходить вперёд — частый, хотя и распространённый, случай.
Ну так и язык не функциональный, понятно, что сложные функциональные штуки типа каррирования и композиции функции в нём будут хотя бы чуть-чуть, но сложнее синтаксически :D
Python по-умолчанию считает своих разработчиков достаточно вменяемыми
Не очень понял, о какой функции, которая может ходить в разных направлениях, идёт речь, но опять же, не вижу причин, почему нельзя то же самое сделать на любом другом языке.
-- | Дерево
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 = take n
bar = filter f
baz = map g
quux = foo . bar . baz
foo = partial(itake, n)
bar = partial(ifilter, f)
baz = partial(imap, g)
quux = compose(foo, bar, baz)
Вот здесь дерево ленивое — две строки.
class Tree(object):
def __init__(self, n):
self.n = n
def __getattr__(self, name):
if name == 'left':
return Tree(self.n + 1)
elif name == 'right':
return Tree(self.n + 2)
else:
raise AttributeError("No such attribute: %s" % (name))
def __repr__(self):
return "Tree(%s)" % (self.n,)
In [30]: t = Tree(1)
In [31]: t
Out[31]: Tree(1)
In [32]: t.right
Out[32]: Tree(3)
In [33]: t.right.right
Out[33]: Tree(5)
In [34]: t.right.left
Out[34]: Tree(4)
Нуу, тут хоть и две строчки, но специальных фич Haskell-а целая куча — и ленивые вычисления, и рекурсия через фиксированную точку, и развитая система типов.
В общем и целом, мы с вами вроде во всём согласны, а этот code golf начинает отнимать время, поэтому если критических возражений нет, предлагаю на этой оптимистической ноте и закончить.
Ну пусть будет у нас пул из 1000 буферов, пусть даже каждый буфер уже будет нужного размера — что угодно. Мы вызываем нашу функцию, результат записываем в буфер #1, затем в буфер #2, затем в буфер #3
Кто мешает написать функцию ФП с «выделением» памяти, которое на самом деле будет просто возвращать ссылку на область памяти из предвыделенного пула?
Он не модифицируется (для этого необходимо чтение), а _полностью перезаписывается_.
Ваш пример чисто функциональный 'с = a + b'.
Причём тут DoSomething() вообще?
Нет никакой разницы между C sum(A B) и sum(*C, const *A, const *B)
array sum(array a, array b)
{
array c = zeros(length(a)); // мы только что выделили кусок памяти
for (i = 0, i < ) {
c[i] = a[i] + b[i];
}
return c;
}
array sum(array a, array b, array c)
{
for (i = 0, i < ) {
c[i] = a[i] + b[i]; // заполняем преаллоцировнный буфер
}
return c;
}
Да кто ж мешает. Сделайте «бесконечный» пул и храните там.
А ваш язык программирования необоснованный? (или почему предсказуемость важна)