V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  secondwtq  ›  全部回复第 64 页 / 共 123 页
回复总数  2447
1 ... 60  61  62  63  64  65  66  67  68  69 ... 123  
2020-01-13 19:01:21 +08:00
回复了 IMFFA 创建的主题 JavaScript 请教一下,怎么把这段代码换一个优雅的写法
语言只是表达计算的工具,就算楼主的项目不能用 ES6+,人肉 babel 把楼上的 Snippet 改改也能用。
2020-01-13 18:59:02 +08:00
回复了 pmispig 创建的主题 Go 编程语言 请问怎么解析用户输入表达式
当然是用正则,Go 的正则库很好用
"浮点运算结果不准确"恰恰是预期行为,因为浮点数的定义就决定了它必然”不准确“,浮点数本身就是一个 approximation。

这和 primitive 的整数有有符号和无符号之分、范围限制、溢出问题之类某种程度上是同一个性质。同样我们可以问:primitive 整数有溢出是不是 bug ?
不同的在于,primitive 整数溢出可以是 bug——一些编程语言包含了边界检查的语义,在出现溢出时会抛出异常。但是浮点运算从定义上就是不准确的,因此不算做 bug。
之所以说它们有共同之处,是因为它们都是在计算机上实现计算过程时做出的妥协。

