2016-08-09 4 views
0

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

const func1 = (x => x * x); // JavaScript Arrow Function syntax. 
const func2 = (x => {log("I'm func2!"); return x + 1;}); 

var a; 
var b <= func1(a); // `<=` is a *Binding* operator. 
var c <= func2(b); 

a = 1; 
log(c); // Logs "I'm func2!", and then logs 2, which is ((a * a) + 1). 
log(b); // Logs 1, which is (a * a). 

a = 2; 
log(c); // Logs "I'm func2!", and then logs 5, which is ((a * a) + 1). 
log(b); // Logs 4, which is (a * a). 

// When b or c is accessed, the function for it is called and make the value. 

Есть ли язык программирования, который имеет концепцию, описанную выше?

+1

Возможно, вы захотите взглянуть на функциональные языки, такие как Haskell (который является одним из «чистейших» функциональных языков) – DAXaholic

+2

Lazy оценка? https://en.wikipedia.org/wiki/Lazy_evaluation – Wickramaranga

+1

Таблицы Excel подобны подобным работам. – svick

ответ

0

Основные стратегии оценки в языках программирования являются ленивым (например, вызов по имени) и строгое (например, вызов по значению). Ленивые языки оценивают выражение только тогда, когда это необходимо; строгие языки с нетерпением оценивают выражение. Чтобы быть конкретным, разница обычно возникает в вызовах функций: ленивые языки передают аргумент функции unevaluated, а строгие языки оценивают аргумент, а затем вызывают функцию. Есть много компромиссов, о которых вы можете прочитать, например. https://en.wikipedia.org/wiki/Evaluation_strategy.

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

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

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

+0

Это не так уж плохо - обратите внимание, что в [по крайней мере большей части] lisps, даже если они имеют лексическое охват, определения уровня сложности связаны динамически (в противном случае вы не могли бы переопределить их в repl и должны были бы использовать что-то вроде letrec или Y combinator, чтобы иметь возможность записывать рекурсивные определения). – dercz

0

Я не знаю такого языка, но для этого я знаю другую стратегию: вы можете снова обернуть вызов в объекте Lambda. Это будет иметь тот же эффект, что и вы описали. Например. в Лиспе вы можете:

(defun some-func (x) (* x x)) 
(defvar *a* (lambda() (some-func 4))) 
*a* ;; => returns the function 
(*a*) ;; => returns 16 

Это возможно практически на всех современных языках. Даже в C, но там это становится довольно сложно.

+0

Это позволяет отложить оценку стоимости, но позволяет ли она ее изменять? – dercz

+0

Если вы рассматриваете замену использованной лямбды как «изменяющую» ее, тогда это возможно: '(setf * a * (lambda() (some-func 10))' –

0

Какое смешное совпадение! Я знаю, что один язык обладает этим свойством, он существует с понедельника, здесь: https://github.com/drcz/ZJEB

Предполагалось, что это будет отчасти шутка и отчасти эксперимент в выразительности; вот сессия РЕПЛ с вашим примером:

(-- ALGORITHMIC LANGUAGE ZJEB v0.1 --) 
copyleft 2016/08/08 by Scislav Dercz 
type (halt) to quit 

READY. 
>(def func1 (bind (x) (* x x))) 
(new shorthand func1 memoized) 
>(def func2 (bind (x) (+ x 1))) 
(new shorthand func2 memoized) 
>(def a 1) 
(new shorthand a memoized) 
>(def b (func1 a)) 
(new shorthand b memoized) 
>(def c (func2 b)) 
(new shorthand c memoized) 
>c 
2 
>b 
1 
>(def a 2) 
(new shorthand a memoized) 
>c 
5 
>b 
4 

и вот проще один

>(def x 5) 
(new shorthand x memoized) 
>(def y (* x x)) 
(new shorthand y memoized) 
>y 
25 
>(def x 3) 
(new shorthand x memoized) 
>y 
9 

Дело в том, в отличие от схемы (или любой другой сюсюкать я знаю), то «DEF» форма делает не оценивать второй операнд - он просто создает синтаксическую «стенографию» для данного выражения. Таким образом, в любое время, когда оценщик находит символ, он пытается найти его в среде (т. Е. Привязки переменных значений, созданных с помощью формы «bind» - это вариант соответствия шаблону «lambda» с несколькими вариантами из lisp) , и если он терпит неудачу, он также проверяет список определений - когда он преуспевает, он сразу же оценивает соответствующее выражение.

Он не предназначен для того, чтобы делать то, что вы описываете (и вообще не полезно вообще, только заставляя оценщика делать немного больше работы в текущей реализации), поскольку язык является чисто функциональным (и фактически возможно только переопределение указаний на сокращение, для удобства работы с repl). Это «особенность» находится там, потому что я хотел бы думать о рекурсивных определений как

(def fact (bind (0) 1 
       (n) (* n (fact (- n 1))))) 

на самом деле означает бесконечное выражение

(bind (0) 1 
     (n) (* n ((bind (0) 1 
         (n) (* n ...)) (- n 1))) 

в духе «Lattice диаграмм потоков» Dana Скотта.

Обратите внимание, что бесконечные структуры данных, как

(def evil `(ha ,evil)) 

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

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

Комментарий Svik также очень приятный, электронные таблицы работают так (и некоторые люди утверждают, что они являются FRP-языком).

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

0

Ленивая оценка - это метод оценки программы Haskell. Это означает, что выражения не оцениваются, когда они привязаны к переменным, но их оценка отложена до тех пор, пока другие результаты не потребуются для других вычислений. https://wiki.haskell.org/Lazy_evaluation

0

Что вам нужно, это позывные по имени и изменяемые переменные. Одним из самых ранних языков с (необязательным) звонком по имени был Algol 60.

Более современный язык с (по желанию) по телефону - Scala. Вот порт ваш пример кода в Scala:

def log(a: Any) = println(a) 

def func1(x: => Int) = x * x 
def func2(x: => Int) = { 
    log("I'm func2!") 
    x + 1 
} 

var a = 0 

def rest1(b: => Int) = { 
    def rest2(c: => Int) = { 
    a = 1 
    log(c); // Logs "I'm func2!", and then logs 2, which is ((a * a) + 1). 
    log(b); // Logs 1, which is (a * a). 

    a = 2; 
    log(c); // Logs "I'm func2!", and then logs 5, which is ((a * a) + 1). 
    log(b); // Logs 4, which is (a * a). 
    } 
    rest2(func2(b)) 
} 
rest1(func1(a)) 

Параметры, которые объявлены такие как b: => Int являются вызовом по имени (в то время как b: Int бы вызов по значению).