LINUX.ORG.RU

Glasgow Haskell Compiler 7.6.1

 , ,


1

5

Вышла новая версия GHC 7.6.1 — одного из самых мощных и развитых на сегодняшний день компиляторов функционального языка программирования Haskell, который разрабатывается свободной рабочей группой из многочисленных разработчиков, собранных по всему миру и координируемых из лаборатории университета Глазго.

Основные изменения:

  • polymorphic kinds и data promotion полностью реализованы, Kind polymorphism;
  • реализована поддержка платформы Windows 64bit;
  • флаг -fdefer-type-error позволяет отложить обнаружение ошибок типов в рантайме, Deferring type errors to runtime;
  • опцию RTS -N возможно изменять в рантайме с помощью Control.Concurrent.setNumCapabilities, RTS options for SMP parallelism;
  • новое расширение ExplicitNamespaces, которое разрешает ограничивать экспорт типов с ключевым словом type;
  • изменено поведение расширения TypeOperator;
  • добавлена возможность вывести экземпляры Generic1 автоматически, Generic programming;
  • новое соглашение о вызовах FFI capi, включаемое через расширение CAPI;
  • новая прагма CTYPE, используемая с CAPI;
  • новое расширение InstanceSigs, которое разрешает типам сигнатур быть специфицированными в instance;
  • GHC поддерживает числовые и строковые (включаемые через DataKinds) и символьные литералы, Promoted Literals;
  • тип Any может быть использован как аргумент для foreign prim functions;
  • ключевое слово mdo вновь введено. Оно может быть использовано для создания do-выражения с рекурсивными привязками. Поведение ключевого слова rec было изменено;
  • новая синтаксическая конструкция (включаемая через расширение LambdaCase) для создания анонимной функции из case-выражения, Lamda-case;
  • исправление ошибок и улучшение производительности.

>>> Подробности

★★★★★

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

Что вы имеете в виду под условностью аргумента и возвращаемого значения? И как на это влияют указатели?

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

Года четыре назад как-то заметил, что один риэлтерский сайт был явно написан на каком-то лиспе. По крайней мере все URLы заканчивались на ".lisp" :)

Жаль, ссылку не сохранил.

Сайт был где-то на пост-советском пространстве.

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

Вы понимаете, что при работе с указателями, аргумент и возвращаемое значение вещи сугубо условные?

Сразу не обратил внимания на «ret» - так можно считать перегрузкой по значениям, согласен. Но это предполагает писать, например

A a;
B b;
C c;
baz(&c, d);
bar(&b, c);
foo(&a, b);

вместо

foo(bar(baz(d)));

или

foo $ bar $ baz d

и

A a;
B b;
C c;
if (!we_good(baz(&c, d))) {
    throw bad;
} else if (!we_good(bar(&b, c))) {
    throw bad;
} else if (!we_good(foo(&a, b))) {
    throw bad;
}

вместо

foo =<< bar =<< baz =<< d
quasimoto ★★★★
()
Ответ на: комментарий от rtvd

Он имел ввиду что-то вроде read(2), memcpy(3) и т.п. - там результат передаётся через буфер переданный указателем в аргументе.

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

Сколько мейнстримных языков умеют полиморфизм по возвращаемому значению? А полиморфизм по кайнду отличному от *?

С каких это пор наличие второй совершенно перпендикулярной фичи заменяет отсутствие первой фичи?

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

вот полиморфизм по kind отличному от * был ну просто очень нужен"

Седня попытался спеку на эрланге объявить вида:

-spec flatten/2 :: (atom(), [M[A]]) -> [A].

расстроился.

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

Сразу не обратил внимания на «ret»

Это не имеет значения. Как раз «аргументы по ссылке» - это условность - по факту это передача адреса данных по которой можно их модифицировать - но по факту это передача именно адреса - и сам этот адрес поменять нельзя. С таким же успехом можно через файлы передавать задавая имя файла и говорить что в языке есть «возврат аргументов по файлу».

[code]

#include<stdio.h>

void f(int * x) {
*x = 1;
}

int main(char* argc, int argv) {
int x = 2;
printf(«%d, addr=%d\n», x, &x);
f(&x);
printf(«%d, addr=%d\n», x, &x);
return 0;
}

