LINUX.ORG.RU
ФорумTalks

Зачем нужна обработка списков в искусственном интеллекте?

 ,


2

6

По легенде, Lisp придуман для решения задач искусственного интеллекта. Зачем для этого надо было придумывать такой довольно необычный язык - язык обработки списков? Чем например Fortran не подошёл бы?

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

Метод монте-карло. Долго, но приблизительно вычислить можно.

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

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

Такой же бред несёшь. Феерический.

всегда проще что-то (для тебя) новое и непонятное объявить «бредом», и продолжать жить в своём уютном, придуманном мирке.

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

Ты поставил условие не пользоваться гуглом =) Я не спец по численным методам, тут быстрее бы сообразил Anon

Но ты мне лучше скажи, неужели методом монте-карло невозможно вычислить кубический корень?

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

Разупорись. factorial — это функция, а (factorial 2) — это вызов функции. Или как в твоей любимой сишечке, factorial — указатель на функцию, а factorial(2) — вызов функции. В твоём примере функция не принимает параметром другую функцию, а просто вызывает себя.

К списку языков, которые ты не знаешь, прибавился lisp.

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

Так скажи мне, где здесь умножения числа на функцию?

в слове «рекурсивно». Ты подразумеваешь то, что аргументы — сами по себе списки операторов, а не значения. В сишечке такого нет, там есть только список операторов, единый и нерушимый. Т.е. последовательность действий жёстко задана, и называется кодом. Есть также аргументы, какие-бы не был аргументы, над ними выполняются ОДНИ И ТЕ ЖЕ действия. И что-бы действия изменить, необходимо собрать программу заново. Для LISP'а такой нужды нет, там достаточно просто скормить другие аргументы.

А говорить, что в лиспе можно умножить число на функцию, равносильно утверждению, в следующем примере число i умножается на функцию rand

это синтаксический сахар. Полностью это так выглядит:

temp = rand();
int foo = i * temp;
причём rand() эквивалентна макросу, который ВСТРАИВАЕТСЯ в код. Т.е. мы имеем ОДИН список операторов. И рекурсивно он НЕ обрабатывается. Rand() компилируется всего один раз и навсегда. Ни о какой рекурсии речи нет. В LISP'овом коде эта твоя rand() будет «компилироваться» каждый раз заново, для каждого вызова, если они будут.

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

А в лиспе с ними стало _удобно_ работать.

Именно так. Но вот в сишечке — ничем не лучше, чем в машинных кодах.

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

Lisp в теории самый могучий, универсальный и удобный ЯП
в реальном мире им никто не пользуется

Python ... привнёс только новую грусть и проблемы

Ещё один наркоман.

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

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

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

Ты правда не понимаешь, что в случае с лиспом ты тоже умножаешь на ЗНАЧЕНИЕ функции, или просто притворяешься? Я просто к тому, что такой тупости я даже от тебя не ожидал.

тупишь тут ты, а не я. LISP'е у тебя нет не только значения, но и самой функции тоже нет. Надо её сначала сделать в данном контексте. Т.е. когда ты считаешь 3!, ты сначала СОЗДАЁШЬ выражения

3!

3*2!

3*2*1!

3*2*1

А потом созданное выражение вычисляешь. Сишная реализация работает иначе: ты сначала считаешь 3!, но для этого тебе надо посчитать 2!, а для этого 1!, вот когда ты посчитаешь 1!, ты возвращаешься из программы, и умножаешь результат на 2, и возвращаешься. И потом умножаешь на 3.

Строго говоря, тут тоже есть СПИСОК ФУНКЦИЙ, но он неявно в стеке хранится. В самой сишечке его вроде как и «нет».

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

int factorial(int x)
{
  int y = 2;
  while(x > 2)
    y *= x--;
  return y;
}
Ессно, для замены произведения на что-то другое придётся всю функцию переписывать. IRL проще другую функцию написать.

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

Што? В какой парадигме? В голых сях проблема только в синтаксисе.

