LINUX.ORG.RU

Си и замыкания

 , ,


1

2

Я тут как-то услышал, что Си поддерживает локальные функции. Сначала не верил, потому, что в плюсах это не работает, однако проверил и был удивлен. Но потом попытался замыкание, и оно тоже заработало. Это конечно хорошо, но почему это работает? Разве локальные функции(в примере «a» и «b») не создаются в стеке и не должны разрушаться по выходу из глобальной функции(в примере «foo»)?

#include <stdio.h>

int (*foo(int key))(int){
	int a(int x){
		return x+1;
	}
	int b(int x){
		return x-1;
	}
	if (key == 0)
		return a;
	else
		return b;
}

int main(){
	int (* f1)(int) = foo(0);
	int (* f2)(int) = foo(1);
	printf("%d %d\n",f1(5),f2(5));
	return 0;
}

★★★★★
Ответ на: комментарий от ilammy
main:
	movl	$6, %eax
	ret

:)

Ну и идея требующая демонстрации в том, что можно принимать/возвращать объект/замыкание — он захватил контекст, у него есть состояние, он first-class, он callable.

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

А что за тред-то?

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

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

Объекты это же фактически и есть замыкания, только с возможностью иметь больше одной функции завязанных на данные

Эм...

(define (make-counter init-value)
  (define value init-value)
  (define (inc)
    (set! value (+ value 1)))
  (define (get)
    value)
  (values inc get))

Несколько функций, завязанных на данные.

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

это больше похоже на ООП, которое в C++, только с упоротым синтаксисом. Типа как LISP на C++.

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

Несколько замыканий возвращённых в кортеже? Ок.

Да, как вариант. Тут, правда, остаётся некоторая разница с некоторыми объектными системами, в частности в которых можно менять определение класса «на лету», и объекты класса должны «подцепить» эти изменения так же «на лету», типа той же CLOS.

Плюс отсутствие приписанного конкретного типа, но, например же, в Ocaml'е класс не является типом и тип определяется как раз по интерфейсу объекта.

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

Кайфолом тред читать, может уже разжевали.. Гцц для каждого такого «замыкания» строит на стеке трамплин, который соединяет фрейм «замыкания» с фреймом содержащей функции и передает его при ее вызове. Такую функцию нельзя вернуть из содержащей, т.к. при ее вызове она обратится к трамплину, который уже невалиден, т.к. стек размотался. То, что ты здесь видишь — сраное UB.

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

Я там говорил, что замыкание это всегда одна функция:

(funcall (lambda (a) (lambda (b) (+ a b))) 1)
; #<CLOSURE (LAMBDA (B)) {10048430CB}>

Кортеж замыканий это уже не замыкание :)

Тогда как с объектом связано несколько (>= 0) функций:

Class obj(...); // obj is a "closure" now
obj(...); // so its callable, nevertheless has state and behaves like a first-class value
// ^ sugar for:
obj.operator()(...);
// or:
std::mem_fun_ref(&Class::operator())(obj, ...);
//                               ^ *this
// another method:
obj.foo(...);
std::mem_fun_ref(&Class::foo)(obj, ...);
// ...

Но если считать, что первое более примитивно, то, конечно, можно и объекты на замыканиях сделать (как в SICP).

Плюс отсутствие приписанного конкретного типа

Динамический тип (который вычисляется глубоким type-of) должен быть чем-то вроде (list closure), то есть не тип конкретных данных на которых работает интерфейс, и не тип самого интерфейса, так как всё смешано.

в Ocaml'е класс не является типом и тип определяется как раз по интерфейсу объекта.

Слышал, что сами камльщики не особо жалуют тамошнее ООП.

Вот в хаскеле если t :: * (тип) и I :: * -> Constraint (интерфейс), то

f :: I t => t -> ...

при вызове

f (obj :: SomeType)

работает как

f {instanceOf obj :: I SomeType} (obj :: SomeType)

с разрешением во время компиляции или выполнения. То есть метод это суть

f :: I t -> t -> ...

где на место I t передаётся реализация someInstance :: I SomeType (которая единственна в случае классов типов, хотя при явной передаче может быть не единственна), на место t — obj :: SomeType, I представляет собой data I t = MkI { _i1 :: sig...; ... }, someInstance :: I SomeType — someInstance = MkI { _i1 = impl...; ... }.

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

Слышал, что сами камльщики не особо жалуют тамошнее ООП.

Просто в отличии от хаскеля в камле ООП реализовано совсем не функционально.

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

Кортеж замыканий это уже не замыкание :)

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

Слышал, что сами камльщики не особо жалуют тамошнее ООП.

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

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

С фига ли?

http://en.wikipedia.org/wiki/Closure_(computer_programming)

closure (also lexical closure or function closure) is a function or reference to a function together with a referencing environment—a table storing a reference to each of the non-local variables (also called free variables or upvalues) of that function.[1] A closure—unlike a plain function pointer—allows a function to access those non-local variables even when invoked outside its immediate lexical scope.

А именно:

a

Какая разница, сколько функций замкнулись на один контекст?

man plural form :)

З.Ы. Я так понимаю, если f и g — замыкания, то ты хочешь называть пару (f, g) замыканием? Я б сказал, что это пара замыканий :)

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

Тут я немой/глухой, так как ООП окамла не видел, но в хаскеле тоже «класс [типов] не является типом» и есть возможность определять тип по интерфейсу, то есть I t => t уже тип (в т.ч. с возможностью делать рантайм диспетчеризацию).

А для простых замыканий/объектов подтипирование и не нужно, не о нём речь (сайд-эффекты даром что «явные» — всё равно замыкания/объекты в хаскеле могут иметь мутабельное состояние).

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

Я так понимаю, если f и g — замыкания, то ты хочешь называть пару (f, g) замыканием?

Нет, я просто напомнил, что можно иметь несколько функций, замкнутых на один контекст, как методы в этих ваших классах. =)

Тут я немой/глухой, так как ООП окамла не видел

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

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

http://langref.org/ocaml/oop — прямо типичное ООП c class/new/abstract/interface, только implements автоматический (точнее, по o :> serializable).

З.Ы. ну а я говорил, что технически замыкание и объект отличаются именно количеством функций — одна vs куча (в vtbl или «так»).

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

То что values это особая вещь я знаю. Правда не знаю зачем они нужны, когда есть все остальные product-like структуры в т.ч. с возвращением по значению с RVO (а то SBCL эти values чего-то там пытается оптимизировать).

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

Вот тут ещё про различия — http://www.cas.mcmaster.ca/~kahl/CAS706/2010/Pres/OCamlObjects_Presentation.pdf.

Если посчитать этот object type <...> за trait (интерфейс), то на Scala с некоторыми дополнительными росписями этого traits и extends можно сделать что-то вроде

trait CellLike {
  def get: Int
  def set(xn: Int)
}

def test(c: CellLike) {
  c.set(5)
  println(c.get)
}

class Cell extends CellLike {
  var x: Int = 0
  def get: Int = x
  def set(xn: Int) { x = xn }
}

def t {

  object cell extends CellLike {
    var x: Int = 0
    def get: Int = x - 1
    def set(xn: Int) { x = xn + 1 }
  }

  test(new Cell) // : Cell, <: CellLike
  test(cell)     // : cell.type, <: CellLike

}

Ocaml просто его сам придумывает и делает всем _:_ к нему на основании наличия имён с сигнатурами (плюс подтипирование, все дела).

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