什么是具有动态作用域和静态类型的编程语言?

发布于 2024-10-06 05:25:55 字数 61 浏览 3 评论 0原文

我知道这种语言的存在,但我无法确定它的具体情况。

动态范围 和 静态类型?

I know the language exists, but I can't put my finger on it.

dynamic scope
and
static typing?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

离笑几人歌 2024-10-13 05:25:55

我们可以尝试推理这样的语言可能是什么样子。显然,这样的事情(使用类似 C 的语法用于演示目的)是不允许的,或者至少没有明显的含义:

int x_plus_(int y) {
    return x + y;        // requires that x have type int
}

int three_plus_(int y) {
    double x = 3.0;
    return x_plus_(y);   // calls x_plus_ when x has type double
}

那么,如何避免这种情况呢?

我可以立即想到一些方法:

  1. 上面的评论者提到 77 年之前的 Fortran 有这种行为。这是有效的,因为变量的名称决定了它的类型;像上面的 x_plus_ 这样的函数是非法的,因为 x 永远不可能有整数类型。 (同样,像 third_plus_ 这样的变量,因为 y 也有相同的限制。)整数变量的名称必须以 i 开头、jklmn

  2. Perl 使用语法来区分几大类变量,即标量、数组(常规数组)和散列(关联数组)。属于不同类别的变量可以具有完全相同的名称,因为语法区分了所指的变量。例如,表达式 foo $foo, $foo[0], $foo{'foo'} 涉及函数 foo,标量 $foo、数组 @foo$foo[0]@foo 的第一个元素)和散列 % foo$foo{'foo'}%foo 中与键 'foo' 相对应的值)。现在,要非常清楚的是,Perl 不是静态类型的,因为有许多不同的标量类型,并且这些类型在语法上无法区分。 (特别是:所有引用都是标量,甚至是对函数、数组或散列的引用。因此,如果您使用语法取消对数组的引用,Perl 必须在运行时检查该值是否确实是数组引用。 )但是同样的方法可以用于真正的类型系统,特别是如果类型系统是一个非常简单的系统。通过这种方法,x_plus_ 方法将使用 int 类型的 x,并且完全忽略 xthird_plus_ 声明。 (相反,它将使用 int 类型的 x,该类型必须从名为 third_plus_ 的任何范围提供。)这可能需要某种类型上面未包含的注释,或者它可以使用某种形式的类型推断。

  3. 函数的签名可以指示它使用的非局部变量及其预期类型。在上面的示例中,x_plus_ 将具有签名“采用一个 int 类型的参数;使用 类型的调用范围 x” int;返回int类型的值”。然后,就像调用 x_plus_ 的函数必须传入 int 类型的参数一样,它还必须提供一个名为 x 的变量int 类型的 code> — 通过声明它本身,或者继承类型签名的该部分(因为调用 x_plus_ 相当于使用 x< /code> 类型为 int)并将此要求传播给调用者。使用这种方法,上面的 Three_plus_ 函数将是非法的,因为它会违反它调用的 x_plus_ 方法的签名 - 就像它试图传递 < code>double 作为其参数。

  4. 上面可能只是有“未定义的行为”;编译器不必显式地检测和拒绝它,但规范不会对其处理方式施加任何特定要求。程序员有责任确保他们永远不会调用带有错误类型的非局部变量的函数。

您的教授可能正在考虑#1,因为 77 年之前的 Fortran 是一种具有此属性的真实世界语言。但其他方法值得思考。 :-)

We can try to reason about what such a language might look like. Obviously something like this (using a C-like syntax for demonstration purposes) cannot be allowed, or at least not with the obvious meaning:

int x_plus_(int y) {
    return x + y;        // requires that x have type int
}

int three_plus_(int y) {
    double x = 3.0;
    return x_plus_(y);   // calls x_plus_ when x has type double
}

So, how to avoid this?

