LINUX.ORG.RU
ФорумTalks

лямбды в новых язычках - PR или реальные полезняшки?

 , ,


7

7

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

Ну что есть lambda в каком-нибудь lisp я представляю и даже понимаю зачем оно и как им пользоваться. В lisp'е. А что имеется ввиду под «лямбдой» например, в C#?

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

Только чтобы это не было аналогом перлового однострочника типа

perl -e 'print sub{ $_[0] + $_[1]; }->(1,2)."\n";'
ибо в этом никаких новшеств и преимуществ нету.

Просто сдаётся мне что «лямбда» в нынешних сишарпах это пиарное название допотопных безымянных функций которые даже в перле есть и никаких новшеств в этом на самом деле нету.

★★★★★

Ответ на: комментарий от quasimoto

Просто если делать сразу список факториалов, то следующий в нём элемент это произведение предыдущего на счётчик:

я знаю. но кто мне говорил про реюзабельность? вот смотри:

вот смотрите, как ваша лисп-реализация сосёт! for(y = 1; x; x--) y *= x;

а ты сделай не 1, 4 факториала от 2х до 5и!

да легко!

	for(n = N; n <= M; n++)
	{
		int x = n;
		for(y = 1; x; x--)
			y *= x;
		*sp++ = y;
	}

у тебя O(N^2)

блжад!

1. это не N^2. Факториал вычисляется O(N), а 4 факториала вычисляются как 4*O(N)===O(N).

2. можно подумать, что список как-то не циклами выполняется!

