V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  secondwtq  ›  全部回复第 64 页 / 共 123 页
回复总数  2442
1 ... 60  61  62  63  64  65  66  67  68  69 ... 123  
2020-01-10 21:25:02 +08:00
回复了 FaiChou 创建的主题 程序员 你们认为函数式编程语言未来可期吗?
居然顶到了某种字数上限,分两段发 ...

首先我无法理解楼主为什么会有在这个社区发这个帖子的想法 ...
虽然是 F2EX,但是我并不期待这里的人对所谓 FP 能有什么高见,至少在这里的人对 FP 有个哪怕是最基础的认识之前
说实话,哪怕是在 HN 上,这种帖子也是引战贴(并且随便一搜就能在同一个站点找出十个战场来),还是去 LtU 抱团取暖吧 ...
关于“FP 概念在大众已经流行起来了”,知乎上有相关问题: https://www.zhihu.com/question/30190384 为什么这两年函数式编程又火起来了?

楼主要想讨论“函数式编程语言的未来”,首先要解决“函数式编程是什么”的问题。这在任何一个主题和函数式编程无直接关系的社区中都是一个无法解决的问题。就连 JavaScript 和 Ruby 这种社区中也免不了三天两头就有人出来科普什么“函数式思维”,说明就算这些一只脚在 FP 里面的语言也没法解决这种问题 ...

我水平有限,不敢为“函数式编程”这个词给出一个足够“函数式”的定义(“函数式编程”这个词背后的 implication 太多了)。但是在我这几年折腾函数式编程的过程中,有那么两点最大的感受:

第一是对于任何 non-trivial 的程序而言,最重要的东西之一是管理复杂度( Complexity )。管理复杂度最重要的手段是模块化,或者按照 FP 的黑话,叫组合( Composition/Composable/Composability )。而这也是 FP 最看重的东西之一。