I can think of a few approaches offhand:

  1. Commenters above mention that Fortran pre-'77 had this behavior. That worked because a variable's name determined its type; a function like x_plus_ above would be illegal, because x could never have an integer type. (And likewise one like three_plus_, for that matter, because y would have the same restriction.) Integer variables had to have names beginning with i, j, k, l, m, or n.

  2. Perl uses syntax to distinguish a few broad categories of variables, namely scalars vs. arrays (regular arrays) vs. hashes (associative arrays). Variables belonging to the different categories can have the exact same name, because the syntax distinguishes which one is meant. For example, the expression foo $foo, $foo[0], $foo{'foo'} involves the function foo, the scalar $foo, the array @foo ($foo[0] being the first element of @foo), and the hash %foo ($foo{'foo'} being the value in %foo corresponding to the key 'foo'). Now, to be quite clear, Perl is not statically typed, because there are many different scalar types, and these are types not distinguished syntactically. (In particular: all references are scalars, even references to functions or arrays or hashes. So if you use the syntax to dereference a reference to an array, Perl has to check at runtime to see if the value really is an array-reference.) But this same approach could be used for a bona fide type system, especially if the type system were a very simple one. With that approach, the x_plus_ method would be using an x of type int, and would completely ignore the x declared by three_plus_. (Instead, it would use an x of type int that had to be provided from whatever scope called three_plus_.) This could either require some type annotations not included above, or it could use some form of type inference.

  3. A function's signature could indicate the non-local variables it uses, and their expected types. In the above example, x_plus_ would have the signature "takes one argument of type int; uses a calling-scope x of type int; returns a value of type int". Then, just like how a function that calls x_plus_ would have to pass in an argument of type int, it would also have to provide a variable named x of type int — either by declaring it itself, or by inheriting that part of the type-signature (since calling x_plus_ is equivalent to using an x of type int) and propagating this requirement up to its callers. With this approach, the three_plus_ function above would be illegal, because it would violate the signature of the x_plus_ method it invokes — just the same as if it tried to pass a double as its argument.

  4. The above could just have "undefined behavior"; the compiler wouldn't have to explicitly detect and reject it, but the spec wouldn't impose any particular requirements on how it had to handle it. It would be the responsibility of programmers to ensure that they never invoke a function with incorrectly-typed non-local variables.

Your professor was presumably thinking of #1, since pre-'77 Fortran was an actual real-world language with this property. But the other approaches are interesting to think about. :-)

笑忘罢 2024-10-13 05:25:55

我没有在其他地方找到它写下来,但是 AXIOM CAS (以及各种分支,包括 FriCAS(仍在积极开发中)使用一种称为 SPAD 的脚本语言,具有非常新颖的强大功能静态依赖类型系统和动态作用域(尽管这可能是一个意外的实现错误)。

大多数时候,用户不会意识到这一点,但是当他们开始尝试像其他函数式语言一样构建闭包时,它就会揭示其动态作用域本质:

                       FriCAS Computer Algebra System 
                         Version: FriCAS 2021-03-06
                   Timestamp: Mon May 17 10:43:08 CST 2021
-----------------------------------------------------------------------------
   Issue )copyright to view copyright notices.
   Issue )summary for a summary of useful system commands.
   Issue )quit to leave FriCAS and return to shell.
-----------------------------------------------------------------------------
 

(1) -> foo (x,y) == x + y
                                                                   Type: Void
(2) -> foo (1,2)
   Compiling function foo with type (PositiveInteger, PositiveInteger)
       -> PositiveInteger 

   (2)  3
                                                        Type: PositiveInteger
(3) -> foo             

   (3)  foo (x, y) == x + y
                                                    Type: FunctionCalled(foo)
(4) -> bar x y == x + y
                                                                   Type: Void
(5) -> bar  

   (5)  bar x == y +-> x + y
                                                    Type: FunctionCalled(bar)