3. оптимизировать можно. А нужно-ли? тут я просто скопировал код вычисления факториала. А если-бы надо было вычислить не факториал 2 3 4 5, а факториалы произвольного списка? или ещё что-нить? по любому код на C работает в 100 раз быстрее, и что его пилить-то? (учитывая что вывод по любому в 100500 раз дольше, а если-бы не было вывода, то это ваше N в любом случае настолько мизерное, что применять асимптоту нельзя. Сортировка вставками работает в 10+ раз быстрее на маленьких массивах любой другой сортировки с асимптотой O(N*log(N)). Хотя у неё O(N^2).

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

Т.е. ты будешь дублировать одинаковую логику каждый раз.

если ты называешь «логикой» x*y, то мой ответ - да. Буду. Написать x*y по любому проще и быстрее, чем лямбда-функцию.

Конечно поддерживаю. Но проблема не только в «простыне».

да. проблема ещё и в том, что ПРОЦЕССОР НЕ УМЕЕТ ЛЯМБДЫ. Не нравится? Ну отпиши гневное письмо CEO Inetl(или AMD, а лучше в спортлото). Потому, твои лямбды, это не просто «сахар», это такой отравленный сахар, который разворачивается в совершенно дикие (ВНЕЗАПНО) указатели на функции. Которые полностью парализуют работу CPU, ибо процессор не в курсе, КУДА передавать управление. Потому, перед твоей лямбдой процессор сначала будет ждать, пока вычислится адрес, потом будет ждать, пока загрузится данный кусок в кеш и развернётся (ибо предвыборка не работает), и всё это только потому, что быдлокодеру было лень записать x*y, а вот лямбду - не лень.

drBatty ★★
()
Ответ на: комментарий от wota
#include <algorithm>
#include <gmpxx.h>
#include <iostream>
using namespace std;

#define NEXT(...)   gen_type& operator++() { __VA_ARGS__; return *this; }
#define RETURN(...) T& operator*() { return __VA_ARGS__; }
#define WHILE(...)  bool operator!=(const gen_type&) const { return __VA_ARGS__; }

#define GENERATOR( name, ... )                                          \
    struct name : public iterator<forward_iterator_tag,T> {             \
        typedef name gen_type;                                          \
        __VA_ARGS__                                                     \
        name& begin() { return *this; }                                 \
        name& end() { return *this; }                                   \
        bool operator==(const name& ) const { return !(*this!=*this); } \
    };

template<class T=mpz_class>
GENERATOR( numbers,
    RETURN( i ) WHILE( n==0 || i<=n ) NEXT( ++i )
	numbers( T to=T() ) { i=T(1); n=to; }
    T i, n;
)

template<class T=mpz_class,class I=numbers<T>>
GENERATOR( factorials,
    RETURN( n ) WHILE( i!=i ) NEXT( ++i; n*=*i; )
    factorials( T to=T() ) { i=I(to); }
    T n=1;
	I i;
)

int main() {
    for( auto i: factorials<>() )
        std::cout << i << '\n';
}

OMG! Что это!? Высокоуровневое программирование на плюсах?

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

Конечно поддерживаю. Но проблема не только в «простыне».

да. проблема ещё и в том, что ПРОЦЕССОР НЕ УМЕЕТ ЛЯМБДЫ. Не нравится?

Ты о чем вообще, болезный? Процессор и for(...; ...; ...) не поддерживает. Это забота транслятора.

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

а с лямбдами можно было всё сделать без лишней писанины

угу. можно. вопрос - нужно-ли? в твоём коде компилятор ТОЧНО знает, когда и какая функция вызывается, и может подстелить соломку, чтоб больно не падать - заготовить параметры и прочее. Gcc очень хорошо помнить константность, для него plusOne НЕ переменная, а вполне конкретное число, которое в тривиальной mapIntArray(int *a, int n, int (*func)(int)) он может просто взять, и использовать вместо func. К сожалению этого пока нет, за то параметры жёстко вшиты в код:

 80485aa:       8d b6 00 00 00 00       lea    esi,[esi+0x0]
 80485b0:       8b 44 b5 00             mov    eax,DWORD PTR [ebp+esi*4+0x0]
 80485b4:       89 04 24                mov    DWORD PTR [esp],eax
 80485b7:       ff d7                   call   edi
 80485b9:       89 04 b3                mov    DWORD PTR [ebx+esi*4],eax
используя лямбды так сделать невозможно, т.к. совершенно непонятно, что эта лямбда принимает, и что выдаёт. А вот сишечка тебя _заставила_ это прописывать. Фишка в том, что эта «писанина» НЕ ЛИШНЯЯ, во первых это позволяет компилировать более быстрый код, а во вторых отлавливает 99% очепяток. Ну и IRL её в разы меньше, чем в тестовых примерах типа plusOne, К.О. утверждает, что функция +1 не нужно. Нужно что-то более нужное. Т.е. IRL кода будет больше, а «писанины» ровно столько же.

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

Процессор и for(...; ...; ...) не поддерживает.

поддерживает. Есть там циклы. А вот ВЕРНУТЬ ФУНКЦИЮ ИЗ ФУНКЦИИ нельзя. Можно вернуть указатель. Проблема в том, что использования указателя ОЧЕНЬ ДОРОГО, ибо ломает весь конвейер. А вот циклы - не ломают. Переход назад(в цикле) уже как минимум 15 лет отлично предсказывается с 99% вероятностью, и НЕ ЗАНИМАЕТ ВРЕМЕНИ. Фактически процессор просчитывает сразу несколько итераций цикла одновременно.

Это забота транслятора.

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

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

ну вот цикл из сишечки напрямую переводится в код

пруф

		for(y = 1; x; x--)
			y *= x;
 8048303:       ba 01 00 00 00          mov    edx,0x1
<skip>
 8048310:       0f af d0                imul   edx,eax
 8048313:       48                      dec    eax
 8048314:       75 fa                   jne    8048310 <main+0x20>

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

Отсутствие специфичного для clojure синтаксического сахара не является недостатком CL.

Что интересно, несмотря на все рассуждения примеры практически идентичны. Единственное что массив setf-ов стоило написать по другому, если ставить целью мериться строчками.

Фактически ваши рассуждения уже перешли от многопоточных высот к примитивному мерянию синтаксическим сахаром. А оно мне как уже говорил выше фиолетово.

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

это не N^2. Факториал вычисляется O(N), а 4 факториала вычисляются как 4*O(N)===O(N).

Квадратично от (M - N) в общем случае. Конечно, если они всегда 5 и 2, то вообще константно, так как никакого входа нет.

можно подумать, что список как-то не циклами выполняется!

Define «циклы». jmp / cmp* / test* / je / jne? Тогда непонятно различие между циклами / if и goto / if и tagbody / if и tail-calls / любым другим набором примитивов языка. И это на усмотрение компилятора, могут и рекурсивными функциями выполняться :)

Но тоже - либо квадратично, либо линейно:

mapM_ print $ map (product . enumFromTo 2) [1..]
mapM_ print $ scanl1 (*) [1..]

это ваше N в любом случае настолько мизерное, что применять асимптоту нельзя

А процесс почему-то ест под 100% времени на ядре, так что если начать печатать списки из тысяч факториалов, то разницу по времени можно будет заметить как раз квадратичную.

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

угу. можно. вопрос - нужно-ли?

Имхо, нужно. Может не в Си.