Composition 是什么意思?就是看上去非常牛逼如同魔法的东西,一点点拆开发现是由非常简单的东西构成的。比如乐高方块组合成完整的乐高玩具,细胞组成器官,器官组成人体,晶体管组合成逻辑门再组合成芯片,if、for 等操作组合成函数,函数再组合成程序,若干个程序模块组合成一个完整的软件系统。比如说人体一定程度上是 composable 的,因为器官可以移植,器官移植需要配型,所以器官又不那么 composable。
在编程语言中的例子比如:C 的宏有不 Composable 的方面(比如使用有副作用的片段展开可能会导致副作用执行多次); goto 不如 if 和 for 更 composable:用 if 和 for 写的程序可以随便拷贝到其他地方,作用还是一样的(在预期之中),但是纯 goto 写的做不到; Checked Exception 的问题也是出在不 Composable 上——使用其他模块除了需要了解模块本身接口之外,还需要折腾模块本身,以及可能的底层模块暴露出的异常;多重继承会出现 Diamond Problem,这意味着在使用多重继承时必须挨个确定每个基类都有哪些基类,也不 Composable。Implicit Coercion 看上去允许不同类型的值之间 compose,但是往往也会伤害 composability:比如 C 中无符号和有符号值之间的比较。锁也是不 Composable 的(见 https://en.wikipedia.org/wiki/Lock_(computer_science)#Lack_of_composability )。JS 中的 callback 相比 Promise 的缺点也是不 Composable:值是编程语言中最 Composable 的东西,并且 Promise 的错误处理也更有利于 compose。

可以发现不 Composable 的地方往往体现为各种各样的“坑”,而 Composable 的地方我们一般都 习以为常。

对于管理复杂度这一问题(比如设计软件),Composition 提供了极其明显的好处:在理想情况下,系统可以被分解为若干模块,模块可以递归地分解为更小的模块。特定的开发者在特定的时刻只需要关心自己正在开发的模块,以及所依赖的模块的接口。模块可以独立地开发与测试,模块之间的依赖被最小化,实现细节和复杂性被隐藏在抽象接口之后。

较差的 Composability 对于简单程序来讲问题不明显,项目越大,造成的麻烦就越大。

函数式编程提供了实现 Composability,管理复杂度的理论。函数式编程提倡通过小函数组合成更大的函数的方式来构建软件,对于 Composability 极为重视(某些 FP 厨经常扯的猫论更是从根上就是关于 Composability 的理论)。

函数式编程同时认为 Side Effect 是 Composability 的最大障碍,虽然“函数式”这个词可以被套在很多东西上,但是 Side Effect 不是其中之一。如果以 Side Effectful 的方式写程序,就意味着当使用某一模块时,必须考虑这个模块是怎么实现的,可能触发哪些 Side Effect,这个模块依赖的模块又可能触发哪些 Side Effect,这些被依赖的模块依赖的模块又可能触发哪些 Side Effect ...

但函数式编程并非完全拒绝 Side Effect (这不可行),而是强调应该 管理 Side Effect。比如 Haskell 强制使用类型的方式标记哪些函数有 Side Effect,哪些函数没有,有 Side Effect 的函数可能会包含哪些 Side Effect。这就强制你把有 Side Effect 和没有 Side Effect 的函数分开,并且对无 Side Effect 的函数实现了 Composable 的目标。
ML、LISP 和 Scala 等则对程序员比较信任,除了变量赋值稍微麻烦一点,使用 Side Effect 和 Imperative 语言没有什么不同。但从语言设计到库再到代码风格都偏向于限制 Side Effect 的范围。

---

第二是对于简洁、优雅的理论框架的追求。(注意这里的“优雅”不是语法上的“优雅”)

什么叫做“简洁、优雅的理论框架”呢?我举一个和目前 V 红话题相关,F2EX 应该能懂的例子:
https://raphlinus.github.io/ui/druid/2019/11/22/reactive-ui.html 这是我前段时间研究 GUI 时找到的一篇文章。文章试图用一个“简洁、优雅的理论框架”,统一目前流行的 React、Flutter 等 GUI 框架的思想——注意意思不是统一这些框架,而是尝试找出这些框架实现思想的共同点,并总结起来。注意文章作者自己就是 Berkeley 的 PhD,同时自己也在开发一个 GUI 框架。
该文章提出:
* 所有的 “reactive” 框架(该文章并未明确定义 “reactive” 这个词,只是用它来概括现在流行的 UI 框架)都可以被视为一个 Tree Transformation 的 Pipeline ——从状态的 tree,生成高层的组件 tree,再生成底层的元素 tree,布局、渲染后变成屏幕上的像素。
* Tree 有两种表示形式,一种是 linked data structure (就像数据结构课本上教的二叉树),另一种是 “trace of execution”,即遍历 tree 得到的 flatten 表示。
* 不同的 GUI 框架在实现这个 pipeline 的很多细节有不同。其中比较重要的部分之一是当输入(状态 tree )变化之后,如何高效更新 pipeline 中的其他 tree——也就是 React 中的 Reconciliation,Angular 的 Dirty Checking 之类的。
这个“理论”最牛逼的地方在于它不仅统一了 React 和 Flutter 之类所谓“声明式”( whatever it’s called ...)框架,还通过不同的“Tree 表示形式”,把渲染中间层和 imgui 这种 Immediate 模式也囊括进来,因此说是“Unified Theory”。这个文章是所有关于 GUI 的资料中,对我最有帮助的。
另外作为被王垠在去年 12 月 24 日喷过的人,我发现这套理论与现代优化编译器的设计思想有非常相似的地方。另一个 GUI 框架的开发者在看这篇文章时也有同样的感受: https://blog.anp.lol/rust/moxie-intro,并且他还找出了其他领域的例子。这时候直接搞到我颅内高潮了 ...

其他相关的例子包括:
* 天文学在牛顿之前一直在试图解释天体的运行规律。
* 后来的物理学两百年来一直在追求把四种基本作用统一的理论。
* 生物学使用演化论解释不同生物之间不同特征的关系。
* 图形学使用 Path Tracing 渲染所有光学效果。
* 大数据处理基于 MapReduce 模型操作数据(嘛这其实是 FP 的东西 ...)。
* 编译优化领域使用 Polyhedral Model 建模程序的行为。(编译优化好像还不存在所谓“Unified Theory” ... 这是让我感觉很别扭的一点)

UNIX 的“一切皆文件”,“一切皆文本”和某些 OOP 语言中的“一切皆对象”其实同样也是非常熟悉的例子。

与之相对的是,很多编程语言的基础是很随意的:C 最开始是为了折腾 UNIX 整出来的,C++ 则是在 C 的基础上的另一个个人项目。JavaScript 是为了跟 Java 的风十天写出来的。PHP 是为了维护个人网站写出来的。Python 的性质也类似。
Java 反倒是最根正苗红的一个——Java 有 Guy Steele ( Scheme 设计者之一)的参与,泛型等则更有 Martin Odersky ( Scala 之父)和 Philip Wadler ( Haskell 设计者之一,同时也在所谓 “Monad” 概念的应用中有重要贡献)的参与。Pascal 同理。

基础“随意”是什么意思?意思就是语言的设计只是为了满足设计者当下的需求,缺少理论的指导。当然一门语言的设计一定有“为了满足某种需求”的目的,但是“我想做个语言来写博客”和“我们实验室想开发个语言来写 Theorem Prover”显然是两种目标。而这些“随意”的语言的设计者,在设计语言的时候,往往对“他眼前所要解决的问题”比对“程序语言设计”这件事要在行的多,换句话说“随意”的语言是由 PL 外行人做出来的 ... C 的成功是 UNIX 的成功,C++ 的成功则是建立在 C 的成功的基础上,PHP 和 JavaScript 的成功是 Web 的成功——它们都不是“语言本身”的成功,而是傍着别人的大腿获得成功的。

理论的指导能带来什么好处?
相对论预言了黑洞,量子力学在半导体上有很多应用,真的有了物理上的统一理论,指不定又会捣鼓出什么东西。
写算法,了解了贪心、动态规划、分治等套路,能帮你更好地理解与设计算法(虽然有很多题还是做不出来 ... 但是如果你不知道这些套路,只会更难)。

在编程语言中,循环可以被看做是递归的一种特例,那么可以把循环拿掉,用递归(以及 PTC )来实现循环。
Exception、Coroutine、Generator、Async/Await 等则可以统一在 Continuation 的概念下,那就只需要实现 Composable 的 First-class Continuation 就行了。
HM 类型系统则可以进行完整的 Type Inference,允许以动态语言的方式写静态语言——整个程序不需要写一个类型,却依然是类型安全的。

函数式语言则会如此设计:首先把 Lambda Calculus 拿来,Lambda Calculus 允许用非常简单的模型,表达足够的抽象。然后使用 Type 来扩展(限制)它,变成 STLC,再加入一些简单的扩展,比如 Parametric Polymorphism,Algebraic Data Type。最后把 int、bool 等 primitive type 放进去。基本上就是一个能用的语言。
整个语言核心就是这个样子,找不到多余的东西。每一个新的 feature 都是对之前语言非常自然的扩展。
再加入 Reference 用来处理 Side Effect,Modules 来组织程序,做个 Type Inference,这就是一个 ML 语言。
最重要的是,所有的东西都是 Composable 的。

当然 ML 自己有不那么 Composable 的东西,比如只支持 Rank-1 Type,可以说是为了 Type Inference 做的妥协。那也可以扩展到 Higher-Rank 和 Impredicative 到完全的 System F (只是这时候理论会告诉你 Type Inference 不好做了)。所有这些并没有限制程序员的能力,而是提供了更好的 Composability。
Side Effect 也不利于 Composability,这方面也有若干方法来管理,同样是为了更好地 compose。

所以说我对一个”函数式语言“的判定最重要的是它有没有建立在函数式理论的基础上。而至于什么乱七八糟的”First-Class Function“”Algebraic Data Type“和”Immutability“之类的具体的特性并不是最关键的,因为它们都是在这个理论上自然的延伸。

另外,我个人并不喜欢 FP 标榜“利于并发”来搞宣传工作,“利于并发”只是 FP 所提供的 Composability 的一个 Side Effect,而 Composability 本身是 FP 理论的一个 Side Effect。冲着“利于并发”折腾 FP 的,大概率会 totally miss the point。
2020-01-06 19:37:58 +08:00
回复了 Renco 创建的主题 程序员 问下大佬们平时流程图是用工具画还是直接用代码生成的。
2020-01-06 19:36:09 +08:00
回复了 PainfulJoe 创建的主题 支付宝 支付宝年账单出来了,自己年支出 12w,干了一年没攒钱
@evam
把理财也算支出意思就是买的理财就是韭菜 :)
2020-01-05 21:55:23 +08:00
回复了 Cloudmxy 创建的主题 AMD AMD 压根没这么香...装了驱动小毛病不断...翻车...
楼主啥时候装的?
我被 intel 的 Linux 驱动问题折磨好几十天了
自己看: https://bbs.archlinux.org/viewtopic.php?id=250765
2020-01-02 22:44:36 +08:00
回复了 JCZ2MkKb5S8ZX9pq 创建的主题 git 关于 git 项目拆分的精度问题
楼主的工具之间应该是没啥依赖关系的,用 monorepo 必要性不大
不过全拆了太麻烦,所以可能只能 monorepo …
2020-01-02 22:10:35 +08:00
回复了 Whsiqi 创建的主题 写周报 2020 不会再写任何一行代码
NGA 用户表示飞行员苦不苦不知道,但是前途一定是牛逼的
等做到机长就有你享受的了
2020-01-01 18:39:24 +08:00
回复了 manami 创建的主题 分享发现 不可能完成的任务:在图片上打上它的 md5 哈希值
碰撞这个理论上倒是可行,问题是发到网络上(尤其是国内网络)基本都会有一道压图,格式也可能会转。我觉得不行
2020-01-01 18:31:04 +08:00
回复了 sandman511 创建的主题 程序员 Java :如何处理空指针?
这不就是一个 Maybe Integer 解决的问题 ... 搞这么复杂
说来话长 ...