2, addr=148711316
1, addr=148711316
[/code]

Значение аргумента, а именно адрес - не поменялся - его модифицировать нельзя.

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

С таким же успехом можно через файлы передавать задавая имя файла

Формально — да. Но тогда у вас вообще никакого полиморфизма средствами языка нету. А я привел пример, когда есть.

В рамках существующих архитектур (а не, скажем, аритектуры «лисп-машины» :-), 'return value;' это по сути «синтаксический сахар» к обращению к неким

register unsigned long ABI_return_regitster; // EAX
и
register unsigned long ABI_stack_pointer; // SP

То есть, если принять, что возвращаемое значение помещается в регистр, то

void return_val(void) { ABI_return_register = 1; }

есть подноготная вот этого:

int return_val(void) { return 1; }

Собственно, передача аргументов это тоже «синтаксический сахар», и на С можно успешно писать программы, которые будут использовать лишь void func(void) «функции». Но тогда мы по сути урезали язык до ассемблера, и ни о каких «фишечках» языка вроде полиморфизма речи уже быть не может.

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

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

Как раз «аргументы по ссылке» - это условность - по факту это передача адреса данных по которой можно их модифицировать

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

и сам этот адрес поменять нельзя

В смысле, x = &some_var в f никак не повлияет на x в main? Ну да, это же просто адрес какого-то места на стеке, в куче, data, bss по которому лежат данные которые можно читать и/или писать. Его можно изменить только локально как значение адреса которое попало на стек именно f.

А насчёт перегрузки по результату в C++ - cin так и работает. Аналогично read хаскеля.

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

а меня все-таки интересует именно CL

InspireData

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

там статья есть - интересно, берёшь и читаешь.

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

раз уж об этом зашёл разговор:

https://gist.github.com/3501333

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

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

Очень повышает чсв, а это помогает знакомиться с женщинами (они, чертовки, чувствуют неуверенность).

Какашкель уже давно попал в руки школоты и опопсел. Я вот знаю язык - документации нет, сообщества нет, а вместо сайта - README на ftp. Все чиксы мои!

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

Во-первых, так:

doMagick :: (Default a, Typeable a) => [a]
doMagick = inner undefined where
  inner :: (Default b, Typeable b) => [b] -> [b]
  inner s = trace (show $ typeOf s) [def]

хотя это то же самое. a и b независимы, но тип результата inner совпадает с типом результата doMagick, то есть b ~ a, кроме того тип аргумента inner совпадает с типом его результата, так что в typeOf попадает аргумент именно нужного типа [a]:

 undefined
    /\
   /  \ (Default a, Typeable a) => [a] (1)
  /    \
 /    typeOf
 \       \
  \       \ TypeRep
   \       \
    \     show
     \       \    /
      \ String\  / (Default b, Typeable b) => [b] (2)
       \       \/ 
        \     trace
         \    /
          \  / (Default b, Typeable b) => [b] (3)
           \/

Дальше хочется сделать

doMagick :: (Default a, Typeable a) => [a]
doMagick = trace (show $ typeOf undefined) [def]

но не получается, так как это фактически следующее:

undefined
    \
     \ Typeable b => [b] (1)
      \
     typeOf
        \
 TypeRep \
          \
         show
            \     /
      String \   / (Default a, Typeable a) => [a] (2)
              \ /
             trace
               |
               | (Default a, Typeable a) => [a] (3)
               |

то что типы (2) и (3) всегда совпадают утверждено в зависимости в типе trace, но связать типы (1) и (3) у тайпчекера никак не получится, так как тут композиция через два неполиморфных типа TypeRep и String - нужно делать тот inner и вручную передавать свидетеля типа.

Ещё можно связать (1) и (3) положив doMagick в класс, указав тип undefined и включив ScopedTypeVariables:

class (Default a, Typeable a) => Magic a where
  doMagick :: [a]
  doMagick = trace (show $ typeOf (undefined :: [a])) [def]

instance Magic Int
instance Magic Char
quasimoto ★★★★
()

Пришёл сентябрь, и на ЛОРе появились новости о ФЯП :)

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

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