у меня нет проблем с синтаксисом. IRL я typdef юзаю, потому с синтаксисом как раз всё просто. Проблема в том, что указатель на функцию — не число. Т.е. все операторы для такого указателя не имеют смысла. Имеет смысл только оператор разименования и вызова. (ну и оператор копирования конечно). И проблема в том, что этого никак не заметно по вызову функции/переменной.

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

в слове «рекурсивно»

Правила (упрощённые) вычисления значения вызова функции в C:

вычислим аргументы функций в произвольном порядке:

  1. если аргумент — константа или переменная, то используется её;
  2. если аргумент — вызов функции, то РЕКУРСИВНО вычислим значение этого вызова по данному алгоритму;
  • вызовем функцию к вычисленным аргументам.

Лисп от С, конечно, отличается довольно сильно, но в этом аспекте они работают одинаково, как бы тебе не хотелось обратного.

А ставить равенство между «функцией» и «вызовом функции» (и приплетать к этим понятиям функции высшего порядка, лол), по-моему, может только наркоман.

Ты подразумеваешь то, что аргументы — сами по себе списки операторов, а не значения. В сишечке такого нет, там есть только список операторов, единый и нерушимый. Т.е. последовательность действий жёстко задана, и называется кодом. Есть также аргументы, какие-бы не был аргументы, над ними выполняются ОДНИ И ТЕ ЖЕ действия. И что-бы действия изменить, необходимо собрать программу заново. Для LISP'а такой нужды нет, там достаточно просто скормить другие аргументы.

О_О

это синтаксический сахар

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

%1 = load i32 %n
%2 = sub i32 %1, 1
%3 = call i32 @factorial(i32 %2)
%4 = mul i32 %1, %3
ret i32 %4

Да что гадать, посмотри сам:

(disassemble (lambda (a) (* a (random 4))))

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

тупишь тут ты, а не я. LISP'е у тебя нет не только значения, но и самой функции тоже нет. Надо её сначала сделать в данном контексте. Т.е. когда ты считаешь 3!, ты сначала СОЗДАЁШЬ выражения

3!

3*2!

3*2*1!

3*2*1

Big fat lie (про правила вычисления я тебе уже написал выше, ты либо их не увидел, либо просто слишком туп, чтобы их осмыслить).

...

Дальше опять пошли мокрые фантазии.

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

Ты поставил условие не пользоваться гуглом

дело не в гугле. Метод Монте-Карло, это когда много раз тыкаешь(рандомно), и собираешь статистику. Регулярные методы — это когда тыкаешь в определённом порядке, и тоже собираешь статистику.

Но ты мне лучше скажи, неужели методом монте-карло невозможно вычислить кубический корень?

можно наверное. Микроскопом-же можно гвоздь забить? Тут тот же случай.

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

Метод Монте-Карло, это когда много раз тыкаешь(рандомно)

Тут я маху дал, да. В терминологии. То, что я имел ввиду, пожалуй, ближе к Gradient Descent, что, правда, тоже не самый быстрый способ. Но быстрее.

feofan ★★★★★
()
Ответ на: комментарий от i-rinat

Разупорись. factorial — это функция, а (factorial 2) — это вызов функции.

это ты разупорись и прочитай, КАК это работает в LISP'е — (factorial 2) это не вызов функции, а 2*1. Т.е. сначала мы создаём выражение, а потом, то что получилось, мы вычисляем. А в сишечке код СРАЗУ вычисляет. Т.е. в LISP'е выражение вычисляется только ПОСЛЕ того, как оно распарсится до тривиальных операторов. Не раньше. Потому, обычно умножается в LISP'е в нормальном порядке 3*(2*(1)), а не как в сишечке — в обратном, с временными переменными лежащими в стеке. Посмотри на этот код: Зачем нужна обработка списков в искусственном интеллекте? (комментарий) тут 3 временных переменных int n, которые лежат в стеке между кодами возврата, перед началом вычислений. В LISP'е никаких таких переменных нет, потому-что (factorial 2)сам по себе ничего не вычисляет, а «возвращает строчку» 2*1, а (factorial 3) делает из этой строчки 2*1 строчку 3*2*1, и уже ПОТОМ она вычисляется. Разница принципиальна.

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

Т.е. когда ты считаешь 3!, ты сначала СОЗДАЁШЬ выражения