首先明确一点,语言的 spec 里一般不会关心堆 /栈这些问题。也就是说楼主最开始的问题“reference values 一定在堆上,primitive values 一定在栈上吗?”的答案是否定的。

然后说实现问题,JavaScript 是动态类型语言,虽然动态类型语言在语言本身定义和实现上和静态类型语言有很多不同,但是动态类型语言的编译器实现技术和静态类型语言没有本质上的差别。考虑静态类型语言和动态类型语言之间,最重要的区别是什么?当然是在动态类型语言中,一个变量的类型只能在运行时确定,或者说所有的 type checking 都发生在运行时。

主流的动态类型语言优化编译器实现最大的区别是引入了一套新思想:在编译的过程中逐步去除语言的动态性,并对于 hotspot,根据上下文和运行时信息对类型等信息做 speculation,对常见的 path 做 specialization,在执行这些 specialized code 之前做 check,如果和预期不符则进行 deoptimization。其实也不算新,静态类型语言做 PGO 之类的跟这个很类似。

但是无论是用解释器还是 JIT 优化编译器的方式实现动态类型语言,最后在一方面的结果是相同的:就是最后生成的代码和数据结构都可以转换成某种静态类型语言(不特指 C,因为存在结构体布局等一般是非标准的细节,另外 deoptimization 应该也不能以标准 C 来表示)。也就是说可以用静态类型语言来模拟动态类型语言——比如定义一个类型叫 any,这个类型实现了所有的操作符,同时还可以包含任意的字段。然后在运行时调用的时候检查是否合法。