Как это нету да полно:

struct IntFileRef {}
struct CharPFileRef{}

void f(IntFileRef file) {....}
void f(StringFileRef file) {....}

Ровно то же самое.

это по сути «синтаксический сахар» к обращению к неким

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

То есть параметры этой фукнции:

void func(int arg, int *ret);

int аргумент, int* - адрес. А то что это return - это только интерпретация в наших мозгах а не с точки зрения типизации. На коленке такого типа возвраты по аргументам как я показал на файлах делаются нараздва. От этого они не становятся полиморфизмом по результату. На плюсах это нагляднее что-то типа

template <typename T, string F>
class FilePointer {
F file;
public:
FilePointer &operator=(T value); //write to file
T operator*(); //read from file
}

будет делаться ровно то же самое. В твоем случае это что-то типа MemPointer - который работает с памятью - язык засахарил его в поддерживаемую структуру на уровне языка - но факт остался фактом - это _не_ return. Передаваемый параметр не меняется - изменение по адресу - это side effect.

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

В смысле, x = &some_var в f никак не повлияет на x в main?

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

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

ну в реальном примере, там не typeOf было а константа в зависимости от типа, (всегда пытаюсь выделить подзадачу, а появляются отвлекающие внимание детали).

за вариант с ScopedTypeVariables спасибо.

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

будет делаться ровно то же самое. В твоем случае это что-то типа MemPointer

Окей, обернув путь до файла в struct (то есть придав файлу тип), вы сделаете полиморфизм. Согласен, отлично.

факт остался фактом - это _не_ return

Дайте определение return'a. Если оно ходит как return, крякает как return, то я таки склонен думать, что это return. ;-)

Как именно функция передаёт возвращаемое значение, и как потом к этому значению вы обращаетесь, не играет роли. Вы просто не можете смириться, что значение вдруг оказалось справа, а не слева от функции. Мол в языке нет синтаксического сахара для «полиморфизма по возвращаемому значению». Ну и что? И без сахара этот же функционал реализуется и обладает ровно такими же свойствами.

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

Дайте определение return'a.

Возвращает результат выполнения функции.

Если оно ходит как return, крякает как return, то я таки склонен думать, что это return.

Но оно так _не_ делает. Значение конструкции &x - это _адрес_. Туда передается адрес. И после выполнения там же (&x) находится тот же адрес. Другой адрес не вернулся - то есть значение &x не поменялось.

Тип у нас относится к переменной x. Обсуждаемый полиморфизм - бывает относительно типа. Конструкция &x «создает» экземпляр типа addr<int> и этот экземпляр передается в функцию. Константно, не изменяемо. Просто Си скрыл этот момент. Но подобная же хрень может быть мимикрирована в любом статическом языке с мутабельными данными кучей способов.

То есть по факту оно делат «запиши мне по этому адресу я потом оттуда прочитаю».

Это как говорить что в жабе есть ретурн в параметрах потому что Collections.sort(list) сортирует список по месту, а не делает новый сортированый.

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

То есть по факту оно делат «запиши мне по этому адресу я потом оттуда прочитаю».

Шок, но return делает тоже самое, внутри. :-) Пишет результат по адресу/в регистр, который определен ABI. А вызывающий потом от туда читает/оперирует.

Так и хочется спросить, как в анекдоте: вам шашечки, или ехать? :-)

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

В любом случае, я вас понял, но таки я вижу ваши аргументы как буквоедство. Настаиваю на своем — принципиальной разницы нету, все дело в легком сахаре. Что не отменяет того, что С++ ужасен, и заставляет делать всё это через «два притопа, три прихлопа».

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

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

Вы можете этим значением инициализировать новую переменную?

Мол в языке нет синтаксического сахара для «полиморфизма по возвращаемому значению». Ну и что?

Суть полиморфизма по значению в правильном выборе полиморфной функции, а не чтобы было(псевдокод):

f[T](x:T, p: ()-> T ):(T,T) = (x, p())

poly():String = «str»
poly(): Int = 1

f(1, poly) -> (1,1)

f(«s», poly) -> («s», «str»)

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

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