(6) -> (bar 1)
   Compiling function bar with type PositiveInteger -> 
      AnonymousFunction 

   (6)  y +-> #1 + y
                                                      Type: AnonymousFunction
(7) -> ((bar 1) 2)

   (7)  #1 + 2
                                                    Type: Polynomial(Integer)

这种行为类似于尝试使用 (lambda (x) (lambda (y) (+ xy))) 在动态范围的 Lisp 中,例如 Emacs Lisp。实际上,函数的底层表示本质上与早期的 Lisp 相同,因为 AXIOM 最初是在 IBM 大型机上的早期 Lisp 实现之上开发的。

然而,我相信这是一个缺陷(就像 JMC 在实现 LISP 语言的第一个版本时发生的情况一样),因为实现者使解析器像 bar 的函数定义中那样进行非柯里化,但这不太可能在没有能力用语言构建闭包的情况下有用。

还值得注意的是,SPAD 会自动重命名异常函数中的变量以避免捕获,因此它的动态作用域可以用作其他 Lisp 中的功能。

I haven't found elsewhere has it written down, but AXIOM CAS (and various forks, including FriCAS which is still been actively developed) uses a script language called SPAD with both a very novel strong static dependent type system and dynamic scoping (although it is possibly an unintended implementation bug).

Most of the time the user won't realize that, but when they start trying to build closures like other functional languages it reveals its dynamic scoping nature:

                       FriCAS Computer Algebra System 
                         Version: FriCAS 2021-03-06
                   Timestamp: Mon May 17 10:43:08 CST 2021
-----------------------------------------------------------------------------
   Issue )copyright to view copyright notices.
   Issue )summary for a summary of useful system commands.
   Issue )quit to leave FriCAS and return to shell.
-----------------------------------------------------------------------------
 

(1) -> foo (x,y) == x + y
                                                                   Type: Void
(2) -> foo (1,2)
   Compiling function foo with type (PositiveInteger, PositiveInteger)
       -> PositiveInteger 

   (2)  3
                                                        Type: PositiveInteger
(3) -> foo             

   (3)  foo (x, y) == x + y
                                                    Type: FunctionCalled(foo)
(4) -> bar x y == x + y
                                                                   Type: Void
(5) -> bar  

   (5)  bar x == y +-> x + y
                                                    Type: FunctionCalled(bar)
(6) -> (bar 1)
   Compiling function bar with type PositiveInteger -> 
      AnonymousFunction 

   (6)  y +-> #1 + y
                                                      Type: AnonymousFunction
(7) -> ((bar 1) 2)

   (7)  #1 + 2
                                                    Type: Polynomial(Integer)

Such a behavior is similar to what will happen when trying to build a closure by using (lambda (x) (lambda (y) (+ x y))) in a dynamically scoped Lisp, such as Emacs Lisp. Actually the underlying representation of functions is essentially the same as Lisp in the early days since AXIOM has been first developed on top of an early Lisp implementation on IBM mainframe.

I believe it is however a defect (like what JMC happened did when implementing the first version of LISP language) because the implementor made the parser to do uncurrying as in the function definition of bar, but it is unlikely to be useful without the ability to build the closure in the language.

It is also worth notice that SPAD automatically renames the variables in anomalous functions to avoid captures so its dynamic scoping could be used as a feature as in other Lisps.

酒中人 2024-10-13 05:25:55

动态作用域意味着,特定代码行中的变量及其类型取决于之前调用的函数。这意味着,您无法知道特定代码行的类型,因为您无法知道之前执行过哪些代码。

静态类型意味着,在代码开始运行之前,您必须知道代码每一行的类型。

这是不可调和的。

Dynamic scope means, that the variable and its type in a specific line of your code depends on the functions, called before. This means, you can not know the type in a specific line of your code, because you can not know, which code has been executed before.

Static typing means, that you have to know the type in every line of your code, before the code starts to run.

This is irreconcilable.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文