в твоём коде компилятор ТОЧНО знает, когда и какая функция вызывается, и может подстелить соломку, чтоб больно не падать - заготовить параметры и прочее. ... А вот сишечка тебя _заставила_ это прописывать.

Не обратил внимание на варнинги и прога упала в рантайме через месяц работы.

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

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

Используя лямбды где? В haskell известно.

К.О. утверждает, что функция +1 не нужно.

Конечно не нужно, но тебе придётся её написать в Си. А с лямбдами всё проще.

Есть список таймаутов по пользователям — нужно уменьшить на единицу каждый таймаут и убрать тех чей таймаут меньше 0.

> filter ((>0) . snd) $ map (\(x,y) -> (x, y - 1)) [("user1", 33), ("user2", 10), ("user3",1)]
[("user1",32),("user2",9)]

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

OMG! Что это!? Высокоуровневое программирование на плюсах?

list<int> l=generate_n(10, rand);
int s=accumulate(l, 0, sum);
list<int> m=transform(l, inc);

вот «высокоуровневое» программирование на С++ (если под себя сделать «шорт» версии функций STL, или даже свои ленивые списки), а то так - извращения, чтоб не писать полноценную поддержку итераторов, для примера она не нужна

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

Я не утверждаю, что CL плох (сам был увлечен им какое-то время). Я утверждаю, что он провоцирует писать нечистые функции и что в clojure добавили вещи (вы проигнорировали соответствующие пункты), которых нет в принципе в CL: collection comprehension, STM, persistence data structure, syntax hygiene, lazy collections).

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

Вон люди всю жизнь кодят на php и не страдают.

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

Квадратично от (M - N) в общем случае. Конечно, если они всегда 5 и 2, то вообще константно, так как никакого входа нет.

как ты не можешь понять, что при малости M-N асимптоту использовать НЕЛЬЗЯ? Если ограничиться целыми, то можно вычислить факториал максимум 2х-3х десятков первых значений. Асимптота вообще не влияет на вычисления, а влияет лишь константа пропорциональности. Эта константа очевидно у моего кода как минимум на порядок ниже.

Define «циклы». jmp / cmp* / test* / je / jne? Тогда непонятно различие между циклами / if и goto / if и tagbody / if и tail-calls / любым другим набором примитивов языка. И это на усмотрение компилятора, могут и рекурсивными функциями выполняться :)

рекурсивными конечно можно, но зачем этот оверхед?

Но тоже - либо квадратично, либо линейно:

можно узнать, как тут «линейно» получилось?

А процесс почему-то ест под 100% времени на ядре, так что если начать печатать списки из тысяч факториалов, то разницу по времени можно будет заметить как раз квадратичную.

если ты желаешь печатать тысячи факториалов, то это совсем ДРУГАЯ задача. Начнём с того, что вычисление умножения уже не будет O(1) как для нативных целых, а будет составлять в лучшем случае O(ln(N!)), однако, логарифм факториала растёт в N раз быстрее самого N(sic!) (т.к. n! ~= exp(n*ln(n))), потому асимптота будет в лучшем случае O(N*N*ln(N)) (вот тут - да. Надо юзать прошлое n! для вычисления (n+1)!)

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

Имхо, нужно. Может не в Си.

может и в С, только надо Over9000 раз подумать, чем вбивать твои лямбды. В том виде, в каком они существуют в питоне и шарпе их можно и в Си сделать. Через void*. Можно ещё и макросов добавить, что-бы получить видимость того, что будто-бы всё хорошо.

Не обратил внимание на варнинги и прога упала в рантайме через месяц работы.

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

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

это НЕПРАВДА. ты очень плохо знаешь C++, если с этим согласен - только базовые понятия, вроде int и long. Фишка в том, что в отличие от классической сишечки, в плюсах тип указателя НЕЛЬЗЯ перобразовать иначе, кроме как через void* (для наследования можно ещё ручками преобразовывать базовый класс в производный). В классической сишечке - да, предупреждение, но если быдлокодер «не видит» ТАКИЕ предупреждения, его место - в php-кодинге.

классический Си, в котором, хотя объявление типов и требуется, в действительности все типы данных являются совместимыми по присваиванию

совместимы только внутренние типы данных только в сторону расширения. Т.е. можно например int->long, обратно будет орать.

Используя лямбды где? В haskell известно.

в сабже, в питоне. хаскеля я не знаю.

Конечно не нужно, но тебе придётся её написать в Си. А с лямбдами всё проще.

