我读过一个相关的问题在Python这样的动态语言中,是否存在不必要的设计模式?并记住了这句论Wikiquote.org的话
动态输入的奇妙之处在于它可以让您表达任何可计算的内容。类型系统通常是不可判定的,它们将你限制在一个子集上。支持静态类型系统的人说:“它很好,它已经足够好了;您想要编写的所有有趣的程序都可以作为类型工作”。但这是荒谬的--一旦你有了一个类型系统,你甚至不知道有什么有趣的程序。--软件工程无线电第140集: Gilad Bracha的新语言和可插拔类型
我想知道,是否有有用的设计模式或策略,使用引语的表述,“不作为类型工作”?
发布于 2016-08-15 10:17:32
动态类型意味着您有一流的类型:您可以在运行时检查、创建和存储类型,包括语言自己的类型。这也意味着值是类型的,而不是变量。
静态类型化语言也可能产生依赖动态类型的代码,如方法分派、类型类等,但运行时通常是不可见的。充其量,他们会给你一些内省的方法。或者,您可以将类型模拟为值,但随后就有了一个临时动态类型系统.
然而,动态类型系统很少只有一流的类型.你可以有一流的符号,一流的包裹,一流的.所有的一切。这与静态类型语言中编译器的语言和运行时语言之间的严格分离形成了对比。编译器或解释器也可以在运行时完成。
现在,让我们承认类型推断是一件好事,我喜欢在运行之前检查我的代码。但是,我也喜欢能够在运行时生成和编译代码。我也喜欢在编译时预先计算。在动态类型语言中,这是用同一种语言完成的。在OCaml中,您有一个与主类型系统不同的模块/函子类型系统,它不同于预处理语言。在C++中,您有与主语言无关的模板语言,它通常不知道执行过程中的类型。这在这些语言中很好,因为他们不想提供更多。
最终,这并没有真正改变你能开发什么样的软件,但是表达能力改变了你开发软件的方式,也改变了它的难度。
依赖动态类型的模式涉及动态环境:开放类、分派、内存中的对象数据库、序列化等。像泛型容器这样的简单东西可以工作,因为向量在运行时不会忘记它持有的对象类型(不需要参数类型)。
我试图介绍在Common中对代码进行评估的许多方法,以及可能的静态分析(这是SBCL)的示例。沙箱示例编译从单独文件中获取的Lisp代码的一个很小的子集。为了安全起见,我改变了可读性,只允许一个标准符号的子集,并用超时包装事物。
;;
;; Fetching systems, installing them, etc.
;; ASDF and QL provide provide resp. a Make-like facility
;; and system management inside the runtime: those are
;; not distinct programs.
;; Reflexivity allows to develop dedicated tools: for example,
;; being able to find the transitive reduction of dependencies
;; to parallelize builds.
;; https://gitlab.common-lisp.net/xcvb/asdf-dependency-grovel
;;
(ql:quickload 'trivial-timeout)
;;
;; Readtables are part of the runtime.
;; See also NAMED-READTABLES.
;;
(defparameter *safe-readtable* (copy-readtable *readtable*))
(set-macro-character #\# nil t *safe-readtable*)
(set-macro-character #\: (lambda (&rest args)
(declare (ignore args))
(error "Colon character disabled."))
nil
*safe-readtable*)
;; eval-when is necessary when compiling the whole file.
;; This makes the result of the form available in the compile-time
;; environment.
(eval-when (:compile-toplevel :load-toplevel :execute)
(defvar +WHITELISTED-LISP-SYMBOLS+
'(+ - * / lambda labels mod rem expt round
truncate floor ceiling values multiple-value-bind)))
;;
;; Read-time evaluation #.+WHITELISTED-LISP-SYMBOLS+
;; The same language is used to control the reader.
;;
(defpackage :sandbox
(:import-from
:common-lisp . #.+WHITELISTED-LISP-SYMBOLS+)
(:export . #.+WHITELISTED-LISP-SYMBOLS+))
(declaim (inline read-sandbox))
(defun read-sandbox (stream &key (timeout 3))
(declare (type (integer 0 10) timeout))
(trivial-timeout:with-timeout (timeout)
(let ((*read-eval* nil)
(*readtable* *safe-readtable*)
;;
;; Packages are first-class: no possible name collision.
;;
(package (make-package (gensym "SANDBOX") :use '(:sandbox))))
(unwind-protect
(let ((*package* package))
(loop
with stop = (gensym)
for read = (read stream nil stop)
until (eq read stop)
;;
;; Eval at runtime
;;
for value = (eval read)
;;
;; Type checking
;;
unless (functionp value)
do (error "Not a function")
;;
;; Compile at run-time
;;
collect (compile nil value)))
(delete-package package)))))
;;
;; Static type checking.
;; warning: Constant 50 conflicts with its asserted type (MOD 11)
;;
(defun read-sandbox-file (file)
(with-open-file (in file)
(read-sandbox in :timeout 50)))
;; get it right, this time
(defun read-sandbox-file (file)
(with-open-file (in file)
(read-sandbox in)))
#| /tmp/plugin.lisp
(lambda (x) (+ (* 3 x) 100))
(lambda (a b c) (* a b))
|#
(read-sandbox-file #P"/tmp/plugin.lisp")
;;
;; caught COMMON-LISP:STYLE-WARNING:
;; The variable C is defined but never used.
;;
(#<FUNCTION (LAMBDA (#:X)) {10068B008B}>
#<FUNCTION (LAMBDA (#:A #:B #:C)) {10068D484B}>)上面没有什么是“不可能”与其他语言做的。在Blender、音乐软件或静态编译语言的IDEs中,动态语言采用插件的方式进行动态重新编译,而不是外部工具,动态语言更喜欢使用已经存在的信息的工具。所有已知的FOO来电者?酒吧的所有子类?所有由ZOT类专门化的方法?这是内部化的数据。类型只是这方面的另一个方面。
(另见:CFFI)
发布于 2016-08-12 21:05:11
简短的回答:不,因为图灵等价。
长篇大论的回答:这家伙是个巨魔。的确,类型系统“将您限制为子集”,但从定义上说,该子集之外的内容是无法工作的东西。
您可以在任何图灵完全编程语言(这是为通用编程设计的语言,再加上许多不是;这是一个很低的标准,有几个例子,一个系统变成图灵-完全无意)你可以做的任何其他图灵完整的编程语言。这就是所谓的“图灵等价”,它的意思仅仅是它所说的。重要的是,这并不意味着你可以在另一种语言中轻松地做另一件事--有些人会争辩说,这就是首先创建一种新的编程语言的全部意义:给你一种更好的方法来做一些现有语言不擅长的事情。
例如,动态类型系统可以在静态OO类型系统的基础上模拟,只需将所有变量、参数和返回值声明为基本的Object类型,然后使用反射来访问内部的特定数据,因此当您意识到这一点时,您会发现在动态语言中没有什么是您在静态语言中做不到的。当然,这样做会造成很大的混乱。
引用中的人是正确的,静态类型限制了您可以做什么,但这是一个重要的特性,而不是一个问题。道路上的线路限制了你在车里所能做的事情,但你认为它们是限制性的,还是有帮助的?(我知道我不想在一条繁忙而复杂的道路上行驶,那里没有任何东西表明汽车朝相反的方向行驶,只能站在他们的一边,而不是从我开车的地方过来!)通过设置规则,清楚地描述哪些行为被认为是无效的,并确保它不会发生,您将大大降低发生严重崩溃的可能性。
他还曲解了对方的性格。这并不是说“你想要写的所有有趣的程序都会以类型的形式工作”,而是“你想要写的所有有趣的程序都需要类型”。一旦超越了一定程度的复杂性,没有类型系统就很难维护代码库,这有两个原因。
首先,因为没有类型注释的代码很难阅读。考虑下面的Python:
def sendData(self, value):
self.connection.send(serialize(value.someProperty))您期望连接另一端的系统接收的数据是什么样子的?如果它收到的东西看上去完全不对劲,你怎么知道发生了什么事?
这一切都取决于value.someProperty的结构。但是它看起来像什么呢?问得好!什么叫sendData()?它经过了什么?那个变量是什么样子的?它是从哪里来的?如果它不是本地的,您必须跟踪整个value的历史,以跟踪正在发生的事情。也许您传递的是具有someProperty属性的其他东西,但它不像您认为的那样?
现在,让我们使用类型注释来查看它,正如您在Boo语言中可能看到的那样,Boo语言使用非常类似的语法,但是是静态类型的:
def SendData(value as MyDataType):
self.Connection.Send(Serialize(value.SomeProperty))如果出了什么问题,您的调试工作突然变得更容易了:查找MyDataType的定义!另外,由于您传递了一些不兼容类型,并且具有相同名称的属性,因此出现不良行为的可能性突然变为零,因为类型系统不会让您犯这个错误。
第二个原因建立在第一个基础之上:在一个大型而复杂的项目中,很可能有多个贡献者。(如果不是,你将在很长一段时间内自己建造它,这基本上是一回事。)如果你不相信我,试着读你3年前写的代码吧!)这意味着您不知道在编写代码时编写代码的人的头部是什么,因为您不在场,或者不记得很久以前是否是您自己的代码。拥有类型声明确实可以帮助您理解代码的意图!
像引用中的人一样,人们常常错误地将静态输入的好处描述为“帮助编译器”或“所有效率”,在这样一个几乎无限的硬件资源使其与过去的一年越来越不相关的世界里。但正如我所展示的,虽然这些好处确实存在,但主要的好处在于人为因素,特别是代码的可读性和可维护性。(不过,增加的效率当然是个不错的奖励!)
发布于 2016-08-12 21:47:45
我要绕开“模式”这个部分,因为我认为它已经转移到了什么是或不是模式的定义中,而且我早就对这场辩论失去了兴趣。我要说的是,在某些语言中,有些事情是你做不到的。让我说清楚,我并不是说你可以用一种语言解决问题,而在另一种语言中却不能解决问题。梅森已经指出图灵的完整性。
例如,我用python编写了一个类,它封装了一个XML元素,并将其转化为一个第一类对象。也就是说,您可以编写代码:
doc.header.status.text()您可以从解析的XML对象中获取该路径的内容。有点整洁,海事组织。如果没有head节点,它只返回一个只包含虚拟对象(海龟一直向下)的虚拟对象。在Java中,没有真正的方法可以做到这一点。您必须提前编译一个基于XML结构的知识的类。撇开这是否是一个好主意,这类事情确实改变了你用动态语言解决问题的方式。然而,我并不是说它总是以一种必然更好的方式改变。动态方法有一定的成本,梅森的回答给出了一个不错的概述。他们是否是一个好的选择取决于许多因素。
另外,您可以在Java中这样做,因为您可以构建一个Java的python解释器。解决特定语言中的特定问题可能意味着构建一个解释器或类似于它的东西,这在人们谈论图灵完整性时常常被忽视。
https://softwareengineering.stackexchange.com/questions/328285
复制相似问题