关于编程语言:关于动态类型静态类型语言对于数据的理解的一些差别的随想

6次阅读

共计 4117 个字符,预计需要花费 11 分钟才能阅读完成。

不是谨严的思考, 只是梳理一下感触, 最近在动静类型动态类型之间切换有点多, 对照思考.
我的教训基本上是 js, ts 和 ClojureScript 上边, 再有点 Nim 的应用教训.
而后 Go 跟 Haskell 都只是简略尝试过, 没有深刻进去.
这些个语言都是主动内存治理的, 所以内存往下深刻的我也不探讨了.

数据的示意

动静类型, 介绍数据结构时候说数组, 说字典, 而后就是根本的操作.
基本上只是有一个类型构造, 运行时能力判断具体的类型, 但这个对思维模型来说限度少.
比方思考一个数组, 就是数组, 什么都能够放进去, 数字, 字符串, 对象, 正则, 嵌套数组也行.
在实现层面, 我喜爱叫这个 uni-type, 因为外部就是一个构造蕴含很多信息来示意各种类型.
编程的时候, 思考的就是有这么个构造, 有这么个 value, 至于 value 是什么, 动静的, 能够随便放.

动态类型, 思考就很多了, 特地是内存布局方面的,
Nim 里边就是 object(相似 struct), seq, array, list, 多种的构造.
每个构造会对应的到一个内存的布局的形式. 细节具体怎么布局, 我理解不彻底, 然而模糊地对应上.
特地是动态类型编码的时候不同构造之间性能的差异, 比拟容易显露出来.
我用错了构造的时候, 数组的中央用链表, 访问速度的差距马上就进去了.
而且构造对类型限度明确, int 数组就不能有 string 了, 这个也是显著的.

从业务编码的角度来说, 用动静类型来模仿一些业务逻辑, 通常还是比拟轻易的.
而要用动态类型, 常常须要有一些思考, 用怎么样的类型来示意, 受到什么样的限度.
定义了构造, 还有怎么拜访怎么遍历的问题.
个别说比方 js 性能优化较全面了, 个别场景都不会去思考不同写法的性能差异了,
而动态类型, 示意的形式, 拜访的形式, 都比拟容易对性能造成影响.
动态类型更贴近机器, 或者说用动态类型, 就更加须要相熟编码器的各种行为, 以便生成高性能代码.

Clojure 这边还有个较为非凡的例子, 就是 Vector 类型 HashMap 类型外部是用 B+ 树示意的.
个别动静类型语言不会去强调这种外部的实现, 然而 Clojure 出于性能起因高调宣传了一下.
React 这边, 不是被 immutablejs 搞得满城风雨的, 之前大家也不太留神外部实现什么样子.
我用 Nim 工夫长了一点发现这中形象形式在动态类型当中还是蛮广泛的, 数据结构嘛, 链表, B 树 …

而且有个不小的不适应的点, 在动静类型当中, 一个深度嵌套的数据结构, 间接打印就好了,
比方一个大的 JSON, 打印的时候能够就递归地格式化掉. prettier 一下就能看.
然而动态类型, Nim 当中的例子, 有时候数据就是不晓得怎么打印的,
因为简单数据没有一个 $(示意 toString) 函数, 那么就无奈打印.
这个在 Haskell 当中也是, show 有时候通过 derive 继承过去, 有时候就不行.
因为这一点, 动态类型就比拟容易暗藏掉一个数据的外部实现了.
而动静类型, js Clojure 的数据, 个别就是间接递归打印进去.
js 用 class 的话我记得能从新定义掉, 不过 React 这边也不乐意去用 class.
而且 js 程序员大多也习惯了 Console 当中层层开展间接查看数据的.
我感觉有封装能力对于程序结构来说是更好的, 尽管便利性的确有折扣.

通用性

动静类型, 不同的程序通过字符串替换数据的时候, 简略粗犷, JSON stringify/parse 就搞定了.
JSON 当初是十分通用的构造了. Clojure 那边也是认为这是动静语言微小的长处.
这就是程序根底的构造, 跨程序跨语言都共通的构造, 这才有宽泛的通用性.
而 nil 在当中表演的角色也比拟重要, 因为内部起源的数据很可能就是缺失内容的, 大量的 nil.

动态类型在数据接管传递上就存在限度了, 大量的 nil, 判断起来就很麻烦.
protobuf 我没用过, 调研的时候看说的, 二进制数据须要类型文件能力解析,
而且构造也是严格定义的, 不能错. protobuf 还额定加上限度, 不能删除字段.
从编码器的角度看这边是比拟好了解的, 为了不便生成代码, 这些外来的数据都应该尽量精确繁多,
不繁多不稳固的话, 就须要额定生成很多的代码来解决非凡状况, 就很麻烦.
Nim 的例子我试了, 但这个次要是靠 macro 在简略场景能提供很不便的写法,
理论用的话, Nim 解决一大堆的字段, 先用 JSON 获取, 再读取, 也很啰嗦.

代数类型, 算是另一个方向吧. 依照我后面的思路, 它的表达能力偏差于动静类型,
然而相比于硬件的限度, 代数类型感觉更多是收到数学定义的限度,
比方 data A = B | C 这种或的逻辑, 硬件上示意会啰嗦不少,
而代数类型通过内置的语法, 无论创立, 操作, 还是类型查看, 都比单纯动态类型不便.
另外有名的例子就是 Maybe string 这样的类型来代替 nil 了.
我没什么应用教训, 不好判断这个实用性.

数据校验.