нет. не придётся. В общем случае, для применения функции f(x) для X, есть три пути:

1. написать функцию ff(x), которая принимает X, и возвращает Y=f(X); // передача по значению

2. отдать новой функции не только значение X, но все потроха этой X, тогда ff(x) может менять X непосредственно. // передача указателя &X или ссылки, или замыкание.

3. отдать новой функции саму функцию f(x), дабы ff(f(x)) применяла f(x) непосредственно к X // лямбда или callback

ты вот почему-то решил, что кроме способа №3 ничего и не бывает. Но это не так. В C/C++ во все поля применяют способ №2.

Есть список таймаутов по пользователям — нужно уменьшить на единицу каждый таймаут и убрать тех чей таймаут меньше 0.

почему «список»?

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

это НЕПРАВДА. ты очень плохо знаешь C++

С++ тут при чём? Я про него вообще не писал. Это цитата с википедии про Си.

нет. не придётся.

Покажи этот «красивый код» на Си.

почему «список»?

Предлагай свой вариант решения.

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

используя лямбды так сделать невозможно

Почему?

int fn(std::function<int(int)> f, int i) { return f(i); }

// 	subq	$8, %rsp
// 	cmpq	$0, 16(%rdi)
// 	je	.L4
// 	movq	24(%rdi), %rax
// 	addq	$8, %rsp
// 	jmp	*%rax
// .L4:
// 	call	_ZSt25__throw_bad_function_callv

даже jmp а не call.

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

А я хотел уже mapSecond писать :D

Спасибо, но я побаиваюсь стрелок... Может на выходных почитаю.

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

Да, в хаскеле хорошо видно красоту фп. Спасибо за примеры.

import Data.Ratio
naturals :: [Integer]
naturals = [1 ..]
factorials :: [Integer]
factorials = scanl1 (*) naturals
triangulars :: [Integer]
triangulars = scanl1 (+) naturals
factorialsDivisibleByTriangulars :: [Integer]
factorialsDivisibleByTriangulars
  = map fst $ filter ((== 1) . denominator . snd)
  $ zipWith3 (\n p s -> (n, p % s)) naturals factorials triangulars

Красиво. На clojure вначале близко получилось:

(def naturals (drop 1 (range)))
(def factorials (cons 1 (scanl * naturals)))
(def triangulars (scanl + naturals))
(def factorials-divisible-by-triangulars
  (map first
       (filter #(= 0 (second %))
               (map (fn [n p s] [n (mod p s)])
                    naturals factorials triangulars))))

За исключением того, что reduce не могу использовать из-за его жадности. Пришлось руками писать ленивый scanl

(defn scanl
  ([f col] (scanl f (first col) (rest col)))
  ([f start col] (when-not (empty? col)
                   (let [new-start (bigint (f start (first col)))]
                     (lazy-seq (cons new-start
                                     (scanl f new-start (rest col))))))))

Дальше код получения ленивого списка простых чисел

primes :: [Integer]
primes = 2 : 3 : filter (noDivs $ tail primes) [5, 7 ..] where
  noDivs fs n = foldr (\f a -> f * f > n || (rem n f /= 0 && a)) True fs

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

(defn gen-primes
  ([] (gen-primes [] (drop 1 naturals)))
  ([prime-list start]
     (let [cur-prime (first start)]
       (lazy-seq (cons cur-prime
                       (gen-primes (cons cur-prime prime-list)
                                   (filter (comp not (fn [x] (some #(= 0 (mod x %))
                                                                   prime-list)))
                                           (rest start))))))))
(def primes (gen-primes))

ужас какой-то.

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

Я утверждаю, что он провоцирует писать нечистые функции

Это очень общее утверждение, которое нужно рассматривать на примере несколько другого алгоритма чем несколько строк работы с hashtable. C другой стороны CL действительно не ставит чистоту как самоцель и в этом нет ничего плохого. Это все таки не ML и не haskell.

(вы проигнорировали соответствующие пункты), которых нет впринципе в CL: collection comprehension, STM, persistence data structure, syntax hygiene, lazy collections)

«Я знаю каратэ, тэквондо и мого других страшных слов»

Часть из них в библиотеках, часть не признаны общеполезными, часть отражают собственый ход мыслей авторов clojure то есть не факт что будет повторен. У это «нет в CL» много оттенков. То есть таким пустым и звучным списком нет смысла сравнивать.

Я не ставил целью сравнивать clojure целиком , меня скорре заинтересоволо высказывание о недостатках.

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