比如说,Java 有 Primitive Type 和非 Primitive Type 之分,Primitive Type 成为了 Java 的”一切皆对象“原则上的一个漏洞( web.archive.org/web/20081012113943/http://www.eecs.harvard.edu/~greg/cs256sp2005/lec15.txt )。可见 Primitive Type 造成的问题不仅仅是“运算不精确”一个。楼主的问题”为什么至今仍然有很多开发语言有这个问题呢“首先需要解决的是“既然 Primitive Type 如此麻烦,为什么还要保留 Primitive Type 呢?”并且不只 Java,几乎所有编程语言都具有 Primitive Type,这是为什么呢?

理论上,使用非常精简的规则,即可表达出所有计算需要的东西,包括数字( https://en.wikipedia.org/wiki/Church_encoding ),因此,Primitive Type 在理论上是不必要的。但是在实际应用的编程语言中,很少有大量应用 Church Encoding 的,相反,Primitive Type 被广泛使用。
因为“In theory, theory and practice are the same. In practice, they are not.”

如果把整个计算机看做一个系统的话,编程语言是开发者和计算机之间的接口,ISA 是软件和硬件之间的接口,这些接口都是越简洁越好。图灵机的基本规则也非常简单,如果只是实现一个计算机的话,几条指令完全够用了。
上世纪著名的 PDP 系列有很多是硬件不支持乘法 /除法操作的。Intel 在 8086 之前也不支持,而一直到 486 之前,浮点运算还要靠 Coprocessor。一直到现在的 RISCV,乘除法、浮点还是 Extension,核心指令集只有不到 50 条指令。这个规模并不比 UNIX 最早用的早期 PDP-11 要小多少。
同样的原则可以推出,GPU 所做的工作 CPU 也可以做,GPU 是完全没必要的东西。老黄赚的钱全是炒概念的智商税,AMD 也没必要养着 RTG。包括挖矿什么的也可以通通使用 CPU 搞定。
但是另一方面,RISC-V 也有各种扩展(很多还没做完),现在的 x86 算上各种扩展已经有了一千多条指令。ARM 的核心指令集规模和其他 RISC 类似,但是随便一个 SIMD 扩展甩出来就是几十上百条指令。而现在不仅 CPU 和 GPU 很火,还加入了乱七八糟的 FPGA、TPU 之类的东西,老黄还多此一举的把 RT core 做进了 GPU 里面。这似乎与我们所追求的简洁构成了某种矛盾,最重要的是,老黄又赚了我们一波智商税。

我在 V 站说明过“编程语言的设计可以影响给到编译器的程序信息的量,进而影响优化编译器的优化效果”( https://v2ex.com/t/632869#r_8401400 )以及“高级语言抽象好,低级语言上限高”( https://v2ex.com/t/594287#r_7803885 )的原理。同样的原则也适用在硬件上——硬件在执行计算时需要“我要执行什么计算”的信息,而 Church Encoding 之类的通用表示方法之所以没法用,就是因为它太通用了导致硬件得到的信息太少,执行效率太低——一个 C++ 程序可以被编译为机器码,但是给你一坨 C++ 编译出来的机器码(经过较多优化,无调试符号),不能反编译出原始的 C++ 程序,甚至就算再把原始 C++ 程序给你,把 C++ 代码和机器代码的位置对应起来在没有调试符号的情况下都是个难题,大量的高层程序信息在转换为具体的、底层的机器表示的过程中逐渐不可逆地丢失了。现代 CPU 会利用各种手段以利用更多的程序信息,达到更高的执行效率,但是当程序信息本身就不足时,硬件厂商也无能为力,所以现在硬件厂商宁愿教育开发者多写 “Modern Code” (虽然最后开发者还是更喜欢 Electron )来最大化硬件使用率,提高执行效率(这里的极端便是上个十年的 VLIW 架构——抛弃 CPU 部分的 hack 来简化硬件,寄希望于软件(包括编译器)能给出更多的信息)。另一方面,硬件厂商需要给出用来表示高效代码执行所需的接口,这就是各种乱七八糟的指令集扩展和非通用硬件。
硬件本身则通过 Chip Specialization 的方法,来最大化这些信息的利用。什么是 Specialization ?比如说我们知道整数 a * 8 等价于 a << 3,那么编译器如果有“a 是整数”和“表达式 a * 8”这样的信息,便可以把 a * 8 specialize 为 a << 3。Specialization 要求获得足够的信息,如果编译器不知道 a 的类型,或者遇到“a * b”这样的表达式( b 的值无法推导),就没有办法做 Specialization。
半导体中的 Chip Specialization 则是指对特定已知的计算,直接使用芯片硬件电路实现,而不是用通用的方法(先实现一个图灵完全的指令集,弄一个 CPU,再写软件实现算法)。这样做可以用更少的功耗,对特定计算实现更高的性能——因为算法直接在硬件实现,并且会用经过优化的方法实现。用软件实现和用优化的硬件实现的区别,就像用 Python 实现 FFT 算法性能不如直接调用 scipy 库一样——Python 直接实现的算法,在运行时除了你自己,计算机是不知道它在做 FFT 的,这个信息在源码之后就被丢失了。scipy 库则可以利用“我现在正在做的是 FFT”这项信息给出最优的实现,前提是你通过“调库”的方式,把这个信息告诉计算机。
GPU 是对图形运算的 Specialization,GPGPU 则是对 SIMT 模型的 Specialization,RT core 是对光线追踪算法的 Specialization,现在手机厂商争相加入的 AI 芯片,则是对 AI 算法的 Specialization,苹果为新 Mac Pro 推出了 Afterburner 加速卡,貌似是用 FPGA 做的,可以看做是对 ProRes 格式的 Specialization。
当然,越是做 Chip Specialization,就越会发现 Chip Specialization 的能力是有极限的,这就是现在半导体所讲的 The Accelerator Wall ( https://parallel.princeton.edu/papers/wall-hpca19.pdf )——芯片厂商在把常见算法都用硬件实现一遍之后就又没事可做了,现在看上去大家都在搞 Chip Specialization,只是因为之前都在搞通用处理器,没有来得及充分利用 Chip Specialization 的潜力而已,等到这波“红利”吃完了,还是会回到通过爬制程工艺,堆核扩大芯片规模来提升性能和能耗比的老路。

Chip Specialization 不仅体现在 AI、挖矿、光追等“高大上”领域,不同位数的整数运算、乘除法运算、浮点运算同样也属于 Chip Specialization,只不过这些早就普及了。也正是因为这些东西普及率高,工业上的通用编程语言才会设计 Primitive Type,作用正是允许程序员将优化需要的信息 encode 在程序中,从而方便编译器或硬件的 Specialization (一般做成 Primitive Type 的,在整个系统栈中的某个或多个位置都会有 Specialization,比如上面提到某些处理器没有提供硬件乘法指令,这时编译器会调用一个优化过的库函数来做乘法)。
需要注意的是,Primitive Type 和底层 Specialization 的对应关系,并不能动摇 Primitive Type 本身更像个 hack 的性质。Primitive Type 实际在程序语言中形成了某种边界模糊的 DSL,而将 Specialization 抽象为 DSL 的做法在最近越来越 explicit,比如 CUDA 则是程序员为 NVIDIA GPGPU 这一 Specialization 提供计算信息的工具,同样的现象出现在 AI 领域。
所以 C 语言标准里会针对各种 Primitive Type 做出“至少 32 位”之类奇怪的限制,因为这些 Primitive Type 直接对应硬件或软件的 Specialization 或某个可以用来做 Specialization 的标准。

无限范围 /精度的整数和实数在理论上是不能使用有限空间存储的,并且实现会比固定范围更复杂,而大多数情况下,其带来的好处无法 justify 其成本。最后形成的妥协便是:使用固定位数、有限精度的整数和浮点数来进行大多数的计算。在编程语言中做 Primitive Type,在编译器和库中针对这些类型做优化,在硬件中针对这些类型的运算做 Specialization。
“任何人都不想得到不准确的结果吧”同样的话可以这么说“任何人都不想内存空间受限制吧”“任何人都不想网速有个最大值吧”“任何人都不想一次航班要好几个小时吧”“任何人都不想钱能花完吧”。

浮点数只是系统给你提供的一个选择,当固定位数的整数 /浮点数无法满足你的需求时,你可以选择使用其他手段,就像在编程语言中定义新的函数、类型一样。比如使用符号计算,把你的公式本身(而不是公式运算出的值)存储起来,计算机来做化简,什么数都可以表示。如果楼主够厉害,够有钱,可以使用 Chip Specialization 的方式把这套系统用硬件实现,并做成编程语言的 Primitive Type (或一套 DSL )。就不会有这种问题了。

真正的 bug 出在楼主的认识里。“浮点数”从定义上就是有理数的一个子集而不是实数,也不是有理数。各种 Primitive Integer Type 一般也对应的是整数的一个子集而不是整数。楼主将“浮点数”默认为“小数”或“实数”导致出现了这样的疑问。但是有没有想过,如果“浮点数”等于“实数”的话,为什么要叫“浮点数”这个奇怪的名字而不是“实数”呢。

当然有些编程语言不负责任地定义了一个名字叫 “real” 类型,却用浮点数实现。real 这个名字上包含所有实数,但是只能包含有理数的一个子集。同理有些语言有名叫“int”或“integer”的类型,但是只能包含整数的一个子集。这种挂羊头卖狗肉的行为已经超越了 bug 的范畴,我个人是支持批判的。但是如果名为 “float”“single”“double”,用浮点数实现,只能表示部分有理数,这是预期行为,不是 bug。
2020-01-10 21:42:41 +08:00
回复了 murmur 创建的主题 奇思妙想 [年终总结]为什么我要做果黑,以及果黑的自我修养
楼主漏了一个“macOS 是 UNIX 和 GUI 的最佳结合”
2020-01-10 21:25:14 +08:00
回复了 FaiChou 创建的主题 程序员 你们认为函数式编程语言未来可期吗?
几个 myth:
* 函数式编程需要"高智商"么?

我认为技术社区对函数式编程的陌生感是被建构出来的。或者说进入了”目前 imperative 占了主流,所以大家从一开始就接受的是 imperative 的话语体系,所以会对 functional 有陌生感“的循环。并且这个循环还存在”functional 的资料更少,生态更差“的负反馈。

函数式编程本身做的就是用非常简单的东西 compose 为更加复杂的东西,函数式编程的理论、大多数函数式语言和函数式程序走的都是这个路子,函数式编程本身并不比拼乐高难。

为什么说函数式编程是自然的,符合直觉的?为什么说函数式编程能够 Compose ?
举个例子:为什么要有 closure 和 first-class function 呢?
Closure 反映的是 Lexical Scoping,就是我在外面定义的一个东西,在里面一定能用(无论是一个 if 块里面,还是 for 块里面,还是函数里面)。这本来就是一个编程语言应该具有的属性。
first-class function 则是对编程语言中“值”的语义的自然推广——如果一个 int 可以来回传递,可以被存储,如果一个“对象”( OOP 意义上)可以来回传递,可以进行各种操作,那么为什么函数就不可以呢?
所以并不是“我要 FP,所以我要 closure 和 first-class function”,而是“我要一个正常的,不坑的语言,如果这个语言支持 lexical scoping 和 function,那么它就也应该支持 closure 和 first-class function”,closure 和 first-class function 根本就不是“语言特性”,而是正常的直觉和逻辑的结果。
只是因为很多语言有这两个限制,所以如果你用有此限制的语言入门,你就会先入为主的认为编程语言“就该”是这样的。有一天这个语言更新了,加了这两个“特性”,然后网上一堆 blog 就沸腾了——尽管这不过只是你应有的自由而已。

在 VFX 领域,软件市场大多被大厂商的专业软件所垄断,大多数用户也没什么编程能力。然而就是这么一个行业,最近几年刮起了一股”Node“的歪风邪气——不是 Node.js 的那个 Node,而是”节点“意义的 Node。
这是 Maya 中的 Node: http://i.imgur.com/vuthCQc.jpg
这是 Maya 新加的 Bifrost 特效系统: https://area.autodesk.com/dynamic_resources/area_blog_post_content/3129/html_content/11564262275.jpg
这是 C4D 中的 Node: http://www.llgmotiongraphics.com/wp-content/uploads/2012/09/Pin-Ball-Dynamics.jpg
C4D R20: https://www.maxon.net/fileadmin/_processed_/0/f/csm_nodes_preview_c2598d29d5.png
Softimage ICE: https://4.bp.blogspot.com/-bkZyxX3FmdY/UXdE4Mlii3I/AAAAAAAAoHY/EHFXSKgxsFk/s1600/Softimage+ICE+and+Maya+Fluids+collaboration.jpg
MODO: http://esteldan-3d-graphics.de/contents/Projects_Pics/Car_Rig_3.jpg
这是 Nuke 中的 Node: https://s3.amazonaws.com/pbblogassets/uploads/2013/02/Nuke-Nodes.png
这是 Substance 中的 Node: https://cdna.artstation.com/p/assets/images/images/009/213/162/large/lino-thomas-noodles.jpg
这是 MARI 中的 Node: https://learn.foundry.com/mari/Content/Resources/images/user_guide/node_graph_intro2_680x394.png
这是 Blender 中的 Node: https://pbs.twimg.com/media/ENFE9MyWsAAlAo6?format=jpg&name=large
这是 Clarisse 中的 Node: https://www.isotropix.com/var/img/cms/41/1463651663-lookdev-unified-shading-network-full.jpg
这是 Houdini 中的 Node: https://cpb-eu-w2.wpmucdn.com/coursework.lincoln.ac.uk/dist/7/135/files/2018/10/LTCAO_SS_11.jpg
最有趣的例子是 3ds Max,论技术力,3ds Max 的用户可能比上面的要偏低一点,自动麻将桌在 2011 版本加入了 Node-based Material Editor: https://cdnb.artstation.com/p/assets/images/images/016/919/059/large/paulo-lima-slatematerialeditor.jpg?1553972675,然后又在 2016 加入了 MCG: https://area.autodesk.com/blogs/the-3ds-max-blog/introducing-max-creation-graphs MCG 作者有 FP 背景,直接摊开了说这就是基于 .NET 的“Functional dataflow visual programming language”。
这年头软件不做个 node network,还真不好意思拿出去卖。
(就连 Unity 都在整什么 Shader Graph,Visual Effect Graph )
你可以去搜,VFX 工资并不比程序员高,一搜都是劝转 CS 的。然而这就是人家干活的东西。还是靠简单的东西 Composite 成复杂的东西,只有“表达式”和“节点”,没有“变量”。

最极端的例子是 Excel,在上面的”Monad 之父插手 Java“之后,还有个八卦是”Haskell 之父水过一篇关于 Excel 和 FP 的关系的 paper“( https://www.microsoft.com/en-us/research/wp-content/uploads/2016/07/excel.pdf?from=https%3A%2F%2Fresearch.microsoft.com%2F%7Esimonpj%2FPapers%2Fexcel%2Fexcel.pdf ),其中写到:Indeed, one of the great merits of spreadsheets is that users need not think of themselves as doing “programming”, let alone functional programming — rather, they simply “write formulae” or “build a model”.
MSFT 如果一开始就告诉 Excel 用户他们在”编程“,那估计没人会用 Excel——太多的神秘感都是被建构出来的。不过我倒是相信,如果告诉 Excel 用户他们在做”函数式编程“,可能要比本站的某些用户更好接受——毕竟”编程“这个东西一般大学才学,更别说什么”imperative“了,而”函数“初中就学了。

汉语,英语,德语,阿拉伯语,哪个更难?我作为汉语的 native speaker,在学到大量屈折的语言时,在看到阿拉伯文字时,感受和学习函数式语言是一样的。这也没碍着全世界人讲不同的语言。
(顺便,自然语言作为长期外行设计出来的东西,有很多不 composable 的地方。最简单的就是英语中的大量的不规则动词)
不过我觉得最要命的是,自然语言中每个语言会使用固定的语音集合,不同语言使用的语音集合是不一样的,而在长大之后我就感觉彻底失去了学习新的语音的能力 ...

* Java 一直统治世界,函数式编程就没有未来么?

虽然说完全的函数式编程暂且不太讨好,但是这个问题好像忽视了关于函数式编程的特性一直在不断地进入新的语言和旧有语言的新版本这一事实 ...

用 Bartosz Milewski 的话说:Surely you can’t help but notice that there’s been a steady stream of new functional features INVADING imperative languages. Even Java, the bastion of object oriented ...(注意用词 ...)

Guy Steele 更是直接说"We were not out to win over the Lisp programmers; we were after the C++ programmers. We managed to drag a lot of them about halfway to Lisp."

最后,就算一个语言里一点”函数式“特性都没有,什么 Global Variables are Evil, High cohesion Low coupling, Don't Repeat Yourself, Separation of Concerns 这些设计思想不也是通用的么?见得越多,你就会越重视 Composability 的作用,为了实现 Composability,你会自然地发现有时候有 Side Effect 不如没有,你会自然地认识到理论框架的重要性,这个过程并不需要给你强制灌输一些什么乱七八糟的“教义”。

---

我觉得这几个帖子应该引发的思考是:我们真的有过“在意”这件事情么?再看一遍:C 的成功是 UNIX 的成功,C++ 的成功则是建立在 C 的成功的基础上,PHP 和 JavaScript 的成功是 Web 的成功,Java 的成功是 Sun 和 C++ 的成功——它们都不是“语言本身“的成功。
什么是“语言本身”?考虑这样一个问题,当被问到“XX 语言为什么好?”时,你会怎样回答?
它的 IDE 支持好?它的库多?某个平台甚至某个行业钦定了用它?它的工作多(或者好招人)?它的语法“优雅”?或者单纯它的爹比较厉害?
这些都不是“语言本身”的东西。虽然这些东西会影响大家对其的评价(甚至决定你有没有听说过它)。
但是最有趣的事情是,这些东西不仅会影响对语言的评价,还会影响对“语言本身”的评价,能让人把坏的吹成好的,把好的贬成坏的,所谓 dssq。

说来不怕笑话,Haskell 有个(非官方的?) motto 叫“Avoid success at all costs”。这句话 ... 有点歧义,即“((Avoid success) (at all costs))” 和 “((Avoid) ((success) (at all costs))” 两种解释。但是共同的意思是,Haskell 社区早就看出了“Success”和“语言本身”没有什么关系,Haskell 这个“语言本身”不会向其他因素妥协,好的东西就是好的东西,Haskell 不会因为“Success”的需求而拒绝好的东西,也不会因为“Success”的需求而包容坏的东西。
如果有一天 Haskell 真的“Success”了,那么 Haskell 希望它给人的印象是安全、强大、一致、简洁与高效,而不是“xx 在用 Haskell 修福报”“大家都在用 Haskell”。
虽然说我并不认为 Haskell 真的做到了这一点,也并不认为 Haskell 真的能 “Success”。但是对我个人而言,在这一个网络被封锁和监视,数据被泄露与复制,一言一行一举一动都被记录的时代,能遇到这样一个不忘初心,牢记使命,“pure”的东西,是我的幸运(虽然它有点懒)。

类似的问题不只出现在编程语言领域。“Worse is Better”和“The Right Thing”光在计算机领域就对立了好几十年了。
编程语言天天讲什么 first-class,但是实际上“编程语言”这个东西在它的大多数用户心中什么都不是,连 second-class 都不算。
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 的好么?
另外“相对的价值”不存在一个公认的算法。
1 ... 60  61  62  63  64  65  66  67  68  69 ... 123  
关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2219 人在线   最高记录 6679   ·     Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 · 40ms · UTC 09:17 · PVG 17:17 · LAX 02:17 · JFK 05:17
Developed with CodeLauncher
♥ Do have faith in what you're doing.