然后楼主的问题就转化为:这个 any 类型应该怎么存储? any 实际相当于一个 Union Type,但是我们知道 C/C++ 里面的 union 是不 type safe 的,原因是 C/C++ 里面的 union 本身并没有存储类型信息,C/C++ 项目中所有用到 union 的地方,要么是在其他地方存储了类型信息,要么就是依赖于某种 UB。动态类型语言要想做到“在运行时进行 type checking”,就必须在存储值的同时存储对应的类型信息(否则就退化成无类型语言了),在静态类型语言里面存储类型信息的例子并不少,比如 C++ 的虚函数表就属于这类东西,同理 Java/C# 的对象会有对象头信息,甚至会把整个类型塞进去做反射用。

在这里可以对这一堆东西做个简单的区分:Java/C# 如果不考虑 boxing,它们的 Object 类型结合反射也可以用来模拟动态类型( Java 更统一一点,C# 本身有个 Dynamic 类型,还有 struct 之类的)。C++ 的虚函数 /RTTI 则是比较受限的一种形态——只限于预定义的有限的几个函数,只能在预先声明的 class hierarchy 里工作。

Java/C#/C++ 的这些机制有一个共同的缺点就是:在实现中一般会对一些常见的类型做优化,因此语言中会出现 primitive type 和非 primitive type 的区别(此处的 primitive 不指 JavaScript 中的 primitive,比如 String 在 JavaScript 中按照标准是 primitive,但是在 Java 和 C++ 里面都只是普通的类而已),primitive type 无法直接利用这一机制,Java/C# 用 boxing 来绕过了这一限制,C++ 则需要手动实现 boxing (何况本来就是受限的)。

