LINUX.ORG.RU

Замыкания в Haskell и Scheme


0

2

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

Вот пример чистой функции:

(define a 1)
(define tst (let((a 1)) (lambda(x) (+ a x))))
(write (tst 1)) ; --> 2
(define a 10)
(write (tst 1)) ; --> 2
А вот эта - уже с душком.
(define a 1)
(define tst (let((a (lambda() a))) (lambda(x) (+ (a) x))))
(write (tst 1)) ; --> 2
(define a 10)
(write (tst 1)) ; --> 11
Вот так вот, легким движением руки мы сломали чистоту.

Собственно вопрос: В хаскеле тоже так возможно?

В хаскеле тоже так возможно?

Нельзя.

PolarFox ★★★★★ ()

безо всякого оператора присваивания

(define a 1)
; ...
(define a 10)

5.3.1. Top level definitions At the outermost level of a program, a definition

(define variable expression)
has essentially the same effect as the assignment expression
(set! variable expression)
if variable is bound to a non-syntax value.

ilammy ★★★ ()

Слушай, ты опять выходишь на связь, чистый ты наш?

shamaz ()

Вообще, гениальное стартовое сообщение, на уровне R00T'а если не выше.

..., _хаскелисты_ очень дорожат иммутабельностью. Я _не знаю_ как реализованы замыкания в _хаскеле_, но _на примере _scheme_ ...

aedeph_ ★★ ()

Взял мультипарадигменный(читай: грязный) лисп и начал показывать, как там сломать чистоту. Лучше бы на сишечке показал.

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

У тебя не получится быть толще ТС'а, можешь прекращать.

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

Какая дискуссия с тобой, кроме тупняка ничего родить не способен.

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

Этот сумасшедший хоть какое-то понятие о семантике имеет. Ты же - вообще полный ноль.

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

А как же циклы пейсать? Или на память хаскелистам вообще насрать?