На счет захламленного синтаксиса, конечно, можно привыкнуть,

Вот именно мое впечетление о clojure:) Как раз то самое слово.

Отсюда ваше перманентное «не нужно».

Я этого не говорил. Не на приписывать вские домыслы.

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

А чтобы такое провернуть по феншую нужно Data.Lenses использовать?

data User = User {name:: String, timeout:: Integer}

map (change timeout (+1)) $ [User "user1" 10, User "user2" 20]
adzeitor
()
Ответ на: комментарий от adzeitor

С++ тут при чём? Я про него вообще не писал. Это цитата с википедии про Си.

вика не авторитет. если в си можно отстрелить ногу, это не говорит о том, что в си нужно отстреливать ноги.

Покажи этот «красивый код» на Си.

уже показывал. добавление в список *sp++ = x;

Предлагай свой вариант решения.

это приоритетная очередь? почему у тебя относительное время? почему нельзя взять абсолютное? скажем сейчас 12345 секунд, таймаут оканчивается в 12355 секунд (10сек). тогда не нужно бегать по всему списку и вычитать 1. Скорость увеличивается и лямбда не нужна.

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

Эта константа очевидно у моего кода как минимум на порядок ниже.

Вот этого не пойму. Одним циклом:

1! = 1
2! = 2
3! = 3 * 2!
4! = 4 * 3!
5! = 5 * 4!

3 умножения. Двумя:

1! = 1
2! = 2
3! = 3 * 2
4! = 4 * 3 * 2
5! = 5 * 4 * 3 * 2

6 умножений.

рекурсивными конечно можно, но зачем этот оверхед?

Его быть не должно, поэтому там где делают рекурсивными добавляют ещё TCO.

можно узнать, как тут «линейно» получилось?

Если не считать нижеследующей поправки про умножение для больших чисел не за O(1) (ghc использует gmp)? Абстрактно линейно от входа. Вход же поток - как ещё тут что-то может работать кроме как не линейной его обработкой?

[1..] это ленивый генератор:

nats from = go from where go n = n : go (n + 1)
-- take 10 $ nats 5
-- => [5,6,7,8,9,10,11,12,13,14]

--                ,-----.
--   $@           v     |
--  /  \    ->    :     |
-- go   n        / \    |
--              n   $@--^
--                 /
--                @
--               / \
--              @   1
--             / \
--            +   n

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

Аналогично scanl это отображение ленивого генератора:

pp z (x:xs) = z : pp (z * x) xs
-- take 10 $ pp 1 $ nats 2
-- => [1,2,6,24,120,720,5040,40320,362880,3628800]

--                      ,-----.
--                      v     |
--      $@              :     |
--     /  \            / \    |
--    /    \     ->   z   $@--^
--   @      :            /  \
--  / \    / \          @    xs
-- pp  z  x  xs        / \
--                    @   x
--                   / \
--                  *   z

mapM_ относится уже к IO, которое должно как-то заставлять ленивый язык работать, и, в конечном итоге, превращается в tail-call алгоритм (то есть как раз цикл) вычерпывающий значения из этого ленивого преобразователя ленивого генератора и печатающий их. В STG это выглядит примерно так:

main3 ... [xs ...]
  case xs of _ {                                  -- case
    ...
    y : ys ->
      let {                                       -- let
        str = showInteger y
      } in case hPutStr2 stdout str ... of _ {    -- case
        ... -> main3 ys ...                       -- TC? yes.
      }
}

main2 ...
  case main1 of _ {
    ...
    x : xs -> scanl (*) x xs of zs {
       ... -> main3 zs ...
  }
}

main1 ...
  enumDeltaInteger 1 1
quasimoto ★★★★
()
Последнее исправление: quasimoto (всего исправлений: 1)
Ответ на: комментарий от quasimoto

В продолжение темы ленивости в C++. Вот тут говорится про то как определённая реимплементация итераторов может позволить ленивые вычисления.

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

добавление в список *sp++ = x

в список, да? Мдааа. Зачем это здесь? Я спрашивал как в мою функцию mapIntArray на Си передать (x*y) или (x+1) без написания фукнции.

почему у тебя относительное время?

мне так удобно.

почему нельзя взять абсолютное?

те же яйца только сбоку.

это приоритетная очередь?

Зачем она здесь? Скорость удаления возрастёт, а скорость добавления наоборот будет ниже.

таймаут оканчивается в 12355