Эдак можно сказать что в эрланге есть параметрический полиморфизм потому что можно сделать то же самое методом:

sum(int, A, B) -> A + B;
sum(string, A, B) -> A ++ B;
sum(char, A, B) -> [A, B].

Хага- параметрический полиморфизм

sum(int, 1,2).
sum(string, «aa»,«bb»).

Чем это отличается от скального

sum[String](«aaa», «Bbb»).
sum[Int](1, 2).

та же фигня:)

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

Даже проще: возьмем две функции:

void f1(int i, int * j) {
printf(«%d», i + *j);
}

void f2(int i, int *j) {
*j = i;
}

их типы:

f1 :: int -> Ref int -> void
f2 :: int -> Ref int -> void

Ну и внимание вопрос - где тут видно что это полиморфный return? А нигде - потому что его нет - это просто сайд эффект функции. Это не возворащаемое значение.

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

где тут видно что это полиморфный return?

В первом случае должно быть const, тогда будет видно. Если бы в C/C++ по умолчанию было const, а для сайд эффекта требовалось добавить какой-нибудь mutable, то было бы вообще наглядно (с предупреждениями вроде «вот тут переменная mutable, но её значение нигде не меняется» так где это можно вывести).

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

С таким же успехом там может лежатьне адрес а имя файла

Файла может не оказаться. Или вызывающий должен создать файл (имя файла ~ как имя переменной), передать его имя вызываемому, тот должен написать туда данные, которые вызывающий должен прочитать. Но тут большая разница. return и изменение значения по ссылке отличаются примерно так:

callee:
    eax <- val
    ret

caller:
    call callee
callee:
    (rdi) <- val
    ret

caller:
    rdi <- offset(rsp)
    call callee

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

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

В первом случае должно быть const, тогда будет видно

Не будет - мутирвать ее все равно не обязательно.

добавить какой-нибудь mutable

С exhaustive checking чтобы параметр оязательно мутировался по всем ветка имполнения иначе ошибка - есть ветка в которой параметр не мутировался, нука мутируйте его? :))

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

мутирвать ее все равно не обязательно

return тоже делать не обязательно (хотя -Wreturn-type решает эту проблему).

нука мутируйте его?

Ну я сказал «так где это можно вывести», то есть понимаю что есть проблема с тем чтобы вывести действительно ли что-то mutable (if (random() > 42) { *x = val; }). С const проще - можно выкинуть всякое поползновение использовать мутатор в каком бы месте control flow он не появился. Этого достаточно. По крайней мере наличие мутатора это признак вероятного изменения (но это только необходимо).

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

Файла может не оказаться

Там наверху C++ интерфейс для такого дела. Файл создает класс Pointera которій собственно ответственен за хранение и возврат значения.

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

Ненене.

class FilePointer<T> {
filename = «/tmp/ » + rnd().
operator= (T) = запись в filename
T operator*() = чтение из filename
}

Ведь по сути в C происходит то же самое - типа

class *<T> {
address = allocate(sizeof(T))
operator= (T) = запись в address
T operator*() = чтение из address
}

то есть почти ничем.

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

Так и в эрланге при полном отсутствии типов вышеуказанный
пример эффективно полиморфный выбор функции по типу. Но это ж не значит что это такой 'параметрический полиморфизм'. Хотя в голове заккрутилась идейка написать парстрансформ который по спеке будет генерировать guard чекающий типы в рантайме:)

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

return тоже делать не обязательно

return писать не обязательно. Это не значит же что его не происходит?

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

чтобы еще более понятно вот что в действительности происходит:

int x = 2;
long addr = (long)&x;
f((int*)addr);

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

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

Это не значит же что его не происходит?

return != ret (как инструкция). Это значит, что eax (или подставить нужное) останется с мусором или неизменным, точно так же как останется неизменным или с мусором адрес переданный аргументом и не мутированный.

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

return != ret (как инструкция). Это значит, что eax (или подставить нужное) останется с мусором или неизменным, точно так же как останется неизменным или с мусором адрес переданный аргументом и не мутированный.

Мне кажется, Вы смешиваете в кучу несколько очень разных уровней абстракции. Так можно прийти к очень интересным выводам. :)

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