3!

3*2!

3*2*1!

3*2*1

А потом созданное выражение вычисляешь. Сишная реализация работает иначе: ты сначала считаешь 3!, но для этого тебе надо посчитать 2!, а для этого 1!, вот когда ты посчитаешь 1!, ты возвращаешься из программы, и умножаешь результат на 2, и возвращаешься. И потом умножаешь на 3.

Кстати, ты не заметил, что в обоих случаях ты описываешь один и тот же процесс?

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

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

в этом и есть смысл кодинга на LISP. Например (factorial 3) выдаёт алгоритм 3*2*1. Алгоритм может быть и бесконечным, например это может быть сходящийся бесконечный ряд. Но если нужна какая-то точность, получившийся ряд на выходе алгоритма будет конечным, а не бесконечным. В сишечке тебе придётся

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

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

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

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

если аргумент — вызов функции, то РЕКУРСИВНО вычислим значение этого вызова по данному алгоритму;

слово «рекурсивно» относится тут к вызовам и к операторам. В LISP'е нет рекурсивного вычисления тривиальных операторов. Т.е. функция в LISP'е НЕ производит вычисления над данными в момент своего вызова.

Разве тебе непонятна разница?

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

Кстати, ты не заметил, что в обоих случаях ты описываешь один и тот же процесс?

я тебе больше скажу — машинный код тоже получается одинаковый в итоге. Разница в том, КАК он получается.

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

Т.е. сначала мы создаём выражение, а потом, то что получилось, мы вычисляем. А в сишечке код СРАЗУ вычисляет. Т.е. в LISP'е выражение вычисляется только ПОСЛЕ того, как оно распарсится до тривиальных операторов. Не раньше. Потому, обычно умножается в LISP'е в нормальном порядке 3*(2*(1)), а не как в сишечке — в обратном, с временными переменными лежащими в стеке.

Ох лол, в лиспе особая, уличная рекурсия.

Придется тебе объяснять, как вычисляется рекурсивное выражение в Лиспе, если уж ты не знаешь. Для примера, тот же (factorial 2):

  1. 2 != 0, поэтому делаем рекурсивный вызов (factorial 1) (двойка, естественно, остаётся в текущем кадре стека);
  2. 1 != 0, рекурсивный вызов (factorial 0), единица в текущем кадре стека;
  3. 0 == 0, возвращается единица, двигаемся обратно по стеку вызовов;
  4. единица, сохранённая в стеке, умножается на единицу, возвращённую из рекурсивного вызова, получается единица, которая и возвращается из текущего вызова;
  5. двойка умножается на единицу, в результате получается двойка, которая возвращается из функции и становится результатом исходного выражения.
theNamelessOne ★★★★★
()
Ответ на: комментарий от emulek

В sicp'е ведь в самом начале, когда вводится подстановочная модель вычисления, говорится, что интерпретатор так не вычисляет, что это просто модель, и что на самом деле интерпретатор работает с локальными окружениями, т.е. так, как написано здесь:

Зачем нужна обработка списков в искусственном интеллекте? (комментарий)

Если я ошибаюсь, прошу меня исправить.

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

слово «рекурсивно» относится тут к вызовам и к операторам.

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

В LISP'е нет рекурсивного вычисления тривиальных операторов.

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

Т.е. функция в LISP'е НЕ производит вычисления над данными в момент своего вызова.

Ложь. В лиспе аппликативный порядок вычисления. Если бы это было не так, тогда такой код выполнялся бы без ошибок:

* (defun foo (a b) (if a a b))

FOO
* (foo t (/ 1 0))

debugger invoked on a DIVISION-BY-ZERO in thread
#<THREAD "initial thread" RUNNING {1002998D53}>:
  arithmetic error DIVISION-BY-ZERO signalled
Operation was SB-KERNEL::DIVISION, operands (1 0).

Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-KERNEL::INTEGER-/-INTEGER 1 0)
0] 
theNamelessOne ★★★★★
()
Последнее исправление: theNamelessOne (всего исправлений: 1)
Ответ на: комментарий от Rastafarra

без священного факториала конечно никак :D