принцип другой — если пользователь подтвердил, что он присутствует, то timeout опять равен 60. Похожая система, например, в redis.

Скорость увеличивается и лямбда не нужна.

Нужна. Каждые x секунд делается (-x). (-x) кто делать будет?

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

что-бы это значило?

printf(«%d\n», fn([](int x) { return x + 1; }, 5));

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

6 умножений.

ну посчитай сколько _делений_ делает твоя printf(2)? Кстати у меня ещё 1!=1*1, 2!=1*1*2 (; И что? Тебе легче от этих 50и тактов сэкономленных?

mapM_ относится уже к IO, которое должно как-то заставлять ленивый язык работать, и, в конечном итоге, превращается в tail-call алгоритм (то есть как раз цикл) вычерпывающий значения из этого ленивого преобразователя ленивого генератора и печатающий их. В STG это выглядит примерно так:

блжад! столько матана, что-бы вычислить какой-то сраный факториал, который любой сишник с опытом неделя и больше считает в нулину пьяный левой пяткой ноги...

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

в список, да?

ага.

Я спрашивал как в мою функцию mapIntArray на Си передать (x*y) или (x+1) без написания фукнции.

сказал-же: не нужно.

мне так удобно.

тебе так кажется. время должно течь у всех одинаково.

Зачем она здесь? Скорость удаления возрастёт, а скорость добавления наоборот будет ниже.

откуда мне знать?

принцип другой — если пользователь подтвердил, что он присутствует, то timeout опять равен 60. Похожая система, например, в redis.

добавь +60 нужному юзеру.

Нужна. Каждые x секунд делается (-x). (-x) кто делать будет?

никто. в том и профит. каждые x секунд надо сделать global time-=x;

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

сказал-же: не нужно.

Я так и думал.

откуда мне знать?

Сам же про неё начал писать...

добавь +60 нужному юзеру.

Я делаю =60.

Твой способ:

timeout = 0 (globaltmie = 0)
(прошла секунда)
он подтвердил стало -  60(globaltime = 1)
(через секунду)
опять подтвердил стало - 120 (global time = 2)
(через секунду)
опять подтвердил стало - 180 (global time = 3)
пользователь вышел
(прошла минута)
у пользователя 180, он давно вышел( global time = 60)
система не сработала.

Конечно, если он фиксированно будет через 60 секунд подтверждать, то это сработает. Но это вакуум какой-то.

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

Я так и думал.

правильно думал.

Твой способ:

я не знал, ЧЕГО ты хочешь. При подтверждении ставь время в time+60, тогда, через 1 сек и подтверждение будет 61. А условие исключения будет достигнуто на 61й сек, без подтверждения. А если будет в момент 30 подтверждение, то будет 90. И на 90й юзер будет удалён.

Я делаю =60.

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

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

А чтобы такое провернуть по феншую нужно Data.Lenses использовать?

В принципе да. Хотя иногда можно настроить свою схему агрегации - тут получится, так как User это произведение разных типов:

{-# LANGUAGE MultiParamTypeClasses, TypeOperators, TypeSynonymInstances #-}

default ( Integer ) -- ?

class a :> b where
  sup :: b -> a
  sub :: a -> b
  inout :: (a -> t) -> b -> t
  inout f = f . sup
  change :: (a -> a) -> b -> b
  change f = sub . f . sup

data User = User { name :: String, timeout :: Integer } deriving Show

instance String :> User where
  sup = name
  sub = error "no rule for: String -> User"
  change f x = x { name = f (name x) }

instance Integer :> User where
  sup = timeout
  sub = error "no rule for: Integer -> User"
  change f x = x { timeout = f (timeout x) }

и далее выбирать по типы без явного указания поля (defaulting что-то не работает):

t1 = map (change (++ ".usr")) [User "user1" 10, User "user2" 20]
-- > t1
-- [User {name = "user1.usr", timeout = 10},User {name = "user2.usr", timeout = 20}]

t2 = map (change (+ (1 :: Integer))) [User "user1" 10, User "user2" 20]
-- > t2
-- [User {name = "user1", timeout = 11},User {name = "user2", timeout = 21}]
quasimoto ★★★★
()
Ответ на: комментарий от drBatty

Да, теперь это сработает. Имхо, оно не лучше и не хуже моего. Не нравится мне этот global_time ;) Точно какие-нибудь косяки с ним вылезут.

может нарушится причинно-следственная связь

всё в одном месте — не нарушится. Сейчас это thread-safe STM и оно работает.

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

Я утверждаю, что он провоцирует писать нечистые функции

Это очень общее утверждение, которое нужно рассматривать на примере несколько другого алгоритма чем несколько строк работы с hashtable.

Если в коллекциях нет поддержки иммутабельности и все базовые методы изменеий коллекций (list, hashtable,vector) реально модифицируют целевую коллекцию, то достаточно тяжело будет при необходимости работать с чистыми функциями. Плюс ко всему в коллекциях CL нет реюза элементов при создании новой коллекции из старой. Я понимаю, что можно все с нуля написать, но как бы базовый функциоанал языка тут не помогает. Это достаточно конкретное утверждение?

(вы проигнорировали соответствующие пункты), которых нет впринципе в CL: collection comprehension, STM, persistence data structure, syntax hygiene, lazy collections)