Haskell、OCaml、Rust、Swift 等语言则基于 Algebraic Data Type,提供了 tagged union + pattern matching。这一套机制可以直接 cover 所有的类型,同时也不碍着编译器做优化。TypeScript 的类型系统允许用户手动实现这一套机制。其弱点是在实际应用中的扩展性受限,Scala 的 case class 则是另一个有趣的结合体,学术界另有若干论文尝试解决这个扩展性的问题。

tagged union 这个扩展性问题具体说来就是:一个 tagged union 内部包含哪些选择,在声明时已经确定,不能像 Java 那样通过声明新类来添加新的类型。( OOP 另有自己的扩展性问题,这是另一个 topic,此处按下不表)这个问题带来的一个好处就是 tagged union 所占大小是可以在编译时确定的——在 C 的角度,最简的通用实现就是一个 struct 里面放了一个 union,但同时还放了一个字段表示当前使用的是 union 中的哪个类型,并且编译器帮你去做转换和检查工作。

从实现角度看,可以说 Java/C#/C++ 更偏向于 pointer dispatch,tagged union 则是 tag dispatch。这是两种基本的实现手段。在实践中,tag dispatch 可以在堆上也可以在栈上,pointer dispatch 一般和堆分配有更强的关联(但是理论上,实现动态行为的机制和内存分配的机制是正交的)。但是要特别注意的是,tagged union 的实现一般都是以 C/C++ 的 union 为基础,所以 C/C++ 的 union 可以怎么分配,tagged union 就可以怎么分配。

动态类型语言的 “any” 类型在绝对能力上则是最强的——可以包含一切类型,可以包含一切数据和操作。但是这个类型该如何实现呢?最后还是要落到上面两种实现手段上。(以上说的这些都是 type safe 的,C/C++ 的裸 union 因为不 type safe 被排除掉了)

类 Java/C# 的这种模式,在一个大家很熟悉的动态类型语言里面被应用了:就是 Python 的 CPython 解释器实现,在 CPython 中,所有的数据都存在堆里面,CPython 的解释器栈仅仅会存储对堆数据的引用,就算是 primitive type 的值也会变成 PyIntObject、PyFloatObject。这些堆中的 object 的文件头会存储类型信息,在应用某个操作符的时候调用某个函数之类的。

注意 JavaScript 也可以使用同样的方式实现。当然大家也知道 CPython 是出了名的慢(毕竟每一个操作都相当于过了一次 Java 的反射),这个现在的 JS 是没法接受的,你实现那么慢我们的垃圾代码怎么办?(什么你说优化代码?对不起我们只会写垃圾代码)那就要考虑进行优化,那这个 object 可不可以放在栈上呢?

我前面说了这个 object 相当于一个 any 类型,即所有类型的 Union。一般在 C/C++ 中,union 所占的空间由其所有类型的大小的最大值决定(还要根据对齐调整一下)。any 类型理论上包含所有类型,而用户理论上可以定义无限大的类型——结果是如果同样按照 Python 一样教条式的做法,坚持把数据全放栈上不动摇,any 类型只能无限大,不存在足够大的内存可以容纳这个类型,直接 Game Over。

解决方法就是改革开放,哦不变通一下——一般做法是由语言的实现钦定一些“primitive type”(注意此处的 primitive type 依然和 JavaScript 的 primitive 无关,而是实现角度的 primitive )使用栈分配 + tag dispatch,其他类型(包括用户定义的类型)作为“reference type”使用堆分配 + pointer dispatch。这和静态类型语言编译器针对一些 primitive type 做优化的思想是一样的。这样做的结果是,按照“union 所占的空间由其所有类型的大小的最大值决定+对齐调整”的规则,在 64 位机器上,假设钦定 bool, (unsigned) int 8/16/32/64, float, double 为 primitive type,再加上 reference type 所需的 64 位指针,这个 tagged union 的数据部分只需要 64 位就行了。前面说了单纯的 union 没用,还需要存储类型信息,那这个理论上需要 4 位来存储,实际会对齐成 ... 64 位,就是说一个值放栈里,占 128 位 /16 字节的空间。