动静类型, 默认也没什么校验的, 数据不对, 执行的时候报错, 就这样了.
须要校验的话都是动静地依照规定去校验, 比方 Clojure 用的 spec, js schema 之类的.
动静有个益处是这两头的逻辑能够用编程语言间接去表达出来, 非常灵活.
and or 这样的逻辑操作轻易用, if switch 也能用, 其余逻辑也是,
这就有了比拟大的施展的空间了, 封装成不同的类库.

动态类型, 从生成代码的角度, 这个代码能运行就是曾经通过校验的,
或者应该说, 程序能运行, 就是说编码的数据满足了一些编码器要求的限度的.
seq[int] 当中是 int, 错的话代码编译就会报错. 除了业务, 个别也没什么须要类型校验的.
但这个, 相对来说也就是编码器要的那种水平, 对于业务来说是比拟死板的.

另外有个比拟花哨的, TypeScript, 理论运行容许 any, 就显得十分非凡了.
我偏向于认为 ts 就是个 type checker, 而不是当做一个 compiler.
ts 当中也借鉴了代数类型做了一些 union type, product type, 十分弱小,
只是说跟前两者相比, 是一个十分奇异的思路了.
代数类型, Haskell 有个 quickcheck, 能通过类型主动生成随机数据测试, 据说是十分弱小的,
Clojure Spec 局部山寨了这样的性能, 依赖的是运行时加上 macro 一些能力.
动态类型这边要这么做, 就要反射机制了, 这个我不相熟 … 这整块货色感觉水就比拟深了.

数据办法

动静类型, 还是后面 uni-type 的想法, 数据很多时认为是一个类型, 没得辨别的.
具体运行的话, 就有个专门的字段比方 .kind 标记, 用来判断类型,
而后针对不同类型要调用不同的办法的话, 也就能做针对性的操作了.
动静语言, 多态靠的对象继承的形式来实现. 绑在类上边有个函数办法.
当然, 这个在 React Clojure 生态里边, 这种就比拟少了.
特地是数据还以 JSON 模式到处散发, 更多的还是用 .kind 标记, 再调用不同函数.

动态类型这边, 当我应用的 Nim 的时候, 就发现这里多态就很天然有个计划, 因为有类型,

proc f(a: int): bool

proc f(a: string): bool

理论调用 a.f()(Nim 里边这是 f(a) 的缩写 ) 的时候, 依据 a 的类型就有对应的办法.
我联想起来 Haskell 定义 class instance 的时候相似也是这样的,
OOP 不是我相熟的畛域我也不好说了, 然而这个场景有类型, 解决方案就很天然.
而动静类型, 我定义一个 join, 就要用判断不同类型再针对解决了, 也不不便扩大.

我感觉这一点对咱们了解数据比拟有影响, 因为数据往往是要被操作的.
我是把数据认为是树状的一大个, 而后须要函数动静地去判断, 而后再操作呢?
还是说认为数据一块一块互相拼凑, 然而各自有清晰的类型, 而后有各自的办法?
还是说跟原始的面向对象那样, 就是一个对象, 承受音讯, 收回音讯?
我不好说这些观点互斥, 然而比如说你设计一个简略的框架, 他人依照框架来疾速实现性能,
那么在这个框架当中, 他人怎么去了解数据这个事件, 当然还有状态, 这两头的关系和性能?
因为我是 ClojureScript 用得比拟习惯, 我就更认为是树状的一大串.

形象能力

上边梳理的根本还是系统的想法, 最终还是会回到形象能力这个事件上来.
当然开发当中有不同的场景, 不同的计划并不是说某个就是不对的, 它起码也是有一个适宜的场景.
然而这种, 对于思考业务的差异还是挺大的, 反馈在不同的编程语言当中.

比方 Clojure 当中, 动静的, 而且强调不可变数据, 就会导致全局存储树形的数据结构,
我对待状态的时候, 在这类场景当中就是一棵可能是很深的树, 而后判断.
因为是动静的, 也是树, 我就会十分随便地容许一些配置化的数据随处呈现,
我也不那么在乎这个构造是不是个单元, 须要的时候, 我就写个函数, 动静解决一下就好了.

而 Nim 当中, 定义类型, 枚举, 构造体, 这些概念就很根底,
我定义一个函数, 也要想要函数的操作对象首先是谁, 哪些可变哪些不可变.
这个跟面向对象那种思路也相近一些, 他能被怎么操作有哪些办法, 都会想一想.
而后如果有深一点的数据, 很显著就是这些小的构造的组合了.

我用动静类型的话, 比拟容易会淡化掉这种结构性的思考, 因为创立新的构造老本很小,
不用说两个类似的数据, 多一个属性少一个属性, 思考是不是去复用,
原本大家就是 uni-type 了, 大家都是一样的, 只是外部蕴含的信息动静有差别.
这个随意性, 对于性能来说是不好的, 然而编码当中便当是存在的.
动态类型, 当然, 性能好.
此外, 还有个状况, 就是有类型推断参加的场景,
因为类型推断依赖一些假如, 这两头的情景就随着变简单. 就感觉挺混搭的.
比方 TypeScript 根据结尾创立的字段和元素简历类型了, 然而后边编码可能又被颠覆掉.

再开展点想, 如果只是为了实现业务, 甚至都不限于应用某个编程语言,
那么咱们形容的业务用的 DSL 当中, 为了也是专门定制的数据,
这个时候数据类型啊, 操作方法啊, 这里的数据该往怎么样的状态上演变?

我没有个清晰的论断. 本能得我会心愿业务上我能动静类型轻易上,
当然我也晓得类型推断提供的类型反对, 对于防止程序破绽十分强力,
而且一些根底的数据结构, 能形象进去封装好称为一个类型, 益处也很多.
大略后边要对 Haskell 那样的体系还是要减少一些理解, 反过来帮忙了解动静类型.

正文完
 0