другие примеры намного сложнее. А этот — простой. Но даже по нему видно, что умножать на константу 1 не нужно. По сишному коду этого не видно, т.к. там 1 — значение функции, а значит, компилятору придётся всю функцию развернуть, что-бы это заметить. В ФП это разворачивание by design.

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

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

ты бы перечитал внимательно своё-же объяснение. Может заметишь наконец, что СНАЧАЛА функции разворачиваются, а уже ПОТОМ вычисляются. Обдумай, ЧТО возвращают функции на самом деле.

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

В sicp'е ведь в самом начале, когда вводится подстановочная модель вычисления, говорится, что интерпретатор так не вычисляет

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

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

Если я ошибаюсь, прошу меня исправить.

Да нет, всё ты правильно сказал. В том же разделе есть цитаты:

The purpose of the substitution is to help us think about procedure application, not to provide a description of how the interpreter really works. Typical interpreters do not evaluate procedure applications by manipulating the text of a procedure to substitute values for the formal parameters. In practice, the ``substitution" is accomplished by using a local environment for the formal parameters. We will discuss this more fully in chapters 3 and 4 when we examine the implementation of an interpreter in detail.

In general, when modeling phenomena in science and engineering, we begin with simplified, incomplete models. As we examine things in greater detail, these simple models become inadequate and must be replaced by more refined models. The substitution model is no exception. In particular, when we address in chapter 3 the use of procedures with ``mutable data," we will see that the substitution model breaks down and must be replaced by a more complicated model of procedure application.

и подраздел про порядок вычислений, где сказано:

Lisp uses applicative-order evaluation, partly because of the additional efficiency obtained from avoiding multiple evaluations of expressions such as those illustrated with (+ 5 1) and (* 5 2) above and, more significantly, because normal-order evaluation becomes much more complicated to deal with when we leave the realm of procedures that can be modeled by substitution. On the other hand, normal-order evaluation can be an extremely valuable tool, and we will investigate some of its implications in chapters 3 and 4.

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

слово «рекурсивно» относится тут к вызовам и к операторам.

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

ВНЕЗАПНО: именно об этом я и говорил. А вот в сишечке разница есть: аргументами операторов могут быть только числа. Аргументами функций могут быть не только числа, но и указатели на функции. Причём все эти операторы в сишечке НЕ определены, если аргумент — указатель на функцию. Также определены некоторые операторы для операций с другими указателями, но определены _иначе_. Т.е. оператор + для указателя и целого имеет другой смысл, нежели оператор + для целых. В лиспе нет таких сложностей, там у любого оператора любой аргумент может быть оператором, потому каких-то особых правил не предусмотрено. Они просто не нужны.

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

Ложь. В лиспе аппликативный порядок вычисления. Если бы это было не так, тогда такой код выполнялся бы без ошибок

ещё раз: это уже детали реализации.

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

ты бы перечитал внимательно своё-же объяснение. Может заметишь наконец, что СНАЧАЛА функции разворачиваются, а уже ПОТОМ вычисляются.

Ага, как и в сишечке.

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

Ложь. В лиспе аппликативный порядок вычисления. Если бы это было не так, тогда такой код выполнялся бы без ошибок

это уже детали реализации

Лол. Ты понимаешь что результат вычисления будет разным в зависимости от порядка вычисления?

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

КАК это работает в LISP'е — (factorial 2) это не вызов функции, а 2*1. Т.е. сначала мы создаём выражение, а потом, то что получилось, мы вычисляем. А в сишечке код СРАЗУ вычисляет.

У тебя хорошо получается сравнивать тёплое с мягким. Сначала ты описываешь чистое ФП как понятие; а потом сравниваешь это с реализацией ИП. Да, в чистом функциональном программировании вычисление по порядку эквивалентно разворачиванию выражений и их вычислению. Но требований сначала разворачивать программу, а только потом её вычислять — нет. В конце-концов, если результат будет такой же, не важно, как он будет получен.

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

i-rinat ★★★★★
()
Ответ на: комментарий от emulek

Т.е. оператор + для указателя и целого имеет другой смысл, нежели оператор + для целых. В лиспе нет таких сложностей, там у любого оператора любой аргумент может быть оператором, потому каких-то особых правил не предусмотрено. Они просто не нужны.

