微软架构师谈编程语言发展.docx
《微软架构师谈编程语言发展.docx》由会员分享,可在线阅读,更多相关《微软架构师谈编程语言发展.docx(28页珍藏版)》请在冰豆网上搜索。
微软架构师谈编程语言发展
∙微软架构师谈编程语言发展
(一)
∙本文是对微软Channel9中采访几个语言大牛的视频的翻译。
个人感觉这些大牛高屋建瓴,有点有面地谈到了多个语言的发展和语言的相互关系,对于我们开拓视野非常有帮助。
∙本文是对微软Channel9中采访几个语言大师的视频的翻译。
这些语言大师高屋建瓴,有点有面地谈到了多个语言的发展和语言的相互关系,对于我们开拓视野非常有帮助。
由于只能靠听来翻译,篇幅又长,译者程化将其分成了几部分。
这是全文的第一部分,读者可以在文章末尾找到其他几篇的联接。
∙
∙Charles:
好的。
今天我们请到了微软设计编程语言的大师们。
请你们介绍一下自己。
∙(译者注:
Channel9的主持人,从其对话来看,应该是编程出身,对于程序有很好的理解)
∙
∙Herb:
我是HerbSutter,我是VC++小组的架构师。
∙(译者注:
C++标准委员会主席,ExceptionalC++系列的作者,C++领域的大牛人)
∙
∙Erik:
ErikMeijer,我在VB以及C#小组工作。
∙(译者注:
先是SQLServer组的架构师,现为VB、C#组的架构师,从事把CLR、关系数据库、XML数据合为一体的伟大事业)
∙
∙Brian:
我是BrianBeckman,和ErikMeijer一起工作。
呵呵
∙(译者注:
物理学家,天体物理为主,业余时间写程序,包括编译器,自称来自从事影视娱乐业的家族,家里以其从事科学研究为奇)
∙
∙Anders:
我是AndersHejlsberg,我的技术领域是C#。
∙(译者注:
微软的“技术小子”,公认的牛人,C#的主要设计者,.NET框架的重要参与者。
微软之前,Anders是Borland的工程师,TurboPASCAL的主要开发人员,Delphi的首席架构师)
∙
∙Charles:
我们今天访谈主要讨论两个相关的论题:
可组合性(Composability)与编程语言。
作为程序员,当我们构造系统时,总是要面对这两个问题。
你们是创设语法,搭建架构的人。
所以,我想讨论的一点是,你们是如何协调工作的?
三个语言——C#、VB和C++,都在演进,同时又服务于不同的目的,C++更多服务于系统级,C#和VB更多偏向应用层面。
而且,语言在不断创新(译者注:
谢谢ponda的修正)。
这一切是如何形成的?
你们一起工作吗?
你们是如何决定语言的创新的(译者注:
谢谢ponda的修正)?
你们是一起设计,还是想到什么后再与他人共享?
很抱歉提这样的怪问题,请试着回答。
∙
∙Anders:
我想,你说的两种情况都存在吧。
事实上,早在我们做LINQ之前,Erik就在Comega项目做了很多工作了。
在LINQ和Omega之间有很多相似之处,有很多互相影响的部分。
我们一直在讨论相关的问题。
而且,Erik实际也在C#设计组中,所以,我们总是就当前的工作及时交换意见。
VB组和C++组的人也在一幢楼里工作,大家经常碰到一起。
所以,我认为这一切是相互渗透,以及不断聊天的结果。
∙
∙Charles:
但是我的意思是,你们是否也象最终用户一样对自己做出区分?
比如,有的事情在VB中能做,C#中就做不了。
比如,对于VB来说,完全的晚绑定以非常简单的方式实现了,而C#中就没有晚绑定。
为什么VB和C#有这样的不同?
你们有意如此的吗?
∙
∙Anders:
我认为这个问题更多的是历史原因。
我想说的是,我们必须考虑历史因素,尤其当你讨论VB时更是如此。
VB有其悠久而丰富的历史,从一开始,VB就作为晚绑定的语言出现。
(开始时)VB没有任何类型。
很显然,晚绑定对于VB来说有某种核心作用。
但是,从那时开始,VB已经逐步演进为一种更为“强类型”的语言,到现在,甚至你可以把VB看作一种支持晚绑定的强类型语言。
呵呵。
但实际上,这个过程是相反的。
C#从一开始就是强类型语言,而且直到现在,我们都坚持早绑定。
这并不是说我们在未来也不会支持晚绑定,但是,我们很可能以不同于VB的方式支持,而且可能对晚绑定的方式做些改进。
C#是否支持晚绑定其实只是一种选择。
对于老式的弱类型对象模型来说,比如OLE,如果我们从晚绑定角度出发,会比从早绑定角度出发好讨论得多,因为这种对象模型无非就是对象的若干方法的交互,反射,等等。
∙
∙Charles:
这些东西完全可以靠底层帮你完成……
∙
∙Anders:
是的,对,非常正确!
∙
∙Herb:
语言之间的差异在一定程度上是由用户引起的。
对于靠近底层编程的C和C++程序员来说,性能永远都是一个核心和主要的问题。
你可能发现不同语言有不同的特性,但是,更经常的是,你会发现这些不同特性想要解决的都是同一类的问题,比如,“并行执行”。
现在,没有谁能够忽视这个问题,并且,一种语言如果想在未来5到10年保留在主流编程语言的队伍中,这个问题就是无法忽视的,因为这是硬件的发展方向。
我们正处于一个新的时代,50年以来,我们首次在非单核的机器上工作。
任何人都无法忽视这个现象。
因此,就这个问题来说,大家都要处理一些相似的东西,但是,处理方式、语法可能不同,具体的特性也可能不尽相同。
我也相信,不同语言推出同一特性的时间先后顺序也不相同,因为不同语言针对不同的客户群体服务,客户要求的东西不一样,因此,对于特性处理的时间先后顺序并不一致。
就像Anders说的,各种情况都有一些。
∙
∙Erik:
是这样的。
对VB和C#有怎样的差异,我可以给出一个具体的例子。
该例子是“无名函数(或‘lambda表达式’)”。
我们想在VB中也加入这种功能。
首先就是寻找正确的语法。
我们向VB项目组要到了VB的名称表,名称表中的名字支持两种语法的都有(VB和C#)。
但是,这次他们想要更像关键字的名字,而不是C#那样长长的名字,因为他们觉得像关键字的名字更加“VB化”一些。
这里你看到的就是语法上的区别。
但是,在语义上也是有区别的。
当你查看一个大函数内部的,嵌套很深的结构,比如“for”循环的时候,语言是何时、如何处理变量捕获,如何进行实例保护的就非常不同。
在C#中,每次循环时实例都被保护,而在VB中,象JavaScript那样,变量是被隐性提升到函数顶部的。
所以,在变量捕获方面,语义上的区别也存在。
有时这些区别是极其细微的,你必须写非常变态的程序才能看到这些区别。
∙
∙Anders:
每次你写出依赖这样的特性的程序时,我们就能找出成百的Bug。
呵呵
∙
∙Erik:
是啊是啊。
∙
∙Brian:
你逃不出作战室的
∙(译者注:
微软的“作战室”,是产品、程序、测试人员一起对需求、找Bug之所在。
)
∙
∙Charles:
这样看来,大家都同意不同语言在相互影响,不断演进。
对于VB和C#来说,你们有相同的核心——处理引擎,你们必须在CLR的基础上出发,随着CLR的演进而演进。
很显然,C++属于另一个世界。
但是,各种语言要互相影响,你们必须在C#中加点什么来吸引用户,让他们用C#而不是VB.NET,是吧?
应该不止是语法的区别,语言中必须还有一些核心的东西来吸引用户。
∙
∙Herb:
我认为你说的是对的。
但是,我不同意你提出的理由,说我们必须在各自的语言中加点什么特性吸引用户,从而使他们不去使用其他的微软的语言。
为什么呢?
比如我吧,我更加关心使用C++或者C#的用户到底需要什么,我怎样才能帮助他们把工作完成得更好。
也许某处有某种很牛的特性的语言,但我的工作是——怎样才能使客户的工作更成功?
我必须要考虑客户会如何集成,我怎样做才能使客户工作得更好,这也是CLR的核心所在,因为目前已经不是靠一种语言就能做完整个项目的时代了。
∙
∙我怀疑在稍有点规模的实际项目中,是否还有人仅仅依靠一种开发语言。
一般说来,你用脚本语言写点东西,其他语言写工具和组件,系统语言写核心的东西。
你不停地在做集成。
这就带来了我们所讨论的“可组合性”的问题。
因为“可组合性”本质上就是跨语言产生的问题。
当你写Web浏览器时,你不知道某个插件是用C#,C++,某种CLR扩展,还是其他什么写的。
不管如何,这些东西必须一起工作,这就是主要挑战之所在。
因为,要想使这种“可组合性”成为现实,我们必须时时将CLR和CLR以外的东西当作白盒来考虑。
但是,我们这样做的时候又会碰到“锁”的问题。
“并行执行”已经越来越重要了,但是,“锁”是完全不具备组合性的。
因此,这是“可组合性”面对的主要障碍。
我实际上已经转移到另一个话题上了。
总之,对我而言,这更多的是一个语言交互的问题,而非语言竞争的问题。
∙
∙Brian:
我插句嘴。
我在一定程度上代表了用户。
我是个物理学家,同时,我也经常写点小程序,进行模拟和仿真,解决一些数学问题。
要想成功,“可组合性”对我的来说是绝对地重要。
我可以不在乎编程语言,但是我很在乎该语言是否有我所需要的组件。
我有点夸张了,因为我其实还是在乎编程语言的,呵呵。
基本上,我十分愿意使用任何能使我的工作更简单的编程语言。
∙
∙这里,我先戴上顶“老人”帽,谈谈这个世界的历史上,非常少的成功软件之一——数值计算库(译者注:
谢谢drdirac的修正)。
这些东西是N年以前用FORTRAN写的。
几十年以来,人们用这些库解决了许多非常重要的科学问题。
任何头脑正常的人都不会想坐下来从头写一个“线性代数包”(译者注:
谢谢drdirac的修正)或者类似的东西。
有许多数学家终其一生在完善这些软件包。
我们需要的是“互操作性”。
不简单的是互操作性,我们需要的是“可组合性”。
所有人都知道,FORTRAN不支持递归,因为所有的变量都是引用传递。
这就带来了包之间接口问题。
如果你想要集成某种自身内部不支持集成的东西,你就不能再需要集成的两边使用这样同一个包用于集成,这行不通。
呃,我已经忘了最开始我在说啥了,哈哈,我尽讲些物理小故事了。
让我回到C++、C#和VB上。
这些语言我都要使用,我更喜欢C#一些,因为我喜欢它的操作符重载。
为什么我喜欢操作符重载?
因为我做数学计算,类似于四元数和八元数(译者注:
谢谢pongba的修正)的奇怪线代运算,用一个小加号就能够代表那些要进行的一大堆计算。
∙
∙Erik:
伙计,也许你想用的是模板?
哈哈。
∙
∙Brian:
(译者注:
看样子生怕别人认为自己不知道模板)不,我才不想用模板呢。
只要我一用模板,我就会开始想:
喔,模板的预处理器是图灵完备的(译者注:
谢谢drdirac的修正),也许我可以仅用(模板)就实现出一个链表处理库来(译者注:
谢谢pongba的修正)……很快,我就会偏离真正的数学思考。
在应用程序绝对需要晚绑定的场合(比如,那些小的计算模拟器什么的,晚绑定是成功的关键),此时,很自然地,我会选择VB。
至于C++,天哪,大多数时候,C++用来实现其他的语言,做这类事C++很拿手。
在用于科学的环境下,我多次实现过Scheme。
∙
∙总而言之,我就是泛泛谈谈“可组合性”。
∙
∙Anders:
如果你回过头去看看十年之前,会发觉潮流已经逐渐变化了。
当我开始编程生涯时,进入编程这行的学习曲线就是:
学习要使用的编程语言本身。
各个编程语言几乎在每个方面都不相同。
语法是你要学习的很大一部分。
这是以前的事了。
现在,你要学习巨大的框架,这个框架正越变越大,语法只是顶上的一小颗樱桃。
我认为我们在这方面确实前进了很多。
∙
∙很有趣的是,编程语言就像你的眼镜一样,所有的东西根据编程语言的不同,要么看着是玫瑰色的,要么是紫色的,如此等等。
但是,实际上起作用的东西是学习所有的API,学习你所基于的,越来越大的平台或者框架。
如今,学习曲线的90%都耗费在这上面。
掌握了这些,你就可以在C++、C#或者VB.NET什么的之间,毫不费力地进行语言转换,将部分项目使用这种语言,部分项目使用那种,并且找出组合这些语言的解决方案。
相对于以前,实际上是不久之前,这是个主要的进步。
当然,这些能出现,是由于有了通用的类型系统,以及各种语言中的那些抽象。
每种语言之间的差别则是细微的,而且这些差别说不上来有什么特别的理由。
∙微软架构师谈编程语言发展
(二)
∙本文是对微软Channel9中采访几个语言大牛的视频的翻译。
个人感觉这些大牛高屋建瓴,有点有面地谈到了多个语言的发展和语言的相互关系,对于我们开拓视野非常有帮助。
∙本文是对微软Channel9中采访几个语言大师的视频的翻译。
这些语言大师高屋建瓴,有点有面地谈到了多个语言的发展和语言的相互关系,对于我们开拓视野非常有帮助。
由于只能靠听来翻译,篇幅又长,译者程化将其分成了几部分。
这是全文的第二部分,读者可以在文章末尾找到其他几篇的联接。
∙
∙Brian:
是的,在有的情况下,多种语言互相关联。
比如,如今的Windows编程就是一项大苦差:
你必须懂PHP、JavaScript、HTML、XML、SQL等等,要把这些东西全写到名片上,你就只有小小的一块地方可以写自己的名字了。
哈哈哈。
当然,能够同时使用多种语言也是有好处的,至少你可以选择自己喜欢的语法……
∙
∙Erik:
我们的编程语言之所以有差异,还是因为这些语言没有能够统一起来,在语言下面还有若干不一致的地方,我们实际上是被强迫使用不同的东西。
CLR就不一样,基于CLR上面的东西使用相同的库,这些语言之间的排他性就要少一些,你可以选择,而非被迫使用某种特定的语言。
∙
∙Brian:
目前我们做得很多工作就是:
减少大家被迫使用某种语言这种情况。
我们努力改进平台,增加更多的功能,提供更多的.NET库。
值得大家期待喔!
∙
∙Charles:
但是,像VB和C#这样的语言,C++除外啊,就如你们所说,它们确实绑定在某个框架上。
这样的话,在一定意义上是否有其局限性?
我的意思是,让我们谈谈函数型程序,这种程序如何能够融入到我们所谈的巨大的框架中呢?
比如Haskell,有比如流行的F#,它们的结构(与现在的语言)完全不同。
∙
∙Erik:
很有趣的是,传统上,如果我们用“命令型语言”编程,我们的基本成份是“语句”。
“语句”使用并且共享“状态”,从而导致不太好的“可组合性”。
你不能拿着两段语句,然后简单地把它们粘合到一起,因为它们的全局状态不能很好地交互。
这就导致“命令型语言”不能很好地组合到一起。
如果你看看LINQ,就会发现我们已经更多地采用“函数型语言”的风格,所有的东西都基于表达式。
∙
∙“表达式”从其定义来说就是可组合的。
你如何创建一个新的表达式?
你用小的表达式组合出一个大的表达式,你使用lambda表达式,如此等等。
从一定意义上来说,我认为在C#3和VB9中没有什么东西是Haskell或F#中没有的。
这里面有一些深奥的事情,如果你看看Haskell的类型系统,你会发现这个类型系统跟踪程序的副作用。
这就给了你一定形式的可组合性。
现在你虽然不能把有某种副作用的语句组合到有其他副作用的语句上,但是,你可以组合副作用相同的东西。
F#有一个非常强悍的类型推论机制,F#从设计之初就考虑了类型推论。
我们以前也有类型推论,这并非什么新东西,但是现在的类型推论要考虑很多困难因素,比如,重载,这些东西使类型推论很困难。
如果你从这个角度来看,我认为我们已经在很大程度上采用了浓厚的“函数型”风格,并且以相当“可组合”的方式来使用表达式和lambda表达式。
∙
∙Anders:
我想插进来说几句。
我们对“函数型编程”的兴趣并非学院式兴趣。
我们面临的一个挑战,嗯,实际上,当编程语言向前推进时,我们面临两类挑战。
挑战之一是古老的追求——不断提高程序员的生产率,对吧?
将采用和一直以来在采用的方法是——提升抽象的层次,对吧?
给程序员垃圾回收机制、类型安全、异常处理,甚至是全新的“声明型”编程语言,如此等等。
∙
∙在提升抽象层次的过程中,正如Erik指出的,这些“声明型”语言获得了更高层次的“可组合型”。
“函数型”语言之所以有魅力,正是因为你可以做出“没有副作用”,或者其他别的什么承诺,这样一来可组合性就极大地提高了。
不光如此,在我们将如何让多核处理器、多CPU(比如,32个CPU)保持忙碌上,我们也会有所收获。
显然,当我们更多地使用“函数型”或者“声明型”风格的编程时,我们更有可能把运行时框架构建得能更好地发挥多核的优势,更有可能更好地并行化。
如果以“命令型”风格来工作,我们能够发挥的余地就很小,因为你无法预见所有动作——这拿点东西,那放点东西——背后可能带来的影响,所有这些必须串行执行,否则不可预料的事情就会发生。
∙
∙Charles:
这很有趣。
我的意思是,作为程序员,使用了如此巨大的一个处理引擎——比如CLR之后,当然认为这些底层的东西应该被抽象掉。
(译者注:
Charles显然比较吃惊。
)你的意思也是,如果我使用了一个4核的机器,运行时的引擎应该有能力负责分配进程(在CPU上的分配)。
∙
∙Anders:
嗯,你这样想很正常。
但是,CLR以及我们的工业中目前绝大多数的运行时,都是“命令型”引擎,其指令集都是相当传统的,比如,堆栈增长啥的,以及拥有易变的状态,包括易变的全局状态等等。
在此之上能够进行“函数型”编程,因为“函数型”编程从本质上来说,是“命令型”编程所具备的能力集的一个子集。
现在我们想做的是最大化这种灵活性,但其实不过也就是让“函数型”能力子集越来越相关,使其越来越主流化而已。
∙微软架构师谈编程语言发展(三)
∙本文是对微软Channel9中采访几个语言大牛的视频的翻译。
个人感觉这些大牛高屋建瓴,有点有面地谈到了多个语言的发展和语言的相互关系,对于我们开拓视野非常有帮助。
∙本文是对微软Channel9中采访几个语言大师的视频的翻译。
这些语言大师高屋建瓴,有点有面地谈到了多个语言的发展和语言的相互关系,对于我们开拓视野非常有帮助。
由于只能靠听来翻译,篇幅又长,译者程化将其分成了几部分。
这是全文的第三部分,读者可以在文章末尾找到其他几篇的联接。
∙
∙Herb:
我想,我们有必要在“函数型”编程领域做一个进一步区分,将其划分成两个部分。
我非常同意Anders和Erik的意见。
我不太同意的是这样的措辞:
我们之所以继续使用“命令型”编程语言,是因为这是大家目前所能理解的;通用程序员目前的工作并未取得巨大的成功;市场对于“所有的东西都是表达式,所有的语言都应该是表达式类型的语言”这样的理念已经非常接受了;“函数型”语言是“串行执行”的好药方。
我们要想使“函数型”语言运转良好,关键的并不是处理好基本的表达式问题,而是处理好lambda表达式和副作用的问题,关键是能够将表达式作为第一级的编程要素来使用——LINQ也是最近才在做,关键是能够指出lambda表达式和Closure(译者注:
函数型编程语言中的一个概念,可以方便地组合函数,返回函数)的副作用。
∙
∙实际上,最后这点目前是缺失的(Anders也附和着:
对,对)。
这些东西在“命令型”语言中也是要处理的东西。
我为什么提这些?
因为我觉得说“函数型”语言是方向,目前的“命令型”语言不够好,因此是垃圾,必须要抛在脑后,全面采用“函数型”语言这样的说法不对(译者注:
呵呵,对Anders的说法有点急了,毕竟是泡在C++上,对C++有感情的人)。
∙
∙我认为,对于“函数型”语言能够帮助程序员完成哪些工作,目前还不太明了。
比如,能够用它写通用代码吗?
能够用它系统级代码吗?
当然,“函数型”语言有不少我们能够应用的好东西,比如lambda表达式,比如Closure,C#借鉴了,C++也在借鉴,这些语言因此增色不少。
关于“函数型”语言还有另一个问题,那就是有两种类型的“函数型”语言,一种是没有副作用的,因此就没有共享的易变的状态的问题;一种是人人都在使用的,对吧(译者注:
显然Herb认为“没有副作用”的理想情况是不太可能的)?
因为你不太可能说,“瞧,我是完全并发安全的,因为每次我从XX(译者注:
听不清)向量中得到一个拷贝,或者我使用XX(译者注:
听不清)元素的时候,我都是取得一个拷贝”。
确实不错,这里是没有共享的易变的状态,但是是否能够完全并发安全则不一定。
∙
∙Anders:
是的。
我的意思是,在类似C#或VB这样的“命令型”编程语言中加入“函数型”结构,能给我们提供“以函数型风格”写库的能力,从而我们就能够非常明确地说,如果你能保证传入的lambda表达式是纯粹的函数,我们就能保证正确地把它分散到若干个线程或者CPU上,最后把它综合起来,给你一个正确的结果,我们能够保证代码运行得更快,同时你还不用作任何编码上的修改。
如果你在写一个大大的For循环,我们永远都不可能保证做到前面所说的,此时,“函数型”编程能够提供给你的是一系列表达式,再加上“把代码当作参数传递”,“类型推论和泛型编程可以正确地绑定所有的类型”这些特性,这样你就能更方便地编写“可组合的算法块”。
∙
∙Charles:
这样一来不就削弱了抽象吗(译者注:
Charles可能想的是程序员不需要再关心“可组合性”,语言和运行库应该保证这件事,而现在听起来并非如此)?
∙
∙Herb:
呃,我很同意Anders的意见,我想指出的是,当前所有的语言都有意不保证“没有副作用”。
之所以如此的原因是,除非所有的语言都添加一些机制让程序员可以清除副作用,我们这些做语言的人不敢打这个包票。
但是,添加这样的机制涉及到众多参加者,大家一起思考、讨论什么是最好的方法的过程会很漫长。
我们所做的是相信程序员,因为我们自己不知道。
然而,程序员在很多情况下也不知道,因为他写的函数要调用其他的库。
这里“可组合性”又浮上水面了,程序员根本不知道他用的库有怎样的副作用。
一般说来程序员会再增加一层间接性,但是问题依然存在,没有人能够清楚地知道副作用,除非他拥有涉及到的所有的代码,这就是难题所在。
上面这些讨论对“锁”也适用,因为“锁”也是个全局问题,对于“可操作性”是个障碍。
∙
∙Brian:
(译者注:
在Herb说话的时候已经很着急地想说了几次)在这点上Haskell做得很好,Haskell是“永远没有副作用”的范例。
∙
∙Erik:
是的,但做到这点的过程也是痛苦的,因为并非所有的情况都一目了然。
一旦你的(库)代码有副作用,而且因此使程序员的代码必须按照某种顺序执行(因为副作用的关系,该程序必须先干某事,再干某事),某种意义上你在用汇编语言编写东西,因为程序员将不再能用“表达式+表达式”的方式来写代码,他必须决定先对某个表达式求值,再对另一表达式求值,再把值加起来。
因此我认为我们在这点上干得还是不够漂亮。
∙
∙Brian:
现在,我们在“流库”上有例子。
好消息是,我们已经有Haskell向你展示如何以“可行性”方面的代价,换来用绝对纯粹的方式来做事。
当然,除Haskell外我们有各种“杂牌”语言。
呵呵!
∙
∙(众人均乐)
∙
∙Charles:
这是个供研究的语言吗?
∙
∙Brian:
是的,我们将它设计为供研究用。
∙
∙Anders:
没有纯粹的好或坏,我认为,虽然进展缓慢,我们仍然快到一个令人满意的中间点了。
我完全同意说,如果我们确实能够保证函数的纯粹性,生活将会非常美好。
最终我们必须要做到。
∙
∙Brian:
在研究领域,大概有20多项工作与此有关——契约语言,契约和限制,等等。
∙
∙Erik:
但是,不少的副作用也并非坏事,如果我的函数使用了一个局部变量,这就是使用了一个状态,但是,函数本身还是纯粹的。
如果你想要完全避免副作用,我觉得会非常困难,一些东西可以是局部不纯粹而整体纯粹的。
∙
∙Herb:
回过头,让我们从整体上看看“可组合性”。
让我吃惊的一件事是,很多时候,人们甚至都没有意识到这是个问题。
他们并没有意识到自己实际上经常碰到