注意上面两段默认了一个公理,就是“union 所占的空间由其所有类型的大小的最大值决定+对齐调整”。因为前面说了“动态类型语言的编译器实现技术和静态类型语言没有本质上的差别”,而这个条件在大多数静态类型语言中都是成立的。于是才有了动态类型语言使用真·所有类型的 union 不可行->使用 primitive/reference type 做优化的结果。理论上,动态类型语言可以根据自己的特点,把值所占空间缩减到 实际数据大小+类型信息(也就是推翻以上公理),这么做的一个后果是:any 类型的大小不是无限的了(除非用户真的定义了无限大的类型),但是依然是不可确定的。“不可确定大小的类型”如何进行优化是少有先例的——比如在 C/C++ 中根本不允许使用 incomplete type 创建对象,唯一的例外是 C 里面极其受限的 VLA。

上面最后还有一个问题,就是 tagged union 实现方式中,4 位的类型信息最后会被放大成 64 位,很大一部分空间被浪费了。这个同样有一个通用的做法,就是利用各种骚操作把至少为 2*sizeof(intptr_t) 的值大小优化成 1*sizeof(intptr_t)。这个具体的做法,Andy Wingo 称为 “value representation”,并且写了一篇文章来讲这个事情( https://wingolog.org/archives/2011/05/18/value-representation-in-javascript-implementations )。简单来说就是:现在的 64 位平台虽然名义上有 64 位地址空间,但是实际上只用了 48 位,另外 16 位是空的,所以可以拿 1 位来区分 reference 和 primitive ( 32 位平台没这福气,但是有一些其他的机会比如语言实现可以做一个“所有内存分配都会对齐到 4/8 字节”之类的保证,这就留了两位出来),但是这样就又有了其他的限制——原来 64 位的整数,有 1 位不能用了,那没办法只能用 63 位整数。另外只有 1 位用来存储类型信息就不能区分整数和浮点——相当于只区分了 63 位整数和指针。这些又可以有其他的优化手段。值得一提的是 JavaScriptCore 的做法:因为 JS 中只有 double 一种数值类型,所以它利用浮点类型的特性,在 64 位机器上实现了一个 64 位值对 64 位指针和 64 位浮点两种类型的区分 ... 实在是高

但是绕来绕去,最后达到了一个目的:就是实现了一个固定大小的 any 类型(至于这个“固定大小”到底有多大是另一个问题,但是一般是实现为 1*sizeof(intptr_t))。我把这个叫做“uniform representation”。这个属性不仅对动态类型语言有用,对静态类型语言也有用。比如 GC 需要这项能力来区分指针和非指针。Java 通过 boxing,事实上也实现了 uniform representation,并且 Java 的泛型依赖于此来实现。同样的做法存在于 Haskell 和 OCaml 中(所以在 OCaml 中可以看到 int63 这种奇葩类型 ... OCaml 也有 int64,但是是 boxed 的)。实现 uniform representation 会方便更多动态语言特性的实现(简单的泛型可以不依赖 uniform representation,用 Monomorphization 来实现,但是 Higher Ranked Polymorphism 之类就没这么简单了)。

如果要对楼主的问题简单给一个答案的话,那就是:在具备 uniform representation 的语言实现中,被钦定的那部分 primitive 是 有可能 放在栈上的,reference type 是 一定 放在堆上的。

这个结论同样假设了一个前提:根据上文,reference type 的定义仅仅是“除开 primitive type 之外的所有类型”,在 any 类型中借助指针来表示,连 primitive 都不一定是分配在栈上,reference type 又怎么能钦定放在堆上呢?编译器能不能把一部分 reference type “优化”在栈上呢?

理论上是可行的。比如静态类型语言编译器里面有一种优化叫 Scalar Replacement of Aggregates ( SRA/SRoA ),简单来说就是满足一定条件的前提下,把一个复合类型里面的字段展开成单个的变量。在静态类型语言中这有利于进行进一步的优化,在存在 reference type 的语言中,成功的 SRoA 还可以减少内存分配、访问和 GC 压力——你想想如果一个 reference type 值里面所有的值都是 primitive type,SRoA 之后不就相当于消灭了原来的 reference type 值,全变成 primitive type 了么。

但是优化必须保证不影响原有语义。也就是说编译器不仅要实现 SRoA,还只能在经过分析确保安全的情况下才能进行。所有的优化都是可选的,编译器就像一个尸位素餐的用户公仆,觉得可能有问题就放弃优化——和人类的公仆不同的是,机器公仆虽然在执行时同样的懦弱,但是之前会尽量根据已有的信息做分析(对于 SRoA 来讲主要是 Escape Analysis 和 Alias Analysis )。这里就涉及到动态类型语言和静态类型语言在非技术层面设计思路的根本分歧:动态类型语言的设计者不仅不想让用户提供类型信息,也同样认为程序员不需要提供除了所谓“程序本身”之外的信息。动态类型语言的用户上行下效,通常也不屑于提供此类信息。静态类型语言则认为自由来源于合理的限制,所以很多静态类型语言并不拒绝在程序中提供显式的,有利于优化的信息,并且类型信息本身就是对优化最有价值的信息。

一来二去造成了一个结果:就是静态类型语言实现在编译时可访问到的程序信息远远大于动态类型语言。

动态类型语言的高性能实现为了填语言设计者挖的坑,就必须在没有额外信息的情况下,在运行时猜出所需要的信息。这个猜出的结果随时都可能失效,所以还得准备相应的 fallback。对动态类型语言做有效的优化,难度要比静态类型语言更大。

当然,一般认为动态类型语言有它的优势:就是如果代码写得足够好,编译器在运行时通过运行时实际统计到的信息进行优化,理论上效果会比只能使用编译时信息的静态类型语言要好。但是这个优势明明是 JIT 的优势,不是动态类型语言的优势——静态类型语言同样也可以做 JIT,同样也可以在运行时收集信息并用于优化。并且 JIT 这种形式天然受 latency 限制,不能进行特别复杂的优化,在很多场景下,如果保持其他变量不变的话,静态类型语言的 PGO 绝对效果是更好的。总的来说就是,在编译器可以利用到的信息量方面,静态类型语言严格大于动态类型语言。指望 JS 靠 compiler magic 超过 C/C++ 并不现实。

王垠前两天发了篇文章叫《我不是编译器专家》,在这篇文章中,王垠(再次)强调了业界对编译器和 PL 两个领域普遍的混淆,并且表达了对编译器技术上的藐视、对编译器人坐井观天、目中无人作风的批判。我们暂且忽略王垠三年半之前发了一篇内容相似的《我为什么不再做 PL 人》,并且把相似的罪名同样安到了 PL 圈子上的事实(不行我还是要笑),王垠所说的一些问题是确实存在的。编译器到底是夹在语言和硬件的 spec 之间,戴着镣铐跳舞的活。很多编译器费尽力气分析的结果,从语言上很简单就能解决。这是维度上的差异。新的 ECMA 标准加入的 strict mode、let、const、module、rest parameter 等特性都有尝试从语言层面改进性能的成分。

反映到这个贴子的问题上就是,在 JS 中做同样的优化会更难。V8 据称是实现了 SRoA,但是具体效果我没研究过,楼主可以自己试一试。
2019-12-28 14:07:11 +08:00
回复了 cooioobb 创建的主题 生活 以房东身份跟大家算房租。
首先,真可怕警告

--[真可怕[
@cooioobb 我回答的是“6000 一个月为什么 6000。有 3000 他不租,租 6000 的是为什么”的问题:
“上班近的房子可以租出更高的价格并且还有人租”

这对应的是 #66 中的:“同一座城市(物价、人工成本接近),有月租 3k 的房,有月租 6k 的房”
显然这多出来的钱不是在装修和电器家具上的
--]真可怕]

#81 说的租售比失调是目前某些城市租房市场的一大本质问题(虽然这个和楼主可能没啥关系),但是很明显租售比的问题不是房东的问题,更不是租客的问题。
2019-12-28 13:39:06 +08:00
回复了 cooioobb 创建的主题 生活 以房东身份跟大家算房租。
我给楼主一个实际的建议,联系你们城中村的房东,讨论以后租房不提供电器家具,稍微降房租,其他所有费用租客自负。这样你们省心又能多赚钱。
就是搞卡特尔
2019-12-28 13:35:57 +08:00
回复了 cooioobb 创建的主题 生活 以房东身份跟大家算房租。
@cooioobb
我前面说“‘相对的价值’不存在一个公认的算法”,是因为同样的东西,对不同的人,实际价值是不一样的。
比如我不打算生孩子,那么学区房的溢价对我就一文不值。
但是大多数买房的人要考虑教育问题,所以学区房在市场上就可以卖出高溢价。也就是最后的价格由市场决定(不是由成本决定,更不是由抱怨决定)。

租房的时候也存在同样的问题,比如企业聚集地周围的房子会偏贵一点,因为有许多人看重“上班近”这一属性支撑起了价格。
但是一个不在意上班近的租客,发现这房子除了上班近之外也没啥别的好处,那么就可能不会租这个房子,转而选择 同价格更远面积更大的房子 或 更远价格更低其他都相似 的房子。
这都不碍着上班近的房子可以租出更高的价格并且还有人租。
@yimity
“getElementBy 获取到的是动态的,即 你增加了 dom,再获取还是可以获取到你最新增加的的,你删掉了 dom,再获取的时候,之前获取到的就不在了,就是每次都是实时从 dom 树里面获取。
而 querySelector 是引用,即获取一次后面增加的 dom,不会获取到了,删掉了 dom, 在代码中还是能够用(但是会报错)。”

求依据:标准,源码,或示例
你说的可能是 getElementByClassName/TagName 和 querySelectorAll
楼主这个好像没这个区别
2019-12-28 13:18:58 +08:00
回复了 cooioobb 创建的主题 生活 以房东身份跟大家算房租。
@qinxi 本来想写 19260817,感觉太离谱了点
不过不是不可能哦
2019-12-28 12:38:21 +08:00
回复了 cooioobb 创建的主题 生活 以房东身份跟大家算房租。
@cooioobb 6k 的房子装修一定就比 3k 的好么?
另外“相对的价值”不存在一个公认的算法。
2019-12-28 12:23:26 +08:00
回复了 cooioobb 创建的主题 生活 以房东身份跟大家算房租。
这帖子逻辑是有问题的 ...
资产价格最终由市场决定,没有一个“合理”的价格。楼主说现在一套月净赚 1300,从市场的角度是合理的,如果市场发生变化,一套月净赚 2510 或者净赚 251 甚至净亏都是合理的。所以不存在“真实租金”太“便宜”或者太“贵”一说。
房东会寻求利益最大化(尽量涨租金),而给平均租金设限的是平均工资,就算租客不要求这些东西,最后租金还是不会比现在低多少。
“盖房不用钱”——错,楼主一套房月净赚 1300。还是说楼主想一年回本三年血赚?

考虑这样一个问题:同一座城市(物价、人工成本接近),有月租 3k 的房,有月租 6k 的房,难道说月租 6k 的房每个月贵的 3k 都是用在租客“额外要求”的东西上?
@grewer 不能拿 GitHub 做例子,据我观察 GitHub 是最激进的站点之一,我 Mac 上的老版 Chrome,GitHub 不能加载最新 commit,branch 的菜单点不开。另外 YouTube 不能看。

其他网站没出过问题,说明 GitHub 和 Google 可能确实对浏览器要求比较高。
2019-12-28 08:55:01 +08:00
回复了 22yune 创建的主题 程序员 计算机中有哪些不做 trade-off,鱼与熊掌兼得的解决方案?
没有
#8 难道是楼主的小号 ...

用 querySelector + 字符串拼接意味着可以 query 的不只 id ... 在输入的字符串后面可以加其他选择器

@ysc3839 选择器只用解析一遍,有缓存
但是用来做同样的工作,前者是要稍微慢一点(因为确实包了一层)
2019-12-27 20:02:12 +08:00
回复了 xiaoyanbot 创建的主题 分享发现 JS/Node 改变世界,单文件静态编译 Node 项目: pkg 和 nexe
改变世界有两种,第一种是让世界变得更好,第二种是让世界变得更差
1 ... 60  61  62  63  64  65  66  67  68  69 ... 123  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5202 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 47ms · UTC 06:49 · PVG 14:49 · LAX 23:49 · JFK 02:49
Developed with CodeLauncher
♥ Do have faith in what you're doing.