Фигасе какой лисп могучий. У него только одно правило — никаких правил? :)

ЗЫ

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

//У оператора * 1-й аргумент оператор + и второй, оператор -
int res = (2+2)*(3-1)

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

ещё раз: это уже детали реализации.

Да можешь сказать хоть сто раз, от этого твои слова правдой не станут. Ссылки на конкретный раздел в SICP я тебе дал, ссылка на HyperSpec в этом сообщении, а ты можешь продолжать жить в своём выдуманном мирке.

Вот ещё ссылка на R5RS, цитата оттуда:

Arguments to Scheme procedures are always passed by value, which means that the actual argument expressions are evaluated before the procedure gains control, whether the procedure needs the result of the evaluation or not. ML, C, and APL are three other languages that always pass arguments by value. This is distinct from the lazy-evaluation semantics of Haskell, or the call-by-name semantics of Algol 60, where an argument expression is not evaluated unless its value is needed by the procedure.

И это, приведи пример реализации Scheme/CL с нормальным порядком вычисления.

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

У оператора * 1-й аргумент оператор + и второй, оператор -

int res = (2+2)*(3-1)

Нет, у оператора * первый аргумент — это выражение (2 + 2), а второй аргумент — это выражение (3 - 1). Если быть точнее, то его аргументы — это результаты вычисления этих выражений.

Т.е. оператор + для указателя и целого имеет другой смысл, нежели оператор + для целых.

Детали реализации, которые будут верны и в случае лиспа.

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

Конечно же, это не так, просто Батти опять бредит.

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

Нет, у оператора * первый аргумент — это выражение (2 + 2), а второй аргумент — это выражение (3 - 1). Если быть точнее, то его аргументы — это результаты вычисления этих выражений.

Не буду спорить, я просто разложил это выражение в простейший AST, где в узлах операторы — отсюда и вывод сделал.

Но это я так, в рамках «поржать».

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

Опять ложь. Почему ты делаешь утверждения о том, о чём вообще понятия не имеешь?

почему ты к порядку операторов придрался? Я не про него говорил, а про то, что разворачивание функций происходит ПЕРЕД вычислением, а не во время, как в сишечке. Ошибки в этом твоём коде (define (try a b) (if (= a 0) 1 b)) зависят исключительно от ленивости (lazy) операторов, это их свойство к рассматриваемой теме отношения НЕ имеет. Если оператор if ленивый, то ошибок не будет. Это вообще к лиспу не имеет отношения false && (( 1/0 )) в bash'е тоже без ошибок работает, ибо && ленивый оператор. В сишечке он тоже ленивый. А вот в этом твоём CL, выходит, не ленивый. И что?

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

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

В принципе, любую программу на Хаскеле можно считать чисто-функциональной, если принять следующее определение типа IO:

newtype IO a = IO (RealWorld -> (a, RealWorld))

Т.е. функция getLine :: IO String, которая считывает строку со стандартного ввода, на самом деле превращается в getLine :: RealWorld -> (String, RealWorld), т.е. это функция, которая принимает своим аргументом значение типа «мир» и возвращает пару: «строка» и новое значение типа «мир». А функция main :: IO (), являющаяся входной точкой приложения, принимает своим аргументом значение типа «мир». Таким образом, даже I/O без побочных эффектов. [more info].

Естественно, это просто модель, а в реальности всё немного по-другому.

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

Ага, как и в сишечке.

нет. В сишечке тебе сначала надо ВЫПОЛНИТЬ функцию, и полученный результат подставить в код. Т.е. для вычисления factorial(2) ты сначала вычисляешь factorial(1), и получаешь _результат_ равный 1. Его ты и подставляешь в формулу 2*1. А вот в CL ты в качестве результата factorial(2) получаешь _константу_ 1. Разница между «числом» и «константой» тебе понятна?

emulek
()

Чем например Fortran не подошёл бы?

А напиши на фортране программу, которая на входе получает функцию, а на выходе --- её производную.

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

Ты понимаешь что результат вычисления будет разным в зависимости от порядка вычисления?