Так же как в схеме: (loop (x y z) .... (if (end-loop x y z) (values x y z) (loop new-x new-y new-z))

При TCO память не тратится.

monk ★★★★★ ()

Через хак с unsafeIO можно, если сделать аккуратно (создаешь ссылку IORef в вычислении IO и тут же запускаешь это вычисление через хак, но за такое надо бить по рукам).

Что касается замыканий, то тема не раскрыта. Наиболее ярко разница проявляется при обходе цикла.

З.Ы. Примеры приводить мне лень.

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

Это любители ТСО пишут, забывшие сделать аккумулятор строгим.

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

В ленивых языках используют продуктивную рекурсию

А что это такое? В гугле не нашёл.

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

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

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

короче получаем мутабельный скоп, на любом уровне вложенности замыканий.

Он и так в Scheme мутабельный на любом уровне замыканий. И никаких «хаков».

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

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

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

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

Из какого любого? Ты просто изменяешь глобальную переменную a в глобальном же скопе, как и раньше.

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

Например вот так:

(define a 1)
(define tst (let((a (lambda() a))) (lambda(x) (+ (a) x))))
(define tst2 (let() (lambda(x) (if (eq? x 'a) (set! a 10)) (write (tst 1)))))
(write (tst 1)) ; 
(tst2 'a)
(write (tst 1)) ; 
2
11
11
В обычной семантике Scheme Вы не можете залезть во внутренний скоп tst из tst2. А здесь, правда через глобальную область, уже можете. Может можно это все сделать изящней даже, не через глобальный, а отдельный скоп, самописный, но по факту, мы даже при таком грубом подходе ломаем семантику. Мы фактически, имеем здесь first class окружения.

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

А здесь, правда через глобальную область, уже можете.

Это не внутренний скоп, а глобальный. Твой код абсолютно идентичен:

(define a 1)
(define tst (lambda(x) (+ a x)))
(define tst2 (lambda(x) (if (eq? x 'a) (set! a 10)) (write (tst 1))))

Если надо иметь общий скоп для tst и tst2, то его можно явно описать

(define-values (tst tst2)
  (let ((a 1))
    (values
      (lambda(x) (+ a x)))
      (lambda(x) (if (eq? x 'a) (set! a 10)) (write (tst 1))))))

И да, определить в другом месте ещё одну функцию, которая могла бы менять этот скоп (переменную a) ты уже не сможешь.

monk ★★★★★ ()
Последнее исправление: monk (всего исправлений: 2)
Ответ на: комментарий от monk

Я не совсем об этом говорю, но х с ней. Слишком сложно для меня объяснить, что я имею в виду.

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

Я пытаюсь сказать вот о чем примерно. Мы смотрим на функцию, как на черный ящик. О чистой функции мы говорим как о черном ящике, который при любых одинаковых аргументах выводит одинаковый результат. В этом примере демонстрируется, что несмотря на синтаксис языка, который как бы изолирует окружения функций, мы не можем быть уверены в чистоте. Эта утечка происходит благодаря тому, что мы имеем для всех функций «общую точку соприкосновения» - глобальную область или IO. Поэтому, на мой взгляд, бессмысленно вводить эти искуственные ограничения. Замыкания мы и без этого можем эмулировать. Концептуально разницы нет. Весь вопрос сводится всего лишь к синтаксическим соглашениям влюбом случае. Я подозреваю, что сама концепция замыканий ошибочна. Профита от них я пока не вижу никакого. Вплане безопасности мы выигрываем, однако в плане выразаительности при этом теряем. И кроме того, у меня стойкое ощущение, что именно концепция замыканий породила такой уродливый подход к ООП, как современное класс-ориентированное программирование. Неимоверная сложность, при относительной слабости языков. Но это ИМХО, я пока только вникаю. Просто интуитивное ощущение.

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

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

В Haskell, если в типе функции нет IO, то можем. Так как результат всё равно с большой долей вероятности закэшируется (ленивость ведь).

Аналог замыкания в Haskell всегда существует внутри какой-нибудь монады. И по факту просто получает состояние в параметр и возвращает новое состояние.

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

Ну напиши более выразительный вариант такого

(define (incr x)
  (let ((a x)) (lambda () (set! x (add1 x)) x)))

(define incr1 (incr 0))
(define incr2 (incr 5))

> (incr1)
1
> (incr1)
2
> (incr2)
6
> (incr1)
3

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

именно концепция замыканий породила такой уродливый подход к ООП, как современное класс-ориентированное программирование

В JS прототипо-ориентированное... и замыкания есть.

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

В обычной семантике Scheme Вы не можете залезть во внутренний скоп tst из tst2. А здесь, правда через глобальную область, уже можете.

Потому что a в tst и tst2 — не их внутренний скоуп, а глобальный.

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

«В языке, который никак не гарантирует чистоту, мы не можем быть уверены в чистоте» — вот твоё «откровение».

Пойди расскажи сишникам, что мы не можем быть уверены в чистоте функций в Си.

Я подозреваю, что сама концепция замыканий ошибочна.

Что же в ней ошибочного? Каков по-твоему должен быть результат

(define (make-counter (x 0))
  (lambda () (set! x (+ x 1)) x))

(define count (make-counter))
(count) ; => 1
(count) ; => 2

? «Error: unbound varaible x»?

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

И кроме того, у меня стойкое ощущение

Это к доктору.

именно концепция замыканий породила такой уродливый подход к ООП, как современное класс-ориентированное программирование.

Simula была построена на замыканиях?

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

Что же в ней ошибочного? Каков по-твоему должен быть результат

Есть разные альтернативы.

1. Значение фиксируется на момент формирования лямбды: тогда (count) ;=> 1 всегда

2. Как static в Си. Тогда

(define count (make-counter))
(count) ; => 1
(count) ; => 2
(define count2 (make-counter))
(count2) ; => 1
(count) ; => 2

3. Как special в CL тогда

(define count (make-counter))
(count) ; => 1
(count) ; => 2
(let ((x 1)) (count)) ;=> 2
(count) ; => 3

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