«Я знаю каратэ, тэквондо и мого других страшных слов»

Видимо это вы что-то из своего опыта.

Collection comprehension: попробуйте набрать в CL такую структуру вложенный коллекций

{:mvalue {:ke1 :val1
          :ke2 (assoc {:subm-key1 val3}
                      :vkey [1 2 3 "hello"]})}
 :row '('ONE 'TWO 'THREE)
 :tags #{:fun :clojure :fp}
 :state {:place "here"
         :mood "fun"}}]

STM: вы разрабатывали мультипоточное приложение с каким либо состоянием. Хорошо, когда вы создаете лок на одну переменную, а если переменных больше? Если разные куски кода работают с этими переменными в разном сочетании параллельно в режиме чтение/запись? Уже требуется больше усилий.

Теперь представьте, что у вас появились транзакции как в бд при работе с этим состоянием. Вот это и есть STM. Можно работать полностью транзакционно (dosync, ref), можно работать с отдельными переменными атомарно (atom), можно работать асинхронно (agent). Все просто и очень практично.

syntax hygiene, lazy collections: я уже выше приводил примеры.

Часть из них в библиотеках, часть не признаны общеполезными, часть отражают собственый ход мыслей авторов clojure то есть не факт что будет повторен. У это «нет в CL» много оттенков. То есть таким пустым и звучным списком нет смысла сравнивать.

Не распарсил.

Я не ставил целью сравнивать clojure целиком , меня скорре заинтересоволо высказывание о недостатках.

Если недостаток только в сигналах (и еще что-то одно, лень мотать тред) мешает использовать язык, то это, по крайней мере, странно.

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

блжад! столько матана, что-бы вычислить какой-то сраный факториал, который любой сишник с опытом неделя и больше считает в нулину пьяный левой пяткой ноги...

То у тебя Кнут и прочие асимптотики, то в нулину пьяный. Ты же спросил почему линейный - я ответил.

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

Да, теперь это сработает. Имхо, оно не лучше и не хуже моего. Не нравится мне этот global_time ;) Точно какие-нибудь косяки с ним вылезут.

оно не обязано быть глобальным. Это старое как мир вынесение инварианта из тела цикла. Вместо вычитания из каждого счётчика 1, я прибавляю к одному. При этом сравнивать приходится не с 0, а с общим счётчиком. Результат операции ++X < Y очевидно эквивалентен X < --Y. Вылезти может. Например ++X может переполниться (опустошение --Y ты проверяешь).

всё в одном месте — не нарушится.

нужно замораживать всю очередь на время декремента. Или доказывать факт непротиворечивости при частичном декременте части очереди.

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

То у тебя Кнут и прочие асимптотики, то в нулину пьяный. Ты же спросил почему линейный - я ответил.

а зачем какие-то лямбды вычерпывающие, если

1!=1
2!=1!*2
3!=2!*3
4!=3!*4
5!=4!*5
вполне себе линейно, если x*y имеет сложность O(1)?

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

а зачем какие-то лямбды вычерпывающие

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

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

Если недостаток только в сигналах (и еще что-то одно, лень мотать тред) мешает использовать язык, то это, по крайней мере, странно.

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

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

Если недостаток только в сигналах (и еще что-то одно, лень мотать тред) мешает использовать язык, то это, по крайней мере, странно.

Мешает использовать прежде всего завязаность на яве. Обсуждение фич на этом фоне имеет чисто теоритический интерес.

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

В CL по необходимости какие-то нужные вещи выносятся в foreign функции, которые пишутся, например, на Си. Но это же не мешает, правда?

Мне действительно интересно, почему такое отношение к самой jvm. Ведь сама платформа и виртуальная машина была создана очень талантливыми инженерами и это безотносительно к самому языку джава.