передаваемое в функцию значение (адрес) никакого отношения к переменной в которую «возвращается» результат не имеет

Кроме того что это адрес этой самой переменной, ни много ни мало? Ну ок :)

Можно передавать ссылкой:

#define ret_type(T) T & __return
#define ret(V) do { __return = V; return; } while (0)
#define call_stmt(T, N, F, ...) T /* infered? */ N /* gensymed? */ ; F(N __VA_ARGS__);

(безобразие, конечно) теперь представим что есть что-то вроде { T N; F(N __VA_ARGS__); return_from_block N; } и мы смоделируем обычный return спрятав лишнюю промежуточную переменную.

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

Мне кажется, Вы смешиваете в кучу несколько очень разных уровней абстракции.

Мы - мы, или мы - я? :)

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

Так можно прийти к очень интересным выводам. :)

Параметры по ссылке для скалы:)

class *[T](var value: T) {
	def *=(t: T) {
		value = t
	}

	def *() = value

	override def toString = "ref to " + value
}

object * {
	def &[T](x: T) = new *(x)
}

object RefTest {
	import *._

	def f(v:Int, x: *[Int]) {
		x*=v
	}

	def main(args: Array[String]) {
		val y = &(1)
		println(y*)
		println(y)
		f(3, y)
		println(y*)
		println(y)
	}
}

1
ref to 1
3
ref to 3
r ★★★★★
()
Ответ на: комментарий от hexdump01010101

Вы понимаете, что при работе с указателями, аргумент и возвращаемое значение вещи сугубо условные? Вы можете вообще исключить «return value;» из языка, и всё равно иметь полноценные понятия «функция», «аргументы» и «возвращаемое значение».

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

Т.е. чисто практически Вы можете так делать. И даже можно сделать полиморфизм по типу указателя. Но с формальной точки зрения это уже не будет считаться полиморфизмом по возвращаемому значению.

Более того, чисто формально Вам прийдется пользоваться чем-то, что не является функцией. У функции не может быть побочных эффектов, а запись по адресу - побочный эффект. Так что проблема, которую Вы обсуждаете с r, можно описать так: "- Ёжики не летают! - Летают, если этот ёжик - ласточка.".

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

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

Каким образом он останется с мусором? Это просто адрес. Что там лежало то и будет лежать.

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

Каким образом он останется с мусором?

Если это локальная неинициализированная переменная на стеке (причём, warningа не будет так как адрес её куда-то отдаётся, но не понятно используется для записи или нет).

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

Кроме того что это адрес этой самой переменной, ни много ни мало? Ну ок :)

Это адрес _какой-то_ переменной.

       int a[5] = {1,2,3,4,5};
       int * y = &a[2];
       int * z = y;
       f(z);

ну и кто тут папа? Переменнной в которую возвращается значение вообще нет.

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

guess what

class Monad m => Ref r m where
  ref :: a -> m (r a)
  (*) :: r a -> m a
  (*=) :: r a -> a -> m ()
  (*$) :: r a -> (a -> a) -> m ()
  r *$ f = (r *) >>= (*=) r . f

instance Ref IORef IO where
  ref = newIORef
  (*) = readIORef
  (*=) = writeIORef

instance Typeable t => Show (IORef t) where
  show _ = "IORef " ++ show (typeOf (undefined :: t))

f r v = r *= v

main = do
  y :: IORef Int <- ref 10
  print y
  print =<< (y *)
  f y 25
  print =<< (y *)
  y *$ (+ 35)
  print =<< (y *)

-- IORef Int
-- 10
-- 25
-- 60
quasimoto ★★★★
()
Ответ на: комментарий от r

То есть оно не знает _куда_ оно пишет значение? А return знает куда он возвращает значение? И нет ли там такой же цепочки указателей (...; *z = foo())?

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

Впрочем, return хотя бы возвращает что-то чистое (пусть даже указатель на кучу), а писатель по адресу может запросто затереть что-то не то.

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

То есть оно не знает _куда_ оно пишет значение?

Оно пишет по адресу. Туда _число_ можно передать. Интерпретация этого действия как «возврат через аргумент» только у нас в голове.

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