ну и что? Результат ещё и от аргументов и от алгоритмов зависит. Что дальше? Если ты следил за моей мыслю, ты и должен понимать, что код получится 1, а вовсе не что-то типа 1+0*(1/0), где вторая часть есть в результате, на него не влияет, но программу рушит.

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

почему ты к порядку операторов придрался?

Если что, я про порядок операторов вообще не говорил. Даже более того, в схеме порядок [вычисления] операторов не определён, но тем не менее это язык с аппликативным порядком, что определяет тот факт, что в нём аргументы функции вычисляются до её применения (или вызова, если будет угодно).

а про то, что разворачивание функций происходит ПЕРЕД вычислением, а не во время, как в сишечке.

Невозможно в языке с аппликативным порядком (макросы откинем в сторону). Лисп в этом отношении от сишечки ничем не отличается.

Ошибки в этом твоём коде (define (try a b) (if (= a 0) 1 b)) зависят исключительно от ленивости (lazy) операторов, это их свойство к рассматриваемой теме отношения НЕ имеет.

Вообще-то, факт ленивости/не ленивости языка — это производная от порядка вычислений (нормальный/аппликативный).

Если оператор if ленивый, то ошибок не будет. Это вообще к лиспу не имеет отношения false && (( 1/0 )) в bash'е тоже без ошибок работает, ибо && ленивый оператор. В сишечке он тоже ленивый. А вот в этом твоём CL, выходит, не ленивый. И что?

Внезапно, if в Scheme/CL ленивый (т.к. это на самом деле специальная форма), а ты опять обосрался. И «ошибки в этом моём коде» именно происходят из-за того, что аргументы вычисляются перед непосредственным вызовом функции, что можно посмотреть на следующем примере:

* (defun foo (a)
(format t "OMG! Lisp does have normal evaluation order! Sorry, drBatty, I was terribly wrong!"))
; in: DEFUN FOO
;     (SB-INT:NAMED-LAMBDA FOO
;         (A)
;       (BLOCK FOO
;         (FORMAT T
;                 "OMG! Lisp does have normal evaluation order! Sorry, drBatty, I was terribly wrong!")))
; ==>
;   #'(SB-INT:NAMED-LAMBDA FOO
;         (A)
;       (BLOCK FOO
;         (FORMAT T
;                 "OMG! Lisp does have normal evaluation order! Sorry, drBatty, I was terribly wrong!")))
; 
; caught STYLE-WARNING:
;   The variable A is defined but never used.
; 
; compilation unit finished
;   caught 1 STYLE-WARNING condition

FOO
* (foo (/ 1 0))

debugger invoked on a DIVISION-BY-ZERO in thread
#<THREAD "initial thread" RUNNING {1002998D53}>:
  arithmetic error DIVISION-BY-ZERO signalled
Operation was SB-KERNEL::DIVISION, operands (1 0).

Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.

restarts (invokable by number or by possibly-abbreviated name):
  0: [ABORT] Exit debugger, returning to top level.

(SB-KERNEL::INTEGER-/-INTEGER 1 0)
0] 

Будем дальше тупить, или хватит на сегодня?

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

У тебя хорошо получается сравнивать тёплое с мягким. Сначала ты описываешь чистое ФП как понятие; а потом сравниваешь это с реализацией ИП.

я сравниваю с сишечкой. А классическая сишечка — это и есть реализация чистой воды.

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

Так может только ГСМ рассуждать. Посчитай сколько будет 1-1/2+1/3-1/4... С точностью float например.

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

ты хотел сказать «не смогу свернуть»? Смогу. Другое дело, моя свёрнутая программ сможет выполнить только ОДНО действие. Например принять N аргументов, и вычислить любую функцию от этих аргументов. Такое не подходит только для игрушек(и прочего RT), там да, если скажем играем в шахмати, то будет странно, когда

1. юзер вводит все свои ходы

2. компьютер пишет «Вы проиграли», и пишет свои ответные ходы.

В чистом ФП так и будет. Это, конечно, не очень интересно...

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

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

Из атома factorial лукапится функция, которая вызывается с предвычисленными аргументами и точка. Процессы одинаковы для сей и лиспа.

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