Чем вам не нравится jvm как платформа?

Отсутствие сигнального протокола заставляет задуматься о том что еще не столь очевидного не дописано или признано не нужным.

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

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

Нет завязанности на джаве. По крайней мере джава используется как ассемблер и программист в принципе не

обязан с джавой работать.

Не точно выразился. Имелась ввиду jvm.

Ведь сама платформа и виртуальная машина была создана очень талантливыми инженерами

Которые сделали из тормозного интерпретатора байткода не очень тормозной но жрущий память jit.

Чем вам не нравится jvm как платформа?

Аппетитами к ресурсам, некоторой ненужностью при наличии уже отлаженой нативной компиляции для CL. Язык общего применения должен быть либо отвязан от конкретных vm и платформ как CL и в какой-то степени python и ruby. Или опираться на фундаментальный железный базис как C. JAVA в этом смысле сборник компромисов. Язык основанный исключительно на ней становится либо нишевым, либо разделяет ношу компромисов.

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

На сколько я понял, люди, отвечающие за стандарт CL, очень консервативны.

На самом деле таких людей уже нет. И язык развивается библиотеками, благо залженная база позволяет. Из относительно недавних примеров устаканивание MOP, weak hash table, readtable.

В CL по необходимости какие-то нужные вещи выносятся в foreign функции, которые пишутся, например, на Си.

На самом деле это редкость и пережиток и порождает некоторое количество граблей. Для меня это один из плюсов CL по сравнению с скриптовыми, что можно обходтся без C.

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

Чем вам не нравится jvm как платформа?

Аппетитами к ресурсам, некоторой ненужностью при наличии уже отлаженой нативной компиляции для CL. Язык общего применения должен быть либо отвязан от конкретных vm и платформ как CL и в какой-то степени python и ruby. Или опираться на фундаментальный железный базис как C. JAVA в этом смысле сборник компромисов. Язык основанный исключительно на ней становится либо нишевым, либо разделяет ношу компромисов.

Я догадываюсь, но возможно ошибаюсь. В каких нишах нельзя (неэффективно) использовать решения на jvm?

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

В CL по необходимости какие-то нужные вещи выносятся в foreign функции, которые пишутся, например, на Си.

На самом деле это редкость и пережиток и порождает некоторое количество граблей. Для меня это один из плюсов CL по сравнению с скриптовыми, что можно обходтся без C.

Т.е. все можно писать на CL? Т.е. все библиотеки для работы с БД, сетевыми протоколами и т.п. написаны на CL без использования C?

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

Т.е. все библиотеки для работы с БД,

Все кроме самого С-шного драйвера. И то в основном потому, что публичным является именно С-шный интерфейс драйвера а не сетевого доступа. В случаях когда доступен сокетный api: pg, redis то используется в основном он.

сетевыми протоколами

Все что выше tcp/ip и сокетов. За исключением mq-протоколов опять же из-за первичности драйверов.

То бишь С-шные библиотеки по необходимости используются, но специально куски CL-кода в С-шные не выносятся и не переписываются. Это было актуально в начале нулевых, судя по истории библиотек, по соображениям скорости но сечас как-то сошло на нет за недобностью.

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

В каких нишах нельзя (неэффективно) использовать решения на jvm?

При предоставлении As Service c большой переменной состаляющей предметной области по клиентам и общим аппаратно-програмным бюджетом, x86(64). Для облаков цена мегабайта, секунды процессорного времени вполне реальная величина в отличае от десктопа. И учитывая что клиент платит за что-то свое предметное то все потрачное впустую будетминусом либо в доходах, либо в репутации из-за завышенных цен. А еще потребности у клиентов немножко разные и просто раскидать стоимость на всех не получится. Небольшие «умные» компании которые живут за счет отсутствия бюрократии и способности быстро крутится вполне себе готовы платить то что бы все крутилось, но вот выбить из них сервер среднего уровня проблемно. Как обратный пример можно взять продажную или производственную компанию с некоторой бюрократией. Сервер пойдет по одной статье , зарплата персонала по другой, а какая отдача от получившегося веб-портала посчитать так никто и не сможет. Сделали, наверное нужен и фиг с ним. То есть у персонала нет мотивации делать «максимально хорошо», сделают средне. Мощности сервера могут и превысить потребные. Посещяемость у ресурса, тоже не такая большая а если и свалят то тоже не большая беда. Такой вот средне-стабильный компромис.

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