Shared posts

12 Apr 11:37

How to create a hardened API for middleware

by John Ratcliff

Being a middleware developer is challenging enough to begin with, especially since it is such a tough business model, but support can make your life a living hell.  The advantages for the customer are clear, developing software is expensive and risky and licensing middleware reduces risk by adopting a proven piece of technology.  For the licensee, their software engineers will have to spend less time reinventing the wheel, doing research and development and can instead focus on the specific product they are trying to create.  


Today’s modern game engines make heavy use of licensed middleware.  Gone are the days of the ‘not-invented-here’ syndrome.  Sure, there are a few holdouts who want to do everything their own way, but they are few and far between.  Why would you write your own video codec, compression engine, or sound system?  What would be the point?  These technologies are practically commodities at this point.


Some pieces of middleware are a bit more controversial.  In particular are libraries designed to solve physics and graphics problems.  Rarely do these ever just drop easily into an integration.  There are often unrealistic expectations on the part of the customer.  Frequently they don’t realize that even though they have licensed this middleware they still must account for the time it takes to integrate it, as well as learn how to use it to implement specific features their product requires.


The biggest problem middleware providers face is support.  This goes beyond simply trying to explain to a developer how to use the API, it is the fact that now that your software is deeply embedded in someone else's product, and if their product crashes anywhere inside of your code, then, Houston, you have a problem.  In fact you may have a very, very, very, big problem.  Even if the developer passed complete garbage data into your middleware, even if they abused your middleware worse than Bobby Brown, if it crashes anywhere in your code it almost always instantly becomes your fault.  
Once your middleware has become part of someone else's product, you are for practical purposes usually an unpaid member of their development team. You may often find yourself in the situation of being used as a scapegoat for why things are going wrong. If a developer can't get something to work the way their manager wants it to, or there are other problems, they can just blame the middleware since those engineers aren't around to defend themselves. Frankly, these kinds of political issues arise frequently. 

Something else to be aware of is that, frequently when people license a piece of middleware they may not understand the underlying technology very well themselves.  Often junior level programmers may be trying to use your API in ways that it was never intended to be used or you never could have imagined.  They may call your API from the most unexpected places and in the most unexpected ways.  Once your middleware has been licensed to several customers you may feel like you never get to write a new line of code ever again.  You may feel as though you are under constant attack and bombardment from your customers which, in actuality, you probably are.  


Here are some of the issues you will deal with in support.


  • Their product will crash in your code, which makes it immediately ‘your fault’
  • They will invoke your API from a myriad of different threads, even if your documentation says they shouldn't do that.
  • They will pass bad pointers into routines and blame you when the code crashes.
  • They will pass in bad floating point numbers which will then, in turn, produce infinities, and expose bizarre behavior and crashes.
  • They will pass in what are technically considered ‘valid’ floating point numbers but they will be wildly out of range, either massive in size or incredibly tiny; creating all kinds of pathological behavior.
  • They may create hundreds of thousands of instances of objects in a system designed to handle no more than a few hundred.
  • They may set constants and other tuning parameters to outrageous values that cause the system to consume insane amounts of CPU and then blame you because your middleware is so slow.
  • They may create thousands of objects but never release them.
  • They may create thousands of objects all at the origin since, at object creation time, they don’t know their position yet.
  • They will pass in normals that are not normal.
  • They will pass in matrices which are invalid.
  • They will pass in quaternion rotations which are completely invalid.
  • They will pass in triangle mesh geometry with degenerate faces, and all manner of corruption.
  • They will pass in convex hulls which are completely inappropriate for real-world use.
  • They will report impossible to reproduce problems.
  • They will report problems which only occur, at random, after their product has been running for 10 hours straight.
  • They will report problems but give you no way to debug or support them short of getting on an airplane and flying physically to their location.


I could go on and on, but you get the idea.  Like I said, the business model for middleware is challenging enough, but the support nightmare makes it a largely thankless task.  What is a middleware provider to do?


Treat your customer as your enemy.  Imagine your customer is actively trying to write malware to attack your API.  While they may not consciously be doing that, in the practical case that is essentially what is happening.


There is a solution to this problem, but it is rather extreme.  You do not, necessarily, have to go the full extreme route to address these support issues.  You can do something in between and live with the risks that you leave yourself open to.  The rest of this article is going to discuss the extreme view of implementing a fully hardened API and what that requires.

I also do not talk a great deal about unit-testing here. Obviously you should have a robust set of tests and samples for your SDK, that is a given. However, unless your API is very simplistic, it is doubtful that you will even begin to scratch the surface of how end-users will use your system. Yes, write as many tests as you can but also realize the limitations of this testing coverage.

One thing I strongly recommend is that if your middleware is designed to be used by large scale development projects, then do not release it until it has been thoroughly integrated and tested in at least one large scale development project. Sandbox and sample testing is so unrealistic as to be relatively useless.

In my career as a software engineer I have seen numerous products released that were designed to help developers with some set of problems that, as soon as you applied it to a real-world massive project, rolled over and died immediately.


To be clear, I am not saying that you should do all of these things.  What I am saying is that if you wanted to make a perfectly hardened API then you would have to do so; especially since anything less does expose you to some element of risk. The actual solution is probably to adopt some of these recommendations, but not all of them everywhere at all times.


The following rules apply ONLY to your public API.  Internally, you can code however you wish and to any standard that you wish.  These guidelines only refer to how your end-user, your customer, is allowed to interact with your middleware.  


Rule #1 : Never use pointers, EVER!  If your API ever accepts a pointer, anywhere, then you are implying a level of trust with the developer that is not warranted.  You are trusting that the developer is only ever going to pass you a valid pointer.  You might even think, hey, this is a pointer to an object that I allocated so it must be safe, but that still doesn't cut it. You would now be trusting the application to pass you back, unmolested, the same pointer you created and you don’t know that this is true.  The simple fact is that you cannot allow the end user to pass you a pointer that your code then operates on.  If you do, and they pass you a bad pointer, then your code will crash when you dereference it and, when your code crashes, it’s not their fault, it’s now your fault.  This is more than just testing for null pointers which, of course, you should do.  This is about any potentially random value a pointer could be assigned to.  The pointer could be bad because it was in an uninitialized data section, it could be bad because it refers to an object which has already been deleted, or it could be bad simply because the application developer trashed it.


The same thing goes for references.  References are pointers too, so don’t think that somehow lets you off the hook here.


You might be wondering now, if you cannot ever use pointers, what do you use instead?  Instead of pointers you should use ‘handles’.  A handle is simply an integer value which refers indirectly to a pointer.  In any use case where your API might normally return a pointer, instead you should return a handle (integer) instead.


When the user passes a handle back to you (instead of a pointer) you can completely and safely validate this handle with zero risk.  Your own SDK will maintain a handle table, that maps an integer to a pointer.  First you make sure that the integer passed in is within range and represents a valid handle/pointer mapping.  If it does not, then you simply do not attempt to operate on it, and return an error code and/or issue an error message to the developer via whatever logging mechanism your SDK implements.  


Even if the handle passed in is valid, it may not necessarily be pointing to the correct type of object, so your code should validate this as well before dereferencing it.


Rule #2 : Never use C++ non-static methods EVER!  This may not seem obvious at first, but this is actually just an extension of Rule #1 to never use pointers.  It is pretty much impossible to use C++ without using pointers, unless all of your methods are static which wouldn't really make much practical sense.  Whenever you call a method on a C++ class there is an implied pointer known as the ‘this’ pointer.  If any part of your API uses C++ methods it violates the first rule to not use pointers, for all of the same reasons cited above.


You can use C++ classes or flat structs so long as none of the member variables contain pointers or non-static methods.  


Rather than further confusing things, the best rule is to make your entire API be straight vanilla C.  These functions (not methods) will be physically linked into your application.  The customer will get a link error if they use the API incorrectly, but there is no risk at runtime.


It is important to remember, once again, that this is only in reference to your public customer facing API.  Your actual implementation code can use all of the C++ and object oriented goodness that you want, you just can’t expose any of that to the end user.


Rule #3 : Never use the STL, Boost, or other dependencies in your public API.  This is basically the same as rule #2.  These APIs are all based on C++ and imply way too much trust about the format and validity of the data being passed.  Once again, you can use them in your own internal implementation code if you wish, so long as you are willing and ready to accept any risk that they pose.


Rule #4 : Never trust a floating point number passed into your API : You would be amazed at the world of trouble that this can cause.  Floating point numbers are almost worse than garbage pointers being passed into an API because they are more insidious in some ways.  If you dereference a bad pointer, you will generally get a crash right then and there.  However, if you reference a garbage floating point number, instead of getting a crash, you will likely just get a garbage result.  One bad floating point number used in a calculation will then infect the result and get passed on down the line.  (Infect is actually a good way to put it, bad floating point values are like viruses, they infect the rest of your systems making each one sick and spreading it to others.)

Soon your entire system is riddled with garbage floating point numbers throwing every calculation off.  None of these may even necessarily cause immediate crashes but, instead, you get pathological behavior and things may simply stop working or behave in some very, very, bizarre ways.   One example of how this sort of thing might cause an infinite loop is as follows.  Let’s say that you have some sort of a hierarchical bounding volume tree that you traverse.  Now, let’s say you are traversing it but with a bad floating point number.  This could break the tree traversal in all sorts of unexpected ways since floating point compares may always fail when the expected behavior is that one side is always greater while the either side is always lesser.  This could lead to infinite loops or missing results.

Another chance for bad floating point numbers to crash your code is if you convert a floating point number back into an integer which you then use to index an array with a bogus result.


By default most applications do not generate an exception when a bad floating point number is accessed.  Exceptions can be enabled, but this can have unintended side effects.  In general it is not valid for your middleware to change a global system wide setting like the floating point control word.  You might enable exceptions to catch problems in your own code but then the application you are running inside of might suddenly start crashing or, worse yet, just turn them back off again.  


Another thing to be aware of is how the floating point unit handles numeric precision and rounding behavior and the fact that it is not always preserved. It can be changed under your feet without you knowing about it!  For this reason your API also needs to confirm that the floating point unit is always in the precision and rounding mode that your SDK expects and if it is ever changed, you should issue an error message and stop operating on the input data.  


Since your code should never change the floating point unit state and, more to the point, our goal here is to never, ever, crash, then enabling exceptions that we do not handle gracefully is not really the solution.


Instead, the recommended course of action is to verify that each and every single floating point number passed into your API is valid by using the ‘isfinite’ check in the standard library.


In addition to making sure that all floating point numbers are valid, you should also make sure that they are within a ‘reasonable’ range.  Just what a reasonable range means is specific to the context of your problem space.  Let’s say this is for some kind of a game which has a world size that is never larger than 8 kilometers and is represented in meters.  In this example any floating point number representing a ‘position’ that is outside of that range would be considered invalid.  


Another thing to check for is normalization.  Let’s say one of the values being passed into your API is supposed to represent a vector normal.  All three floating point numbers could be perfectly valid, but that doesn't mean it is a valid normal.  The same thing goes for matrices and quaternions.


To deal with these problems it requires that you perform full floating point validation not just for invalid numerics, but also numerics which are within range and appropriate for the use case.


By now you are probably wondering, isn't all of this going to be incredibly expensive and degrade performance?


The answer to that question is, probably yes.  Here are ways to deal with this.


  • Your SDK can have multiple build configurations.  One which is hardened to an extreme degree, a ‘checked’ build as it were, and one which does no, or fewer, checks and is typically called a ‘release’ or ‘shipping build.
  • Your SDK can have different levels of validation.  You can even implement this as a run-time option; essentially by having each API call redirect to a different implementation that does the degree of validation required.  This solution is a bit more difficult to implement but probably worth it, as it allows the developer to swap out which degree of checking they perform simply by changing a .INI file without requiring a different build of the game.


Rule #5 : Never trust any other parameter passed into your API : While we have discussed the validation of floating point numbers, the same degree of validation is required for all other parameters, whether it is an integer, or enumeration, or the contents of a data structure.  All parameters passed into any method should be completely validated as much as is necessary to make sure they are in a reasonable range and express expected values.


Rule #6 : Always be thread safe!  In this day and age you can never assume that any API call is not going to be interrupted by a call to another function, or even the same routine you are already executing, but from another thread.  All API calls should have, at the minimum, a mutex to make sure that you don’t get burned by reentrancy and may, in fact, need a thread safe command queue if this is a common usage pattern.


Exceptions to the Rule


The rules above are required to create a nearly perfectly hardened API which can never, ever, be crashed because the user submitted bad input data.


It is also possible that even though the API is hardened, the application could still just stomp directly on some of your internal state data.  There are some things you can do to detect and protect against this sort of thing but it is generally considered impractical.  Short of making your SDK act as a kernel mode driver, some of these crazy abuses cannot be easily guarded against.


It is possible that they could still abuse your API by making unreasonable demands and requests on it, causing it to run very slowly or otherwise consume a great deal of CPU. Your code could have checks to detect this situation and allow it to bail out if a particular routine takes too long to execute and treat that as a warning or error.


At the end of the day, no matter how much you want to harden your API, these rules may ultimately be impractical.  If the user needs to add millions of data items and the only way to do so is by making a million discrete API calls which are fully validated, this simply may not work.


You can break any of the rules above, just so long as you are aware of and willing to accept the risk that comes with it.


You could take the approach of having the vast majority of your API be fully hardened yet expose just a handful or routines which require high-speed data transmission to occur.  This would make things mostly safe most of the time and the non-hardened API could be thoroughly documented in detail and would hopefully be a risk you could manage.


Features to make your life easier


There are a couple of features your SDK probably needs to keep your life from becoming a living hell.  As soon as a customer has a problem that only happens in their product and cannot be reproduced outside of it, you will find yourself in a difficult situation.  Usually you will not have source code access to their product and, even if you did, learning how to sync, build, and run their product could consume days, if not weeks, of your time.


There are a few key features you need to implement in your SDK to help deal with this.  Most of these features will be much easier to write so long as you have used the API hardening rules above.


  • Logging : Your SDK should support extensive logging features.  This goes beyond simply logging warnings, errors, and other poor usage patterns but even go so far as to log the contents of every single API call.  You can, and should, support multiple levels of data logging detail.  The data-logging should be easily human readable and act as a useful aid in diagnosing runtime problems. A lot of times just a quick look at a log can reveal to the support engineer, holy shit, they are calling the API this crazy way!!
  • API record / playback :  This is probably the single most important piece of technology you should provide.  No matter how long it takes you to implement a robust record and playback system, it will save you far more time than you would otherwise spend on product support.  The concept is simple.  Every single time the application makes a call into your SDK, you write the entire contents of that API to a stream (flushing the stream immediately).  This process has to be so fast that it does not interfere with the operation of the actual product.  Once you have a recording you should also have a separate application of your own which can play back that data.  Imagine that your customer has a difficult to reproduce crash that only happens semi-randomly after running their product for an hour.  Your hopes of debugging this in the traditional way would be bleak, but with a sufficiently powerful record and playback system there is real hope.  The QA person would run the product with the recording feature enabled up until the problem occurs.  It could be a crash (which really should not happen if your API is sufficiently hardened) or it could be a performance or behavior problem.  They should be able to annotate the recording with metadata tags as well (text tags which indicate what was happening in the application over time).  Once they have captured this file they can simply send it to you.  With that file, and without needing access to their source code or product, you can reproduce this issue exactly and reproduce it deterministically every single time over and over again. No matter how much time you may spend developing this API capture and playback feature all of that effort will be paid back the first couple of times you can fix an issue using it.  Remember that the API record/playback system must be able to take multi-threading into account.  Many problems which happen in modern day software results from issues related to multithreading and reentrancy.  For really tricky multi-threading issues there is a chance that a playback will not be able to reproduce the problem, so you should be aware there are some limitations with this approach.
  • Debug Visualization : Depending on the type of SDK you are creating it may be valuable to have a debug visualization tool which can render a meaningful picture of what is happening ‘inside’ of your engine.
  • Performance Tracking : Often times the reports of problems you will get from customers is not that your SDK is crashing but, instead, that it is consuming too many resources; be that CPU, GPU, or memory for example.  Embedding robust and detailed performance tracking into your product can be invaluable.  In this case, rather than writing your own from scratch, I strongly recommend you consider yet another piece of middleware, which is Telemetry from Rad Game Tools.  It is an incredible tool which will allow you to easily embed many of these logging, analysis, and visualization features.




Conclusion


In conclusion I am not saying that you have to do these things, or even that you should.  The point of this article is simply to present the extent and degree to which an API can be hardened against attack, either intentionally or not, by end users.


As extreme as all of these things sound, it is important to remember the real-world problems they are designed to address.  If you develop a non-hardened API to be used by a general population of not necessarily expert programmers, you are going to run into a lot of problems.  If you would like to spend more of your time developing cool technology and less of it in hopeless support tasks, tasks which most of the time are not even issues with your software, then you may want to consider some of these strategies.


If a lot of this may sound hypothetical but I assure you that it is not.  I have developed a number of APIs in my career targeted at various audiences.  Here are the real problems I have encountered, quite frequently in fact.


  • I have frequently been passed garbage pointers into routines and told it was my fault when it crashed in my code.
  • I have frequently been passed bad floating point numbers.
  • I have frequently been passed unreasonable floating point numbers that were far out of practical range.
  • I have frequently been passed invalid parameters, out of range and otherwise bogus.
  • I have frequently had an API get blindly called from all kinds of different threads in ways I never imagined anyone would, or could, do so.
  • I have frequently had people create insane or unreasonable content that caused performance to tank.
  • I have frequently had reports of problems that only happen in the context of someone’s larger product with no repro case made available.
  • I have frequently had people pass in garbage triangle mesh data, invalid normals, bad matrices, bad quaternions, and all manner of invalid and incorrect input data.


So, as you can see, none of this is hypothetical.  This is the life of a middleware developer.  Let’s be frank, frequently when someone is licensing a piece of technology it’s because they don’t actually understand it that well themselves.  They think that if they license this piece of middleware it will magically solve all of their problems.  However, all middleware has integration costs and requires a good conceptual understanding of the underlying technology so that it is not abused and misused.


If I was just writing something for myself and my personal colleagues, I probably would adopt only a few of these recommendations.  However, if I was developing a piece of middleware going out to a wide audience that I had to support then I would probably do everything recommended here and more.


I hate to use the analogy but really I don’t see that I have any choice.  You wouldn't have sex with a complete stranger without using a condom, you probably shouldn't let the users of your API do so without an equal degree of protection.

Postscript

I should probably address the elephant I left standing in the room here. Some people reading this are probably aware of middleware that I have been involved in and worked on. I would guess that those people are probably wondering how come the middleware I work on doesn't adopt hardly any of these recommendations. The answer to that is that it's not my middleware. I didn't create it and I don't own it. These recommendations are based on work that I have personally done in the past and, perhaps, some work I intend to do in the future. I hope this clarifies any questions you might have had on this point.

07 Apr 17:44

自然的阴谋论和不自然的科学思维

by 同人于野

你相信“巧合”吗?

当然相信。世界非常大而且非常复杂,每天要发生很多很多事,绝大多数事情之间并没有什么因果关系。可是正如人很善于在本来没有规律的地方寻找规律,我们也非常擅长在本来没有联系的事情中发现联系,并且用一个简单理论对这些事情进行解读。

我的理论没有什么进一步的证据,而且我也不需要什么进一步的证据,但是我的理论可以解释这些看似“自然”其实“不自然”的事件,我发现其背后有一个不可告人的目的。这种解读,就是阴谋论。

 


1. 美国的阴谋

比如2014年3月马航MH370航班事故发生之初,整个事件还在被定性为“失联”期间,互联网上充斥着各种阴谋论。其中有一个理论说,马航失联其实是中美“两个大佬”较量的结果,而且中方目前稳操胜券。这篇奇文把近期的国际形势 — 包括乌克兰局势紧张、日本右翼政府的态度软化、朝鲜半岛的微妙变化、西方阵营出现的裂痕和泰国局势未能朝预期方向发展 — 和国内形势 — 改革进展、两会前金融波动、昆明火车站恐怖袭击和河南隧道爆炸事故,都通通联系在一起,认为只有全盘考虑这些因素,还要结合“三年前发生在菲律宾马尼拉的人质事件与奥巴马即将在四月展开的访问活动”,才能理解一架客机为什么会失联。

如果你觉得这个逻辑实在太不可思议,那是因为你不经常上网看时事军事论坛。有人专门写这种文章。他们旁征博引无所不知,从国际政治讲到国内形势,最后主题归于两点:第一,所有坏事,都是国际敌对势力故意针对中国搞出来的;第二,所有好事,都是中国政府巧妙安排的。总而言之,中央正在跟美国下一盘很大的棋。

当然,也有人认为中美两国政府都不是世界上最强的力量 — 真正的大boss是罗斯柴尔德家族。我曾经兴冲冲地买过一本《货币战争》,而且真的被书中前面的阴谋故事所吸引,一直看到罗斯福才把书扔了。

中国流行国际大棋论,美国则流行专门针对美国政府的阴谋论,而且相信的人很可能并不比中国少。芝加哥大学的研究人员最近针对以下六个最流行的医学阴谋论对1300个美国人进行了调查

  1. FDA为了医药公司利益而禁止自然疗法;
  2. 政府明知手机致癌而不作为;
  3. CIA故意让美国黑人感染艾滋病毒;
  4. 转基因食品是削减人口的秘密手段;
  5. 医生和政府明知疫苗会导致孤独症;
  6. 公共饮用水加氟是化学公司排污的手段.

结果发现,49%的美国人至少相信其中一个,18%的美国人相信三个。所以这其实是一个人们普遍相信阴谋论的时代。

相信阴谋论很可能是人的一个思维本能。人们总是希望能给复杂而混乱的世界找个简单的解释,这个解释就是有某个强大的力量,怀着一个不可告人的目的,在控制一切。据Kent大学的几位心理学家研究 [Scientific American (September 2012), 307, 91. ]发现,相信一种阴谋论的人,往往也会相信其它阴谋论,甚至是互相矛盾的阴谋论。越相信戴安娜其实并没有死是假死的人,越容易相信戴安娜其实是被谋杀的 — 反正政府有些事没告诉我们!

所有这些阴谋论都有一个共同的思维模式。这个思维模式就是不承认巧合,不承认有些事情是自然发生的,认为一切的背后都是有联系,都有目的。

这种思维有道理吗?我们必须承认这个世界上的确有阴谋,不可能所有政府在任何时候都是无辜的。但是阴谋是有限度的。根据Business Insider一篇文章考证,以下这个九个美国政府的阴谋,是真实发生了的:

  1. 禁酒令期间,美国政府曾经故意往工业酒精中加入某些化学品使其不能被转化成可用于兑酒的普通酒精,这些化学品是致命的,而且造成超过一千人死亡;
  2. 公共卫生机构打着治病的旗号征召了感染梅毒的黑人来做研究,却从未真正给人治疗;
  3. 超过一亿美国人使用的小儿麻痹症疫苗被一种病毒感染,有研究认为这个病毒会导致癌症,但政府并没有采取有效行动;
  4. 导致越战全面升级的“北部湾事件”中的某些冲突其实并未发生,是美国故意夸大来作为战争借口;
  5. 军方曾经计划在国内搞恐怖袭击嫁祸古巴 — 未能实行,但的确计划了;
  6. 政府曾经在受试者不知情的情况下拿美国和加拿大公民做毒品人体实验;
  7. CIA曾秘密在太平洋打捞一艘苏联潜艇,其上有三颗带有核弹头的导弹;
  8. 美国政府曾经违反禁运协议向伊朗出售武器,并把钱用于资助尼加拉瓜武装;
  9. 海湾战争前夕,一个十五岁的科威特女孩在美国国会作证,说她目睹了伊拉克士兵把婴儿摔死在地上。事后证明这个女孩是科威特驻美大使的女儿,整个作证是公关公司导演的。

跟前面那六个最流行的(仅限于医学相关的)阴谋论相比,这九个真正的阴谋坏到了什么程度?光难度就至少低了一个数量级。

正如林肯说“你可以在所有的时间欺骗一部分人,也可以在一段时间欺骗所有的人,但你不可能在所有的时间欺骗所有的人。”想要完成一个阴谋非常困难,而且就算做成了也有很大的曝光风险。一个整天在军事论坛看阴谋论的人,如果看了这些真实发生了的阴谋,可能会觉得美国政府原来没有想象的那么坏。事实上,维基解密网站曝光了一批美国政府的外交密件之后,《金融时报》专栏作家吉迪恩·拉赫曼发表评论文章认为这反而提高了美国政府的形象,他说

无论是欧洲和拉美的左翼人士,还是中国和俄罗斯的民族主义右翼人士,长期以来都一直近乎肯定的认为,美国人关于其外交政策的一切公开说辞,只不过是在为某种秘密议程打掩护。该议程可因兴趣而变,或者为了照顾大公司(哈里伯顿(Halliburton)!)的利益,或者为了颠覆某个左翼政府,或者为了削弱对手国家。无论美国的秘密议程是什么,它肯定是存在的——只有那些天真到愚蠢的人才不这么认为。

 

……

然而,经过长达两周的曝料,维基解密非常充分地揭示出,美国在任何特定问题上所持的公开立场,通常与非公开立场并无两样。目前仍有许多电报尚未曝光,或许其中还潜藏着一些惊人的事件。但是,过去两周曝光的文件罕有证据证明,美国外交政策中存在耍两面派或背信弃义之处。世界各地的阴谋论者对此一定非常失望。

 


2. 合理性与可能性

 

想要对任何事情的真伪都给以正确判断是不可能的,我们只能在有限的条件下合理评估每件事的可能性。阴谋论之所以不足信,并不是因为我们不应该质疑政府 — 每个人都有权质疑政府 — 而是因为其成立的可能性很低。

诺贝尔经济学奖得主丹尼尔·卡尼曼在《思考,快与慢》这本书中总结了人的种种认知偏误,其中有一个偏误,在我看来非常适合说明阴谋论思维的错误。卡尼曼说,假设有一个叫Linda的单身女性,31岁,直率而聪明。作为哲学系学生的她曾经非常关注歧视和社会公正,并且参加过反核游行。根据这些情况,请你评估以下对Linda的种种描述之中,各自的可能性大小排名:

  • Linda是个小学老师;
  • ……
  • Linda是个银行出纳员;
  • Linda是个买保险的;
  • Linda是个热衷于女权运动的银行出纳员。

结果几乎所有受试者都认为“Linda是个热衷于女权运动的银行出纳员”的可能性,比“Linda是个银行出纳员”更高。但这是不对的!A和B同时成立的可能性小于等于A成立的可能性,这是概率论的常识啊!

如果你答错了,不要自责,因为这个问题就连斯坦福大学决策科学专业的博士研究生都有85%的人答错。卡尼曼最后干脆把其他选项都拿掉,就问受试者“Linda是个银行出纳员”和“Linda是个热衷于女权运动的银行出纳员”哪个可能性更大,仍然有85%到90%的本科生答错。这个错误的原因在于人们搞不清“合理性(plausibility”和“可能性(probability”的区别。“热衷于女权运动”增加了对Linda描述的合理性,但是却降低了可能性。另一个类似的例子是这样的:

  1. 明年北美会发生一场淹死一千人的大洪水;
  2. 明年加州地震,导致一场淹死一千人的大洪水。

2)比 1)更合理,但是显然,它的可能性更低。增加细节也许可以增加合理性,但是一定减少可能性。

现在我们可以回头谈阴谋论了。以下两个论断中,哪个可能性更高?

  1. 昆明恐怖袭击过后不久,又发生了马航失联事件;
  2. 大国博弈,导致昆明恐怖袭击过后不久,又发生了马航失联事件。

 


3. 目的与科学

世界非常复杂,很多事情似乎简直不可理解。为什么明明准备的很好的比赛也会输?为什么一个好人偏偏死于车祸?阴谋论可以让我们对这些事情至少找到一个理由。我们不但找理由,我们还找目的。

近代著名儿童心理学家让·皮亚杰(Jean Piaget)说,在儿童成长的某个阶段,他的世界观会有两个基本点。一个是“animism”,万物有灵。他认为每个物体都是活的,比如汽车之所以不走是因为它累了需要休息。更重要的是,东西有它自己的意愿,比如“太阳在跟着我们走”。另一个是“artificialism”,人为主义。小孩认为一切东西都是人出于某种目的造出来的。比如为什么会有太阳?太阳是人用火柴造出来照亮用的。

由此,在儿童的世界中根本就不存在随机现象,一切都是有目的的。生物学家Lewis Wolpert有本书叫The Unnatural Nature of Science,在此书中他指出,想要摆脱童稚状态搞科学,就必须首先抛弃这个目的论。

科学的标志,是对世界的运行给一套纯机械的机制。风怎么吹,石头怎么落下来,并不是说它有个目的,背后有个什么精神力量,而是物理定律决定了它就会这么做。有些事情发生就发生了,纯属自然,并不是谁“想让”它发生它才发生。比如艾滋病毒在黑人中传播最多,你可以去分析它的传播机制,但是这种传播并不一定有什么“目的”。

很多人研究自然科学为什么没有在中国发生。Ian Morris在Why The West Rules — For Now这本书中说,中国之所以没有自然科学,一个重要原因在于中国传统上认为天道是有目的的。我们认为上天有道德观,他降下自然灾害是对皇帝的警告,或者是对坏人的惩罚。孟子说“天将降大任于斯人也,必先苦其心志,劳其筋骨……”这段话什么意思?他说苦难是老天想考验你。

一般人可以含糊的把孟子的话解释成“我们可以把苦难当成上天对我们的考验”,而回避“上天是否真的会故意考验人”这个话题。但杨绛先生拒绝回避。在《走到人生边上》这本书的第八章一开头,她写道:

大自然的神明,我们已经肯定了。久经公认的科学定律,我们也都肯定了。牛顿在《原理》一书里说:“大自然不做徒劳无功的事 。不必要的,就是徒劳无功的 。”(Nature does nothing in vain。 The more is in vain when the less will do。 )(参看三联书店的《读书 》 2005年第三期 148页,何兆武《关于康德的第四批判》)哲学家从这条原理引导出他们的哲学 。我不懂哲学,只用来帮我自问自答,探索一些家常的道理。

大自然不做徒劳无功的事,那么,这个由造化小儿操纵的人世,这个累我们受委屈、受苦难的人世就是必要的了。

我不得不说,杨绛先生和何兆武都把牛顿的话给理解错了。这段话出自牛顿《原理》中“Rules of Reasoning in Philosophy”这一节,原文是

Rule I. We are to admit no more causes of natural things than such as are both true and sufficient to explain their appearances.

To this purpose the philosophers say that Nature does nothing in vain, and more is in vain when less will serve; for Nature is pleased with simplicity, and affects not the pomp of superfluous causes.

这里“more is in vain when less will serve”的意思是说,如果很少的理由就能解释自然,那么再列举更多的理由就是多余的了。整段话的意思实际是,解释自然界的一切,应该追求使用最少的原理。比如牛顿力学很简单,就足以解释自然届的各种现象 — 所以就没必要认为每个物体的每个运动背后都有它自己的特殊理由!而杨绛和何兆武这里把它解释成大自然是有“目的”的,他们理解成“大自然不会平白无故的让一些事情发生”了。

认为凡事都有个目的,是普通人思维区别于科学思维的根本之一。科学家会科学思维,但科学家也是普通人,脑子里有时候也会冒出目的论来。有研究曾经搞过一个目的论测试,拿一百个句子让受试者判断正误,其中有些句子是目的论的,比如“树生产氧气是为了让动物呼吸”。普通人会把50%的题目答错。这个研究让物理学家、化学家和地理学家来做这个测试,结果如果给他们足够多的时间思考,科学家的答错率只有(或者说“高达”,取决于你对科学家要求的严格程度)15%。

但是如果规定必须在三秒中内作出判断,科学家的答错率就会上升到29%。

既然最理性的人也有一颗阴谋论的心,我们就完全可以原谅中国文化、孟子和杨绛先生了。

06 Apr 08:46

婚姻不止是爱情的坟墓

by Yin Wang
???

王垠还真敢写出来啊

本来这么尖锐的“社会改造”话题打算留到以后再说的,可是最近美国社会发生了一件让人哭笑不得的事情,所以我有兴趣把这话题提前说一下。这件引起我思索的事情就是 Mozilla 最近新上任的 CEO Brendan Eich(也就是 JavaScript 语言的发明者)由于在六年前(2008 年)暗自捐款一千美元以支持加州通过反对同性恋婚姻的法案,后来不知道怎么有人在名单里发现了他的名字,现在遭到 LGBT 社区的激烈谴责。著名的在线约会网站 OkCupid 为了显示对 Eich 的抗议,居然在网站里加入代码检测 Mozilla(Firefox)浏览器并且拒绝为其用户服务。结果上任没几天,Eich 迫于社会舆论压力主动辞职。Mozilla 的网站上也发表了类似道歉的声明,大谈什么“自由”,“平等”,“包容”云云。

长久以来我总是对人类的各种愚蠢举动感到悲哀却又无能为力,现在他们又再一次的让我吃惊。网上的言论不管是反对 Eich 或者支持他的人,都很少有说中关键的,所以我觉得有必要提供一下我,作为一个人类社会很多事情的“旁观者”的看法。当然这里完全是我的个人看法,跟我所在的公司和同事没有任何关系。

在我看来一切都是那么的简单。说同性恋也有结婚的权利,就像是在说女人也有刮胡子的权利,男人也有来月经的权利一样。在美国没有人阻止同性恋关系,没有人阻止他们住在一起,也没有人歧视他们,所以要说自由其实非常自由。可是谈到婚姻就是另外一码事了,我觉得大部分的人都没有搞清楚婚姻到底是为了什么就在跟着瞎起哄。在我看来,婚姻本来就是人类社会残存的一种历史遗留产物,它本身就起源于男人与女人的不平等,不自由关系。如果同性恋想要追求平等和自由,那他们之间就不应该有婚姻关系存在。而且婚姻现在残存的那一点点用处,无非是为了保证两个人生下小孩之后履行抚育它的义务。同性恋生不了孩子,那婚姻对他们也就没有用处。另外就像资本主义一样,婚姻在社会生产力低下的时期曾经起到过正面的,协调社会关系的作用,可是当生产力高度发展之后,它就开始阻碍社会的进步,严重的约束人类的自由,引发一系列丑陋的社会现象。婚姻本来就应该逐步退出历史的舞台,它不应该继续存在下去,所以同性恋婚姻既然本来就不存在,那干脆还是不要诞生的好。

作为一个受到过来自婚姻的间接和直接影响的异性恋者,我想对同性恋者们说,你们应该庆幸自己的生活中本来不存在婚姻这种虚浮,庸俗又麻烦的东西,现在却稀里糊涂的要跟风,要“平等”,要“包容”,其实是自讨苦吃。我觉得 Eich 捐款反对同性恋婚姻,虽然说不上有功,至少没有过错,何况这种个人观点跟他履行作为 CEO 的职责没有半点关系。他的辞职让我对美国社会一直以来崇尚的所谓“言论自由”,“个人主义”和“批判性思维”深表担忧。如果人民智商继续下降,不能理性科学的分析问题,说什么都是空话。

很显然,OkCupid 采取的激烈措施完全是为了自己的利益。如果同性恋不能结婚,上 OkCupid 等网站寻找约会对象的同性恋者就会减少,随之这些网站的收入就会减少。从这篇文章中你可以看到,同性恋者占了 OkCupid 用户的 8%,这是相当可观的比例。有些人喊着各种口号,其实不过是为了自己的市场,利益,出风头。

婚姻的本质

既然话题已经说开来了,我就继续谈一下对于婚姻这东西的看法。喜欢八卦的中国人民请注意,这可不是我一时半会儿受了什么打击形成的想法哈,而是经过许多年的经历,独立的思考,以及参考很多其他人的哲学之后得到的结果。不过我估计这些对我这么显而易见的东西恐怕还是会引起很多人争论。

人们由于从小受到社会的“熏陶”,往往不假思索地接受已经存在的社会规则,以至于让自己痛苦不堪却又无法摆脱。他们根本都没发现问题出在哪里,又何谈解决问题?多少悲剧的爱情小说和电影,都是因为婚姻造成的,以至于人们常说,婚姻是爱情的坟墓。很多人(包括我自己)也感觉到了,每当关于结婚的话题出现,甚至想到它,人们就开始紧张,美好的感情就开始磨灭和瓦解。可是这个社会仍然向小孩子们灌输错误的思想。迪斯尼的电影总是喜欢演王子救了公主,然后他们举行了婚礼,从此过上了幸福的生活…… 其实天知道他们后来的生活是多么的无聊或者发生了什么挫事 :-P 中国女人从小受到的教育就更可怕,脑子里一坨一坨关于婚姻的垃圾,以至于我都懒得理她们了。越是漂亮的脑子越是秀逗和可怕 :) 现在从 Eich 的辞职我们看到了,婚姻不仅可以毁灭爱情,而且可以部分的毁灭一个人的前程。如果我们不正视和意识到这个问题,婚姻就将伤害和毁灭更多美好的东西。

很多人都观察到的一个现象就是,婚姻这东西是跟家族,金钱,利益,甚至政治,权力紧密联系在一起的,而跟人类最美好的感情(友谊,爱情)是直接的冲突关系。中国人结婚是最麻烦的事情。恋爱是两人的事,貌似浪漫又简单。一到考虑结婚就不再是两个人的事了,而是两个家族“传宗接代”的事,所以要“门当户对”。很多人先要通过对方父母的“面试”和各种考验,攒钱买房,买车,然后大摆筵席。我有个朋友恋爱十年,结果最后还是因为父母不喜欢对方而导致的连锁反应分手了。最怕有人来送喜帖,因为参加别人的婚礼意味着要说一些自己不想说的话,送一些价值不菲的礼物或者礼金。如果不去就是不给面子,而且送礼都有“规矩”的,如果送得不够,嘿嘿,别人都算计着呢。很多人都想靠一场婚礼把之前的开销给赚回来,甚至创收。更有甚者结好几次婚,每一次都要你去送礼。这就是为什么我从来不参加别人的婚礼,再好的朋友也不例外。

这些都是肤浅的表面现象,不过你已经由此可见我对婚姻的印象,而它幕后的实质是什么呢?很多哲学家都分析过婚姻这东西,最后他们得出的结论是,就像宗教一样,婚姻并不是什么神圣美好的东西,它是人类社会资源相对缺乏的时期,人们用来协调社会关系的一种规则。在早期的时候劳动力缺乏,男丁的数量决定了家族的力量。男人们想要保证女人生下来的是自己的基因而不是“野种”,所以要保证女人的“贞洁”。女人们呢,都把嫁入好人家作为毕生的事业。可惜的是这种规则沉淀下来,进入了人们的骨髓里,当社会剧烈进步之后还仍然一成不变。

在西方,男人和女人结了婚都会在无名指上带戒指作为标志,以为这样就可以让其它的异性对自己的伴侣敬而远之。有些已婚男人自己去参加 party 没带戒指,跟人合影照到了手,还非得 PS 上一个戒指才敢发到 Facebook 上面 :) 当然你得办一场盛大的婚礼,这样全世界的人都知道你的另一半不再 available 了,即使他(她)跟另外的人更合适一些。就这样,人们希望婚姻可以保证自己有稳定的伴侣,这种规则写进了他们的基因,写进了他们的文化。婚姻代表着占有,代表着稳定。这里的引论就是,一旦结了婚,自己再穷再懒再凶再不讲卫生不打扮漂亮,都没关系了 :) 这就是为什么很多人恋爱甚至同居多年,结果结婚不几个月之后就忽然散伙了。另一种可能的原因是女人想靠结婚来分财产,那就更加丑陋了。

在生产力落后,女人经济地位低下不能独立的世界里,婚姻是起到过积极的作用的,至少它让女人作为寄生虫活下来了。然而当科学技术引起社会生产力高度发展之后,这种社会规则就引起了不必要的矛盾,甚至引起社会的倒退。实际上,很多国家的社会(包括中国和美国)已经开始倒退了。社会文化越先进,福利越好的国家,里面的人就越不想结婚。比如某些欧洲国家,由于人们有很好的社会福利,很少有人想要结婚。有些国家大部分的小孩都是没结婚的人生下来的,因为社会福利和法律保障了这些小孩得到良好的抚养和教育,而且社会的总体风气保障了他们不会被“婚生”的小孩歧视。这些国家也是世界上科技和艺术最发达,人们工作时间最短,生活最悠闲,幽默感最高,男人最绅士,女人最漂亮最有魅力的地方。

婚姻对当今社会的作用

在历史上有一段时间,出现了大量适合女人的简单工作(比如管理,文秘,会计等),所以女人的经济地位,自尊和魅力处于上升趋势,人类的文明和文化也处于进步时期。可是后来由于科学技术的发展,特别是电脑技术的发展,原来需要大量人力完成的工作,现在只需要一个人加一台电脑就能完成,而互联网和机器人技术的兴起,让人能做的工作越来越多的被机器取代。按照现在的发展来看,世界上最后剩下来的少数“合法工作”包括:程序员,机器人技术研究员,妻子。

女人对电脑编程这种枯燥的事情天生的不感兴趣,结果就是女人的工作机会经过一段时间的上升之后,又逐渐趋于减少的趋势。很多女人(特别是中国女人)现在难找到工作,所以就像早期社会的女性一样,把能够找到合适的结婚对象作为了自己的毕生的奋斗目标,而且她们很多人的标准是:高富帅。婚姻在这个时候就再次的成为了她们最有力的“铁饭碗”,跟终身教授(tenure professor)的职位类似。可惜的是哪里有那么多高富帅,结果她们嫁的人往往是“矮富挫”。

现在女人们很喜欢把自己包装成“白富美”,搞点美图秀秀网络自拍之类扩大市场的宣传工作。而一帮傻男人们就开始拼命赚钱,为的是可以买昂贵的礼物,请吃豪华时尚的大餐来讨这些女人的欢心。有些人不能合法赚到钱,就开始做一些非法勾当,甚至进入官场搞腐败。有些外国公司,比如哈格达斯之类的深刻的理解了中国人的虚荣心,于是乘机改头换面从一个只能在美国农村超市里占据一个冰柜位置都没人买的小摊变成一个可以在中国都市黄金口岸开时尚豪华餐厅的公司,打出一些口号比如“爱她,就请她吃哈根达斯”之类的。

中国人到哪里都是中国人。出了国的中国女人都喜欢找老外,再老再穷再丑的都要了。出了国的中国男人都喜欢买宝马,为的是“泡妞”,结果他们每次所谓的“美女”据我评估都在70分以下,还说送了她们多么贵重的礼物才搞定 ;-) 这是多么丑陋的现象。鉴于如此,我有时候喜欢跟朋友开玩笑说,现在找老婆还不如找小姐,因为小姐至少是明码实价,而这些所谓“良家妇女”一般都是黑市价或者搞拍卖的。不要搞错了,我想说的是,我从来不想找小姐,所以我更不想找老婆,因为很多一心想做老婆的人其实已经比小姐还要低贱了 :-P

可是人天生就是“花心”的,不管男人女人都一样。美丽的人就像可爱的小动物,谁不喜欢抱一抱呢,可惜的是人远远不如小动物可爱。因为虽然有些女人开头装得好像无所谓,而其实都想要“终身妻子”的职位,男人们迫于压力不得不入乡随俗,所以他们哄骗女人,让她们以为靠一张纸就能把男人“据为己有”,而其实呢男人总是在婚后发生一些其它的艳遇。可以说这些婚外恋的发生,女人们有很大的责任。比如她们很多人婚后以为有了“法律保障”就开始显露原来的本性,不注意打扮邋遢懒惰尖酸刻薄唠叨八卦无聊沉迷于低级趣味…… 男人很难过,后悔为啥当初跟这人结了婚,忽然在外面遇到了“当年的她”,那怎能不兴奋呢。本来是美好的感情,可是这些正宫娘娘们不高兴了,为了这口铁饭碗,我们要打小三!于是乎事情就越来越复杂,越来越丑陋。

解决方案

鉴于如此,我觉得婚姻不止是爱情的坟墓,它还是一系列其它美好事物的坟墓。它在历史上起到过积极的作用,然而当社会生产力高度发展之后,它开始阻碍社会的进步,导致各种腐败堕落丑陋现象的发生。

就像资本主义和环境污染一样,很难说这种现象在一百年之内会有什么解决方案,但是知道了总比不知道的好。为了拥有真正的友谊,爱情和浪漫,我们的社会规则应该改变一下了。怎么才能改变?我不知道,但我想至少应该打击腐败,改善福利制度,减小贫富差距。让女人们和原来进行简单劳动的人,在智能机器人大生产之后,不需要劳动不需要依附于男人也能过上舒适安稳的生活。独乐乐与人乐乐,孰乐乎?就是这个道理。另外鉴于婚姻在当今中国社会暂时的实际用途(保护小孩子),我建议对婚姻法做出类似以下改进:凡是登记结婚的人必须在一年之内生下小孩,否则婚姻作废。

最后回到 Brendan Eich 的案例:其实他没有做错什么。异性恋的婚姻都应该被逐步废除,同性恋在那里着个什么急?

04 Apr 08:46

科学成功学:几分天注定,几分靠打拼?

by 杨婴

一个人怎么才能混得好?恐怕各人都有一套说辞。但事实证据到底如何?图片来源:seanheritage.com

(文/Michael Bond)“尽管无法保证结果的平等,但我们一直致力于提供平等的机遇——成功取决于努力和付出,”美国总统奥巴马在2013年12月的一次演讲中是这样说的。然而,他接下来也承认,在今天的美国,成功已经比以往任何时候都更加倚重于富二代或官二代的身份。

在英国,被一些人视为首相接班人的现任伦敦市长波里斯·约翰孙(Boris Johnson),也在不久前就日益增长的不公平发表过看法。但他的视角与奥巴马总统截然不同。他认为,成不成功完全取决于智商,因此我们只要给最聪明的孩子提供最好的机遇就可以了。

这些演讲提出了各式各样的议题,但它们的核心是在讨论获得成功的两种方法。这两种方法全然相反:一些人是基因决定论者,认为成功由上天注定;另一些人则是教育至上者,认为只要机会允许,谁都可以成功。那么,这两种观点,哪个才更接近真相呢?

勿庸置疑,真相要复杂得多。遗传基因起作用,人们所处的环境也有影响。智商据称是衡量天生智力的指标,然而就连它也会在一个人的成长过程中发生改变。也就是说,要让一个人更成功,可做的事很多。但是,政府、学校和父母做得对吗?

英国伦敦国王学院的罗伯特·普罗敏(Robert Plomin)最近领导的一项双胞胎研究,让有关成功的争论大大升温。该研究发现,从英国孩子在学校里的文化课差异来看,遗传因素所起的作用要大于教育和其他环境因素。其实这个结果也没什么好惊讶的,毕竟智力大部分决定于基因,这基本没有疑问,而且,机灵的孩子往往学习更好。

但这个结果并不说明教育一无是处。就像谈论身高时的情形一样:对吃喝不愁的孩子来说,不能因为基因是决定身高差异的主导因素,就说食物对身高没有影响。由此反推,普罗敏说,基因占主导作用其实是件好事,这意味着环境越公平,基因这类与贫富出身毫不相干的因素就越起作用。他说,我们也没必要将大量资源堆砌到一个小天才身边。

有个例子就能说明,拥有最高智商的孩子将来未必会成龙成凤。上世纪20年代,美国斯坦福大学的心理学家刘易斯·特尔曼(Lewis Terman)在加利福尼亚征召了1528名孩子参加实验,这些孩子都在斯坦福-比奈智商测试(Stanford-Binet IQ test)中得分甚高。和约翰孙一样,特尔曼当时相信,智商是未来成功与否的关键——这里的“成功”是以收入和成就来定义的(当然,成功也可以由幸福指数等其他指标来衡量,但在本文中,我们将专注于更物质化的狭义层面)。从某种程度上说,他是对的:这些孩子人到中年时,总共发表了约2000篇学术论文,获得了至少230项专利,创作了33部小说和375个短篇故事及戏剧。他们的平均收入是全美平均收入的3倍。

然而,现实没有上面说的那么了不起。即使特尔曼的研究对象有着高达147的平均智商,其中1/4在长大后也是泯然众人,做着小职员、警察、推销员、工匠等默默无闻的工作。这些昔日的“方仲永”,没有一人的贡献可以媲美当时诺贝尔奖得主的学术成就,也不及当时美国国家精英们在其他方面的业绩。实际上,特尔曼只关注智商的取样方式,把像路易斯·阿尔瓦茨(Luis Alvarez)和威廉·肖克利(William Shockley)这样的孩子堵在了门外——要知道,他们后来都荣获了诺贝尔物理学奖!

此外,特尔曼实验的参与者中没有一人在商界拔得头筹,因此他们也不是巨大财富的创造者——但是,为一国创造财富却是精英与否的评判标准之一。最终,在实验进行了25年后,特尔曼承认“才智与成就远非完美相关”。

遗传还是环境?

智力因素显然很重要,但空有才智不一定能获得成功。大量压倒性的证据指明了环境因素的重要性,其中又以社会经济地位最引人注目。在贫苦地区长大的孩子鲜有机会接触电脑和书籍,形成良好生活习惯的机率较小,从父母那里得到的呵护恐怕也较少。因此,他们的身体会差一些,在学校的表现也可能趋于负面。这样,他们要在成年时有一番作为就难上加难。相反,许多成功的企业家、领导人和获崇高艺术成就者都成长于良好的家庭,他们家中的藏书常常汗牛充栋,家人习惯于欢聚一堂,在餐桌上谈天说地。

父母离异和感情不和的家庭中成长起来的孩子也容易输在起跑线上,这个时候他们家庭的社会地位倒显得次要了。这些孩子在学校可能会举止乖张,成绩不佳。

爱德华·梅利斯(Edward Melhuish)在英国伦敦大学伯贝克学院研究儿童的成长发育,他提醒人们说,孩子在5岁前从双亲或抚养者那里如果得不到持续关爱,在试图沟通时也得不到回应,那么他们在社会行为和情感上的发育就会受到损害。最关键的是,这还会影响他们的语言能力,梅利斯说这也可以解释,为什么在负面因素堆积的家庭中出生的孩子一般在学校里表现较差。他还说:“提高语言能力不仅可以提高社交技巧,还可以提高认知能力、读写能力和学习成绩。”

换言之,环境的影响不可估量。养而无教会蚕食孩子的认知能力,将智商值降低多达9个点(参见《儿童成长发育》,第65卷,296页)。良好的成长环境却会提高智商。如果贫苦家庭出身的孩子被好人家收养长大,他们的智商就要比未被收养的兄弟姐妹高很多。

由这些发现可以引出许多显而易见的推断。要让所有孩子都最大程度地发挥潜能,就要赶在他们上学前行动,等到入学恐怕就晚了。梅利斯说,我们需要高品质的“早教中心”,这个机构集儿童保育(child care)、育儿培训(parenting support)、卫生保健(healthcare)、教育学习(learning)于一体。已有证据表明,这种形式的干预对不同家庭背景的孩子都有益处,对家庭环境不好的孩子效果尤其显著。

早教的重要性现在已经得到广泛认同,它也催生了多项促进儿童生长发育的提案,如英国的“确保开端计划”(Sure Start)和美国的“启蒙计划”(Head Start)。美国总统奥巴马目前正在寻求党派间的共同支持,以推行他扩大幼儿园前儿童教育的计划,他在2014年1月说:“研究表明,为孩子提供高品质的早期教育是终其一生我们所能提供的最佳投资方式之一。”但反观英国,在过去两年中,“确保开端计划”的经费已经被砍掉了1/3。

然而,除了天生的智力潜能和能够激发潜能的成长环境之外,还有很多因素在助人成功。“面对一群技能纯熟的人,你感觉不到他们因为认知能力和智力不同而表现出的差异,”美国佛罗里达州立大学的心理学家K·安德斯·埃里克森(K. Anders Ericsson)说道。他和一些人主张,在音乐、体育、棋类以及其他与记忆相关的许多领域中,业内精英之所以成就非凡,与其说是天赋异禀,不如说是熟能生巧。

为什么一些人比别人更勤于练习呢?年少时,恐怕是望子成龙的父母推了他们一把。但是,要规划一条成功之路,一些特定因素对谁都必不可少。比如,没有百折不挠的精神和持之以恒的劲头,就达不成长远的目标——换言之,人们需要“坚忍”的品质。“越坚忍不拔的人越能成功,特别是在艰苦的条件下,”美国宾夕法尼亚大学的安吉拉·达克沃斯(Angela Duckworth)说道。

怎么才能变得坚忍不拔呢?“激励”是手段之一。达克沃斯证明,如果给予一些激励——比如一小笔奖金,人们就能在智商测试中得分更高(参见《美国科学院院报》,第108卷,7716页)。这项发现为成功学的研究带来了一些重要推论。心理学家、经济学家和社会学家经常抓住智商和成就之间的联系不放,以此来证明:成功大部分取决于智力。然而达克沃斯的工作却说明,智商测试测量的不仅仅是智力,激励对成功的作用也可圈可点。

激励在学习成功乃至找工作的过程中,都有突出的影响。图片来源:《新科学家》

还有样东西让我们变得坚忍不拔,这就是做事有始有终的毅力(willpower)。它需要我们努力工作,抵制诱惑,克制冲动。毅力在很大程度上事关自制力(self-control),而自制力则提供了两条重要途径,助我们获得成功。

首先,自制力和智力一样可以让我们终身受益。用它来预测青少年的考试成绩,比用智商准确。达克沃斯发现,自制力越强的学生越会准时到校、完成家庭作业、少看电视,这些行为都会让他们的学业更上一层楼。最近一项涉及1000名新西兰儿童的研究,跟踪了他们从出生到32岁的表现。研究发现,在童年时期自制力越强的人长大后越健康,情绪越稳定,经济状况也越好(参见《美国科学院院刊》,第108卷,2693页)。

控制欲望

这项研究与心理学家沃特·米歇尔(Walter Mischel)的一项著名观察实验相呼应。米歇尔目前就职于美国的哥伦比亚大学,他在上世纪60年代末做过一个实验,给孩子们两个选择:是马上就能得到一份好吃的,还是忍一刻钟得到两份。几年后这些孩子升入高中,米歇尔发现,那些能够等待的孩子比经不起诱惑的成绩更好。成人之后,那些曾经选择晚一点拿奖励的人也比同龄人更受欢迎,更少罹患肥胖,挣的薪水也更多。

自制力的第二个重要特点,在于我们可以增强它。美国佛罗里达州立大学的罗伊·鲍迈斯特(Roy Baumeister)将自制力比作肌肉,因为它可以通过锻炼而得到强化。他的研究团队发现,磨练生活中一个方面的自制力,可以使各方面的自制力得到全面增强。他们还发现一些人自制力的增幅比其他人强,这可能是由于他们的自制力本来就强一些,因此,更适应这方面的强化训练。鲍迈斯特说:“这是一个良性循环,所以身为父母,怎么都得趁孩子还小,就培养他们的毅力。”他还说,自制力对集中式特训也十分关键,这种特训在任何技能训练中都不可或缺,因为有意训练自己的行为就是赶着你去完成最困难的任务,不让你敷衍了事。

知道我们有办法提高毅力、让自己在面对困难时更加坚忍不拔,可以使我们对自身能力抱更乐观的态度。不幸的是,我们经常因为认不清自己或低估自己适应变化的能力而畏葸不前。发展心理学家称,拥有思维定式、将智力和性格等看作一成不变的属性,会使人们惧怕失败,无法坦然面对批评,避免承担新的或困难的任务——这些都不是成功的秘诀。相反,相信品格可以再塑,能让人更愿意拓展自身,学习新的技能。

近10年来,美国斯坦福大学卡罗尔·德威克(Carol Dweck)领导的研究团队提高了数以千计美国学生的分数和出勤率。他们用的方法很简单,不过是告诉学生智力并非一成不变,努力学习可以让人更聪明,为适应高校生活而挣扎不是资质愚笨,而是正常的学习过程。德威克说,“成长型”思维模式会使人受用终身,“它让你接受更多挑战,不会因为挫折而垂头丧气,也不会把自己的努力看得有害无益。”

人们常把一些特定的社会人群归入消极的社会身份模式中,这种思维定式的危害特别大。这些特定的社会人群,比如美国黑人、妇女等,或许无意中就默默遵从了这种模式。虽然整个社会的态度不容易改变,但是改变个人的思维定式相对简单。最近,德威克团队通过一项未及发表的研究,帮助高中毕业的美国黑人提升自己在大学里的表现,所用的方法也很简单,只是鼓励他们采用成长型的思维模式。

有关毅力和思维定式的研究表明,我们可以对自己那些与生俱来的能力施加影响。然而,这并不意味着教大于养。“实际上,在你能想到的每一项心理特征上,遗传因素都对个体差异有影响。这些心理特征包括性格特点和认知能力,”斯科特·巴里·考夫曼(Scott Barry Kaufman)说道——他在美国纽约大学研究智力和创造力。然而,所有这些特征都需要得到开发。他补充说:“个人所处的环境,以及他在生命中作出的决定,对开发这些特点都非常重要。”

埃里克森认为,在大多数情况下,如果方法得当,任何人在任何领域都能做到“只要功夫深,铁杵磨成针”。但是美国加利福尼亚大学戴维斯分校的迪安·基思·西蒙顿(Dean Keith Simonton)说,天赋更高的人学得更快,总能打败学得慢的人。“是啊,我可以花很长时间、下很大苦功,最终成为一名小提琴演奏家,但如果我到了50岁才混到一个地方乐团第二小提琴手的面试机会,那有什么意思呢?”

*视唱(sight-reading)指拿到一份乐谱看谱即唱的技能,具体而言,是一种调动学生独立运用视觉、听觉、感觉进行积极思维活动练习识谱的技能训练,是学习音乐的基础学科之一。图片来源:《新科学家》

以梦为马

事情不仅是“谁比谁更有才”那么简单,人还各有所长。但是,如果不能因材施教,那就只有一部分人有机会脱颖而出。英国一刀切式的学校制度采用国家统一的课程,这种安排本是为了教育面前人人平等,却在无意中特别优待了少部分孩子。几乎所有心理学家和发育专家都更推崇另一种学校制度,这种制度鼓励更多形式的才能和兴趣,不像现有制度般具有过多的量化指标和过强的功利性。

埃里克森说:“学生不是一个模子里刻出来的,所以有必要给出多种选择。”想想这个比喻:喜阳的、喜阴的、喜湿的……花园里的生态位越多,能在这里繁荣生长的植物类型就越广。

考夫曼说,学校应该鼓励孩子们深入自学一些更专的科目,让他们量力而行,毕竟学习,特别是艺术和科学方面的学习,需要积累大量社会和认知技能,因此许多人都是大器晚成。他说:“一个人恐怕要花很长时间才能冲破瓶颈,终成大器。”

专家们认为,像英国如今这样引进越来越标准化的测验方式,其实没什么帮助。美国的教育体制不是这么集中统筹管理,但它也是由一些标准化的测验所主导。对此,考夫曼建议说:“听听孩子们的梦想,鼓励他们,别管他们的分数和先前的背景。不要因为标准化的学业成绩而表扬他们,要表扬他们付出的努力和努力的过程。”

鼓励“大头梦”?对一些人而言,这似乎不是成功的秘诀,但它可能是最重要的因素。美国心理学家艾利斯·保罗·托兰斯(Ellis Paul Torrance)跟踪记录了数百名具有很强创造力、作出杰出成就的成功者的生活轨迹,时间横跨从高中到中年的几十年,他们中有学者、作家、发明家、教师、法律顾问、商界总裁和曲作者。托兰斯发现使他们卓而不群的,并不是在校期间的学业能力、技能或成果,而是有目标、勇于创造、乐于沉思、安于独处等性格方面的“软实力”。他认为其中最重要的是“爱上一个梦想”,而且这份爱最好在少年时就生根发芽,以至于寻梦一生,炽热如一。

托兰斯把这群胸怀远大抱负的人称作“超越者”(beyonder)。在他眼中,他们的成就会超越标准定量测试所能预测的一切,超越其他任何人想象的极限,直到他们实现梦想的那一天。

 

编译自:《新科学家》,The science of success: Blood, or sweat and tears?

扩展阅读

怎么养育出一名“大牛”?

父母怎么做,才能保证孩子有最好的机会发挥他们的潜能?这可没那么容易,毕竟你无法一眼就看出孩子的优点和才能。要知道他的天分是什么,最好的办法就是:他有兴趣,你就鼓励。

美国加利福尼亚大学戴维斯分校的迪安·基思·西蒙顿说,这意味着父母得允许孩子自己追梦,哪怕这些梦想不是父母心目中的首选,比如放着物理学家不做,去做什么诗人。父母还要清楚地认识到,对孩子的一些“投资”可能是收不回来的。

即使孩子们最终没有坚持追寻一个特别的梦想,也没有坚持一项体育运动或乐器训练,只要在一个领域内练成牛人,他们也能了解怎样在其他领域练就实用技能。最好的建议是告诉孩子们,人类具有“成长型思维模式”——这个术语是美国斯坦福大学的卡罗尔·德威克创造的,意思是“能力可通过全心全意的练习而经由后天提高,在生物学属性上不是一成不变的”。德威克劝诫为人父母者多表扬孩子的努力和进步,少称赞他们的智力和天赋,毕竟前者能鼓励他们不断尝试,后者却让他们在失败面前浅尝辄止。

相关的果壳网小组

你可能感兴趣

  1. 鹰爸的极限教育能让“裸跑弟”智商涨到218?
  2. 父母教育你的方式,你满意吗?
  3. 宝宝多才多艺就等于全面发展吗?
  4. 教育熊孩子,沟通比打更有用
  5. 高学费一定能换来孩子的好成绩吗?
  6. 不适当的表扬吞噬“神童”的自信
  7. 穷人的孩子注定输在起跑线上?
  8. 该让谁来教育我们的孩子?
  9. 中国式在家上学
  10. 美国:山镇里的反“学校体制”
  11. 性,怎么跟孩子说?
  12. 体重可能影响儿童的数学能力
03 Apr 19:39

练出来的才是最强大脑

by 游识猷

本文作者:游识猷

running-brain像素眼郑才千、魔方男神贾立平,盲填数独孙彻然,超级记忆者刘宏志和黄金东……随着《最强大脑》收视率攀升,各种讨论也越发热烈。有人列出了参赛者从前接受的训练后问,这种练出来的,算得上最强大脑吗?

事实上,通过训练得到的天才更值得赞许和效仿。最强大脑是靠练出来的,不是靠生出来的。

对于天赋向来有两种截然相反的看法,一是认为天赋生来相对固定、难以改变,我们能做的只有“顺其自然”; 另一种则认为,通过努力训练可以大大改变自身的天赋。

天赋固定论者,很自然地会致力于寻找“天生有才者”。 大概一百年前,斯坦福大学的研究者刘易斯·特曼(Lewis Terman)就相信有些人天生拥有“天才基因”,而且既然这种基因与生俱来,就应该可以被早早识别拣选出来。特曼精挑细选了百位“神童”进行跟踪,遗憾的是,他们中虽然出现了不少人才,但却无人长成“天才”。倒是被特曼排除在外的儿童里,出现了梅纽因这样的世界级音乐家。

天赋可变论者,则自然会致力于寻找“提高天赋的训练方法”。有两项能力好比大脑的“肌肉”和“心肺功能”——工作记忆以及专注力。训练这两项能力,负责归纳推理新知的“流体智力”就会显著提高。每天冥想15-25分钟,甚至能让注意缺陷多动障碍者(ADHD)的认知功能得到改善。

近年来,研究界流行利用冥想训练来锻炼专注力,用电脑游戏来锻炼工作记忆。比如让被试者在游戏中记忆如下要点:之前出现的字母与方格位置,再之前出现的字母与方格位置,再再之前出现的字母与方格位置,再再再之前出现的……2008年,《美国国家科学院院刊(PNAS)》上刊登的一项研究就表示,完成这种“双N-back任务”后,被试者能答上更多的综合分析矩阵问题。这意味着游戏练出的绝非简单的“熟能生巧”,而是训练出了更通用的能力,这种能力能被迁移运用到其他情境中。会玩“Angry bird”的人未必玩得转“Flappy Bird”,但学会速算的人玩“21点”的能力就会提高。

如今的研究结果越来越倾向于天赋可变论。打个比方,属于你的灵魂伴侣其实不是“找到的”,而是“相处出来的”。同样道理,每个人也都有属于自己的伟大,但那份伟大不是一个等着被“找到”的成品,而是一个需要朝夕相对、仔细雕琢的过程。人并不是生下来就注定被分为“小草”和“乔木”,每个人的大脑都蕴藏着长成参天大树的潜能。天赋不但常见,而且是可再生资源,但是如果你不针对 “超越自我的事”进行高强度训练,你的大脑便永远达不到它本能达到的极限。

相比起来,天生如此、难以自抑的“最强大脑”对于研究者是无价之宝, 因为这样的大脑往往在“硬件”上有明显异于常人之处,能为人脑解谜提供宝贵线索,然而,他们的大脑却大多患有疾病,“普通人做不到”的种种才能,其实是疾病的结果。大脑所受的伤害与其说是“制造了超能力”,不如说是提供了专注发展特定能力的机会,是大脑可塑性的表现。悉尼大学的艾伦·施耐德(Allan Snyder)曾用电磁刺激来抑制健康人的左前颞叶,这块脑区负责存储一些固有观念,结果发现抑制此区的大脑不但能绘出细节更丰富的图画,就连创造性解题的成功率都从20%提升到60%。

“学者综合征” 患者能记住圆周率后的数万位数。“雨人”原型金·皮克能逐字记忆数千本书,但却无法给自己系上哪怕一次鞋带。如今,随便一个智能手机也能“记住数万位圆周率数字”“记忆数千本书”,但却无法做到普通大脑所能进行的一些复杂运算。Facebook投入重金的Deepface人脸识别正确率达到97.25%,看上去很高,其实也就是略次于普通人的水准。而2013年,欧盟为亨利.马克莱姆(Henry Markram)教授的人工大脑模拟项目注资十亿欧元,目标仅仅是模拟出一个“普通大脑”……你自以为“平庸”的大脑,蕴藏着远超你想象的“超能力”。

最强大脑,宁有种乎?其实正如Dr魏在微博上所说:“最强大脑选的绝不是大脑天生就和常人不同的,绝不是不需要训练就是天才。没人有不训练就是天才。相反,在一定天赋基础上,拼命训练才可能有天才般的表现。比如,台上记忆高手的大脑结构和普通人的基本没有差别,他们后天大量的记忆训练才是成功秘诀。”

“最强大脑”是练出来的,不是生出来的。在节目之外,还有一批真正的“最强大脑”,它们擅长的不在于博闻强记,而在于创新和挑战。凭着偏执狂般的坚持训练,那些最强大脑们所创造出的东西不但可以改变自身,而且可以改变世界,并改变我们看待这个世界的方式。

关于

本文首发于《北京青年报》)​

25 Mar 16:23

熬夜了补一觉就成?大脑可不这么认为!

by 球藻怪

昨天又熬夜了?想趁着周末好好补一觉?可别想得这么轻松。发表在《神经科学杂志》(Journal of Neuroscience)上的一项新研究首次发现,睡眠不足对大脑造成的损伤是无法通过补觉来修复的。宾夕法尼亚大学的神经科学家西格丽德·维塞(Sigrid Veasey)表示,“熬夜之后找机会补一觉就万事大吉了”这种想法是错误的。

一次熬夜造成的大脑疲劳感即使经过好几天的正常睡眠也无法恢复。这种现象意味着失眠可能对大脑造成了长久的损伤。为了验证这种假设,维塞和她的同事们用实验小鼠进行了研究。他们为小鼠制定了与轮班工作的人相类似的作息时间表:他们先让小鼠小睡一会儿,然后唤醒它们,让它们保持清醒一小段时间;再让小鼠睡觉,再唤醒,这次让它们保持长时间的清醒状态。

随后,研究人员检查了小鼠大脑中的蓝斑神经元(locus coeruleus neurons)。蓝斑(locus coeruleus)是位于脑干的一个神经核团,其功能与应激反应有关。蓝斑内的这些神经元被认为与警觉性和认知功能有关。结果显示,当小鼠轻微缺觉时,神经细胞会合成更多的去乙酰化酶3(SirT3),这种酶可以保护神经细胞并使它们保持兴奋状态;但是,当小鼠长时间缺觉时,这种代偿反应就被关闭了。因此在仅仅几天的“轮班”式作息之后,这些神经细胞开始加速死亡。 维塞表示:“(长时间缺觉的)小鼠大脑中神经元数目减少了25%。”

“之前人们从没想过,熬夜会造成不可逆的大脑损伤。”维塞说,现在这种观点该改改了。

接下来,维塞和她的研究团队计划研究已故的轮班工作者的大脑,以验证动物实验中的发现。研究人员希望这些发现能够为作息不规律、经常需要熬夜的人提供医学上的帮助。

文章来源:CNN,Shift workers beware: Sleep loss may cause brain damage, new research says
图片来源:shutterstock

果壳网相关小组

从神经元到脑

参考资料

  1. Jing Zhang, Yan Zhu, Guanxia Zhan, Polina Fenik, Lori Panossian, Maxime M. Wang, Shayla Reid, David Lai,James G. Davis, Joseph A. Baur, and Sigrid Veasey Extended Wakefulness: Compromised Metabolics in and Degeneration of Locus Ceruleus Neurons The Journal of Neuroscience, 19 March 2014, 34(12):4418-4431; doi:10.1523/JNEUROSCI.5025-12.2014

你可能感兴趣

  1. 自然:睡眠不一定是整个大脑行为
  2. 睡不着?打开睡眠开关吧!
  3. 我们为什么要睡觉?
  4. 我们可不可以少睡觉?
  5. 起床气,你从哪里来?
  6. 达芬奇睡眠法可行吗?
  7. 你到底要睡多久?
  8. 失眠,简单也不简单
  9. 发作性睡病:在白天游荡的睡魔
  10. 鬼压身,只缘脑醒身未醒
  11. 午睡有益,但睡久了白睡
  12. 睡得越多,死得越快吗?
25 Mar 16:02

Deferred Texturing

by Nathan Reed

There’s an idea that’s been bouncing around in my head for awhile about a kind of deferred renderer that (as far as I know) hasn’t been done before. I’m sure I’m not the first to have this idea, but recently it crystallized in my head a bit more and I wanted to write a bit about it.

For the last several years, graphics folks have been experimenting with a variety of deferred approaches to rendering—deferred shading, light-prepass, tiled deferred, tiled forward, and so on. These techniques improve both the cleanliness and performance of a renderer by doing some or all of the following:

  • Elegantly separate lighting code from “material” code (that is, code that controls the properties of the BRDF at each point on a surface), avoiding shader combination explosion
  • Only process the scene geometry once per frame (excluding shadow maps and such)
  • Reduce unnecessary shading work for pixels that will end up occluded later
  • Reduce memory bandwidth costs incurred in reading, modifying and writing screen-sized buffers

Different rendering methods offer different tradeoffs among these goals. For example, light-prepass renderers reduce the memory bandwidth relative to deferred shading, at the cost of requiring an extra geometry pass.

Deferred texturing is another possible point in the deferred constellation—which as far as I know has not been implemented, though it’s been discussed. The idea here, as the name suggests, is to defer sampling textures and doing material calculations until you know what’s on screen.

Renderers these days use quite a few textures per material: diffuse, normal, specular, gloss, emissive, and so on. And materials can involve substantial amounts of math too, if you’re doing things like compositing layers based on blend maps, or applying modifications for wetness/snow/dust/suchlike. It might be worthwhile to avoid doing these operations for pixels that will eventually be occluded.

Beyond that, deferred texturing can also save you from the pressure of squeezing all your material parameters into a G-buffer—a common pain point for deferred shading. With deferred texturing, material and lighting code are in the same shader once again, so you can afford more material parameters and a greater variety of BRDFs, just as with tiled forward shading. But unlike tiled forward shading, deferred texturing only requires the geometry to be processed once per frame.

The Deferred Texturing Rendering Pipeline

Keep in mind that this is all theoretical, and I haven’t actually implemented it; however, I don’t think there are any insurmountable obstacles.

First of all, to sample textures later, we’ll need to store the UVs in the G-buffer. We’ll also need to store a material ID, and later use bindless textures so that we can sample whichever textures we want, based off the material ID. Of course, this will require bindless texture support, which for now means OpenGL and NVIDIA hardware—but should be possible on current AMD and Intel architectures with driver support, I believe. However, there are some caveats here, which I’ll explain later.

What about mip levels, or derivatives? These are needed to get correct mipmapping and anisotropic filtering when we do our deferred texture samples, but I’m not sure it’s necessary to store them explicitly in the G-buffer. It “should” be possible to reconstruct derivatives well enough later, by comparing with adjacent G-buffer pixels. After all, ordinary texture sampling uses quad-uniform UV derivatives, which aren’t all that precise. As long as you have a cluster of a few neighboring pixels using the same material, we should be able to filter them to get good derivatives. We just have to be careful not to filter across material boundaries or UV seams.

We’ll also need to keep interpolated vertex normals in the G-buffer, since there’s no other way to get that information later. (I’m assuming we’ll do our normal mapping with per-pixel tangent space, based on UV and position derivatives grokked from the G-buffer as mentioned above, so we don’t need to store tangent/bitangent/flip values.)

Therefore, the G-buffer layout for deferred texturing might look something like this:

  • Vertex normals—2× 16-bit fixed point (use whichever 2-component encoding you like—I personally favor octahedral)
  • Material ID—16-bit int. 64K materials should be enough for anybody, right? ;)
  • UVs—2× 16-bit fixed point in [0, 1], or maybe [0, 2] to make wrapping simpler. We might need to go to 32-bit for large virtual textures.
  • [maybe] LOD, 16-bit float, or alternatively UV derivatives, 4× 16-bit float.

(These are just my best guesses at bit depths and formats that would give you sufficient precision.)

Here’s something interesting: the G-buffer here is only 80 bits per pixel, in the best case. If you do need to explicitly store LOD, it goes up to 96 bpp; with full derivatives, 144 bpp. With 32-bit UVs and no explicit LOD, it’s 112 bpp. The point is that it can be a reasonably small G-buffer—many deferred rendering pipelines have bigger G-buffers than this. So another possible benefit of deferred texturing is to reduce the G-buffer memory and bandwidth costs.

The rendering pipeline for deferred texturing would then look something like this:

  1. Render the scene geometry and write the G-buffer, as listed above.
  2. Do a light-culling pass, just as in tiled deferred or tiled forward methods, using the depth buffer and generating per-tile light lists.
  3. Do a fullscreen pass in which for each pixel:
    1. Look up the pixel’s material information (texture handles and so forth) from a giant flat array indexed by material ID.
    2. Sample all the textures for that pixel’s material, using bindless texturing.
    3. Do any material computations necessary (compositing layers, applying modifications such as wetness, etc.)
    4. Apply lighting, using the per-tile light lists output previously.

Passes 2 and 3 here could also be combined in a single pass, if the light lists don’t need to be saved out for later re-use.

So assuming this works, why might you want to render this way? I already mentioned some advantages in passing, but here they are collected:

  • You avoid doing the work of texture sampling and material computations for pixels that end up occluded. This could also help when using very small primitives, as less work would be done by quad overshading.
  • If you would have a fat G-buffer otherwise, the deferred texturing G-buffer can be thinner, reducing memory bandwidth costs.
  • You don’t have to squeeze all your material parameters into a G-buffer—you can have more complex BRDFs and a greater variety of them, as with tiled forward shading.
  • Geometry only needs to be processed once per frame.

Why Wouldn’t You Do This?

There are a number of problems that would need to be solved to make deferred texturing a viable approach.

The biggest issue is this: I cavalierly assumed that using bindless textures, we would be able to sample any texture per pixel freely. If you want to be portable, this is not the case. On the AMD GCN architecture, the texture handle can’t be divergent within a wavefront, since it’s stored in scalar registers; on NVIDIA GPUs, divergent texture handles are technically possible, but my guess is that they still don’t perform well (though I haven’t tried). I have no idea about Intel GPUs.

It should be possible to work around this limitation using a tile-based strategy: in a compute shader, gather all the material IDs present in a screen-space tile, then loop over them (uniformly, so no divergence) and issue all the texture lookups for each material together. As far as I know, this should be workable on GCN, but it’s an unusual enough case that I wouldn’t be surprised if you have trouble getting the shader compiler to understand what you’re trying to do!

If you store your textures in sparse arrays, as suggested by AZDO, then this is slightly less crazy—you don’t have to segregate texture lookups by material ID, but only by the size/format combination used to decide which array to look in. The array slice index can vary freely per pixel, with no consequences (besides cache locality).

Incidentally, one could also consider a version of this technique in which instead of using bindless textures, we stored all our textures in one giant sparse atlas. That would avoid the divergence issues entirely—but we’d sacrifice hardware UV addressing modes, as well as requiring all textures to be in the same format.

Another possible variation is to do a separate pass per material, operating only on the pixels that use the current material. This could be optimized by generating per-tile material lists, like the per-tile light lists, and using them to restrict each material pass to regions of the screen where that material is present. It’s possible that this might end up even being faster than the bindless approach, due to better texture cache locality or some other microarchitectural issue.

Arm-twisting bindless textures into doing what we want is the biggest risk here, but there are a few other challenges as well:

  • Sampling textures with explicit LODs or derivatives can be a good deal slower than using automatic LODs.
  • All the material code and lighting code is in one giant shader, which can lead to a variety of issues—compilation, occupancy, and divergence, to name a few.
  • You can only have one UV set per primitive. In the common case that the various textures are all sampled using the same UV set (or transformations of a single UV set), this is fine; but it does make it difficult to use an independent UV set for lightmaps and such.
  • Like deferred shading, deferred texturing is not particularly friendly to MSAA. We would need to use methods like simple/complex pixel classification, similar to efficient deferred MSAA.
  • Deferred texturing isn’t any more friendly to transparencies than deferred shading, either. In fact, it’s actually less friendly, because in deferred shading at least you can create decal shaders that modify the material properties in the G-buffer—for painting stripes on roads and things of that sort. Deferred texturing doesn’t offer a way to do this. However, at least the per-tile light list generated for deferred texturing can be re-used in a forward pass for transparencies.

Defer All The Things

I would be remiss if I didn’t mention the recent JCGT paper, The Visibility Buffer: A Cache-Friendly Approach to Deferred Shading. This paper proposes a rendering pipeline that not only defers texturing, but actually defers vertex attribute interpolation as well! It stores only primitive IDs in its G-buffer; then in a later pass, it fetches vertex data, re-runs the vertex shader per pixel (!), finds the barycentric coordinates of each fragment within its triangle, interpolates the vertex attributes, then finally samples all the textures and does the shading work.

The visibility-buffer approach is explicitly designed for mobile devices where bandwidth is a big concern (both for power and performance), so they don’t want to even fetch vertex attributes for triangles that aren’t going to be seen. On the other hand, they pay for it by doing quite a bit of extra per-pixel computation; they also sacrifice the ability to use tessellation or other methods that generate geometry on the fly, since all the vertex data has to be in memory. Still, it’s an interesting idea—and they actually got it to work, and proved a performance win in some cases. It’s also a partial proof of concept for deferred texturing, since some of the implementation overlaps.

So, Does It Work?

Despite all of the problems just mentioned, I think deferred texturing is an interesting enough idea to be worth pursuing a little bit. I may try to build a working demo when I can find the time, but April is shaping up to be a busy month for me, so it may be awhile! If anyone else wants to try it, please do and let me know how it goes. Or if you have any other thoughts on this method—including if I missed something obvious and it can’t possibly work—please share in the comments. :)

24 Mar 15:21

物质极大丰富的时代:消费文化,“庶民的胜利”

by 同人于野

(《东方早报·上海书评》,2014年3月23日)

我们正处在一个历史上前所未有的富足时代,而人类对此有点不太适应。首先身体上不适应,过去食物一直是短缺的,所以人要尽可能地吸收和存储脂肪,而今天的身体仍然这么做就导致了肥胖症的增多。其次大脑也不适应,过去信息短缺,很多人保留对任何印着字的东西都感兴趣的习惯,而今天如果还这么干就根本没时间处理真正有用的信息了。再者很多人在精神上也不太适应,人们很难相信未来会比现在更好,悲观的预测总是很有市场,当今各国也许只有中国人对未来最乐观。

2013年一个有意思的新闻是瑞士准备搞一次全民公投,来决定是否给全民发钱 — 每人每月2500瑞士法郎(相当于1.7万元人民币)。白给,不必工作,只要你是合法居民。提案的支持者说,“全民发工资计划的目的并不是不让人工作,而是让人做自己更想做的工作。”更有意思的是几乎没人讨论瑞士是不是出得起这笔钱,似乎所有人都认为这点钱不成问题,唯一的担心是这么做会不会减少年轻人工作和学习的动力。

难道瑞士已经提前进入共产主义了吗?据说共产主义社会将是一个“物质极大丰富的时代”。我们显然还没到共产主义,不过现在已经在一定程度上是一个物质极大丰富的时代

世界已经变了。很多适合短缺时代的运行规则,并不适合这个富足时代。总体来说这个时代的贫富差距并没有减少,反而因为全球化、技术进步和更自由的经济制度而加大了,但是有一个现象却是过去任何时候都没有的。历史上一直都是富人享受安逸,而穷人终日辛苦劳作。但是据2013年出版的Plutocrats 一书(作者Chrystia Freeland,中译本《巨富》)研究总结,现在富人比穷人累得多。他们工作时间超长,压力很大而且极不稳定。有的富豪认为自己必须每天凌晨两点半起床才能跟上世界变化的节奏。八小时工作制几乎成了穷人的特权。跟上一代富豪相比,新一代富豪的钱大都是自己赚的而不是继承的,70%以上的富豪的钱都是过去十年挣来的。哪怕是处在人口前0.01%的这些人,年收入超过一千万美元,其大部分收入也是来自工资和商业,而不是来自纯资本投资。

与此同时发达国家的“穷人” — 美国2013年的贫困线是三口之家年收入不到19530美元 — 的日子则相当不错。美国并不是一个以高福利著称的国家,但我们仍然经常能听到中产阶级华人移民对福利制度的抱怨。一个华人用自己辛苦挣的钱买了几处投资房出租。租客中有的家庭根本不工作完全靠福利生活,政府直接给交房租还发钱,拿着食品券偶尔还能吃顿龙虾。这位房东冬天去修房子发现人家的暖气开的比自己家都高,而且因为正在开party嫌进出麻烦连门都不关。他把这件事贴在中文论坛,所有跟帖者都表示了愤慨。这就难怪中产华人往往支持共和党,要求减税:凭什么让我们工作养你们这些不工作的?

就凭现在是物质极大丰富的时代。事实上,把钱送给穷人消费有利于社会进步。更重要的是,这么做还有利于经济增长。

美国经济史学家James Livingston 在2011年出版的 Against Thrift(《反节俭》)一书中提出,消费,不管在经济上,政治上还是道德上,都未必就不如工作。这本书总结了过去一百年美国经济增长的种种手段,非常值得当今中国借鉴。

经济学有一个“常识”:投资推动经济增长。资本家运营一个企业是为了获得利润,利润到手之后他并不是全都自己享受,而是把其中一部分投资出去搞扩大再生产,比如买机器和雇佣更多的工人。这样不但资本家可以在未来获得更多利润,还刺激了就业。利润,是经济增长的动力。也许并非所有经济学家都认同这个常识,但如果我们关注美国总统大选中的辩论,这是共和党人最喜欢的经济理论。共和党候选人说,你需要对投资减税,这样资本家就会扩大投资,经济和就业就会增长,而且你反过来可以收到更多的税。也许是根据这个原理,在包括美国在内的很多发达国家,投资收入的税率低于工资之类的所得税。

在消费和投资之间更鼓励投资,这个原理甚至与人类文明的传统美德暗合。你应该推迟享乐,不要有点钱就花了,省下来投资多好。

不过如果我们仔细想想,投资带来增长这个理论的背后其实有一个隐含的假设:市场是无限大的。只有每一笔投资生产出来的产品都一定能卖出去,不断投资才有意义。如果市场已经饱和,又没有新产品被发明出来,还投什么资?从物理学的角度看投资刺激增长显然是一个简单线性理论,在非线性条件下并不成立。

Livingston认为投资推动经济增长其实是个神话。不过他不需要发明任何物理学,因为在他看来经济学的思想巨变不是谁提一个新理论就能带来的,而必须是新的事实进来,必须是基于经验的 — 就如同哈勃发现宇宙膨胀和伽利略发现行星运动的模式一样。他要用历史事实来震动经济学。

Livingston考察美国历史经济数据,认为投资带动增长这件事,只在1919年以前成立。1920年以后,由于技术进步带来的生产自动化等因素,资本投入在单位产品中所占的比重就开始逐渐下降,社会已经不再需要更多的私人投资。1900年几乎所有投资都来自私人公司,而到2000年投资的大头来自政府花费和个人买房,私人公司投资对经济已经不那么重要了。生产率在提高,产出在增加,而本钱并不需要增加,那么结果就是利润增加。这些多出来的利润去了哪里呢?其并没有被投入到生产中,而是被投到了房地产、股市和国外。

这些进入股市和房地产的钱是泡沫和金融危机的根源。很多人抱怨2008年的金融危机是由于银行不负责任地把钱借给根本没有还款能力的人去买房,是由于华尔街的贪婪。但华尔街什么时候不贪婪?次贷问题的根本原因是钱如果不这么借出去,也没有更好的地方可去 — 是剩余利润实在太多了。传统上对1930年代美国经济大萧条的解释是米尔顿·弗里德曼的说法:中央银行信用紧缩,在该宽松借钱的时候没有做。而Livingston则认为其实大萧条是剩余利润过多导致的。事实上,整个1930年代银行和私人投资都是紧缩的,可是为什么经济从1933年就开始恢复增长了?

这个增长,以及从此之后美国经济的所有增长,都已经不是因为私人投资所代表的“效率”,而是因为“公平”。罗斯福新政做了两件事来增加工人工资。首先联邦政府宁可增加赤字也要办一系列的工程项目来创造就业。这种政府“投资”,并不是以获得利润为目的,而是以拉动就业为目的。其次,罗斯福允许公会成立,这使得工人跟资本家讨价还价的能力变强了。再加上医保和退休金等福利的增加,从此之后政府在美国经济中扮演越来越重要的角色。给地方和联邦政府工作成了增长最快的就业渠道,到60年代,18%到20%的劳动力是政府雇佣的。

但是到了1970年代中期,美国经济增长突然放慢了。放慢的原因这本书没有仔细说,我看另一本书,The Future Babble,的说法是因为当时发生了石油危机。政客们开始研究新的增长办法,达成的共识是用减税的办法刺激私人投资。这就是里根搞的一套。

历史证明里根经济学是好使的。但这一次的经济增长仍然不是私人投资的功劳。如果你仔细看数据,1981年从减税政策中获得最大好处的50家公司,其后两年的投资反而减少了。换句话说私人公司被减税之后并没有把省下来的钱投到生产中去。里根经济学真正的作用是通过扩大财政赤字的方法让消费者有钱去买东西。

但不论如何,里根政策使得工资所占比重在减少,资方所得所占比重在增加。那么为什么到了90年代经济仍然增长?这是因为有三个因素抵消了工资减少的效应:第一是社会福利等转移支付继续增加。第二是美国家庭越来越不爱存款,继续扩大消费。第三,也是最重要的一点,是信用卡越来越普及,借贷消费成为普遍现象。不过接下来,工资减少的这个趋势却逐渐到了必须出问题的时候,而布什的减税政策加剧了这一点,于是最终导致经济衰退。

这样Livingston讲了一个美国经济故事。这个故事的主题就是现在是消费,而不是投资,在拉动经济增长。但Livingston还不满足于此。他还打算整合一下马克思和凯恩斯的经济学。

马克思的理论说任何商品都有两个价值:使用价值和交换价值。在资本主义出现之前,人们进行生产和商品买卖的目的都是为了获得使用价值,而不是为了升值和存款。这个阶段被马克思称为“简单商品循环”,以C代表商品,M代表金钱,那么这个循环就是C-M-C。而资本主义出现以后,人们把交换价值,也就是获得更多金钱,当成生产和交换的目的,商品循环变成M-C-M,使用价值仅仅被当成获得交换价值的手段。简单地说就是过去人们做事是为了消费,而现在人们做事是为了让自己的资产升值。在这个资本主义时代,如果一个人把自己的所有工资都花了当月光族,他就会受到众人的鄙视;而如果这个人把钱用于购买各种理财产品投资出去,不花钱专门等着升值,他就会受到众人的尊敬。

每次发生金融危机或者经济衰退,就会有一批新一代的马克思主义者站出来说你看这证明了马克思是对的。马克思是对的吗?马克思的学说仍然把私人投资当成经济增长的动力。马克思主义者对这次美国经济衰退的解释是产业空洞化 — 传统制造业在减少,资本转而投到金融上。Livingston对此不以为然:现在都什么时代了,凭什么还非得用传统制造业来测量一个国家的经济能力?

马克思的贡献在于提出使用价值和交换价值的区别,而解释经济危机还得借助凯恩斯。1930年凯恩斯出版《货币论》,提出导致问题的是那些既没有被用于扩大再生产,也没有用于给个人股东分红的剩余利润。这正是Livingston在此书中强调的关键论点。众所周知凯恩斯强调需求和消费对经济增长的作用,而Livingston告诉我们凯恩斯还说过发达资本主义社会应该有一种新的道德观。凯恩斯曾经写文章说,现在工业化和自动化使得我们的劳动时间减少了,这其实不是坏事,而是好事。这说明经济问题被解决了,可以把人解放出来去消费。凯恩斯说人不应该为钱而工作。攒钱,而不是追求使用价值,其实是一种恶心的病态行为!

这样把马克思和凯恩斯结合起来,Livingston对这个物质极大丰富的时代提出了四个论点:
· 第一,产生经济衰退的原因是剩余利润。增加私人投资已经不能带来经济增长,应该靠消费带来增长。
· 第二,为扩大消费,应该搞财富的再分配,比如增加社会福利。
· 第三,投资应该社会化。决定一个项目是否上马,不应该只看其能带来多少利润,而应该全社会一起评估它的社会价值,也就是说要追求使用价值。
· 第四,花钱是道德的,消费文化是个好东西。

这个新道德标准值得专门说说。传统上我们认为人应该勤劳致富,富了以后把钱用于投资。存款很道德,而举债消费就不怎么道德。最起码,一个人花的钱应该都是他自己挣的。有统计表明美国一对退休夫妇平均一生之中对政府医保项目的贡献只有14万美元,而他们从这个医保中花掉的钱却高达43万美元。这道德么?如果我们假设消费带来增长,那么举债消费和接受社会福利就都是道德的。Livingston提出1990年以后美国经济的增长正是家庭债务带来的,债务降低了剩余利润的负面影响。

更进一步,Livingston提出一个有点惊世骇俗的观点:消费其实比工作更好。不过我必须给他补充一点,他这里说的工作是纯粹以挣钱为目的的工作。人工作是为了追求交换价值,而消费追求的是使用价值。衣服买回来立即失去交换价值,买衣服很大程度上是为了换取别人对自己的认同 — 凭这一点消费就比工作光荣:为增加社会效益而牺牲自己的金钱!从只知道赚钱养家的工人变成一个消费者,这其实是对人的提升。她开始关注别人怎么看自己!就这个机制,就足以给整个社会增加爱心。我们消费,在很多情况下纯粹是出于精神上的追求。往大了说就是追求比自己更伟大的东西,这是灵魂的升华。这就是为什么越是广告泛滥、消费文化发达的地方,人们越有同情心。

事实上,美国之所以会发生民权运动这样的社会进步,很大程度上得归功于消费文化。本来,爵士、蓝调、摇滚这些黑人音乐只在南方少数地区存在,再加上其艺术水平比不上古典音乐,入不了上层社会之耳,也就成不了主流。然而二十世纪以来品味没那么高的普通民众有钱了成了消费者了,而这时候正好唱片出现,黑人音乐才迅速流传开来。对黑人来说,这更意味着整体形象的提升,再加上媒体的广泛报道,黑人在全美国得到了广泛的同情。到1980年超级碗上出现黑人拍的广告,黑人文化正式成为美国主流文化。现在还有谁敢歧视黑人音乐?还有谁敢歧视黑人?马丁路德金这样的英雄人物当然有功,但是给他们带来战略机遇期的是消费者。

消费文化还可以解释更大社会变革。从1975年到1992年发生在东欧的天鹅绒革命,其本质并不是老百姓反对政府,“革命”没有游击队之类的武装斗争。Livingston认为这个革命的根本原因是东欧消费者也想要牛仔裤和流行音乐之类的现代商品,想要更多的休息时间和更多的艺术,而计划经济政府满足不了。苏联式经济体制一个重大缺陷是把利润过多地用于扩大再生产,而不是用于消费。在这样的背景下,后来当选捷克总统的著名持不同政见者哈维尔1978年的文章《无权者的力量》,根本就是一篇消费文化宣言。他说我们想要欣赏流行音乐的权利— 不仅仅是本国流行音乐,也要西方流行音乐!哈维尔还代言摇滚乐队,要求把演奏摇滚乐的自由作为基本人权。苏联体制的真正失败之处在于它提供不了这些东西。事实上,当时苏联通过石油出口换来外汇,从西方进口了很多东西,包括童装,来满足国人的需求。可是消费者看到西方电视节目以后想要更多!结果一点点小危机,比如食品价格上升,就会导致大事。

所有这些诉求,都可以用更早的时候美国左派的一个口号来概括:more。早在1907年美国经济学家Simon Patten就提出经济已经从短缺时代变成了过剩时代,过去是“疼痛经济”,现在则是“快乐经济”。Patten的学生Walter Weyl则在1912年出了一本书,The New Democracy,提出在这个时代如果能够搞好收入的再分配和生产的社会化,那么就可以不要绝对的社会主义,而变成有条件的社会主义。与此同时美国劳工联盟创始人Samuel Gompers,作为一个工人领袖,则提出他既不想推翻资本主义制度也不想搞垮大公司,他想要的是“合作社会(coorperative society)”。这是一种平行的社会结构,其发生在纯粹的资本主义之后,但又不是社会主义。Gompers说工人唯一要的就是more:更高的工资,更好的工作条件,更多的休闲时间等等。快乐经济会使得过去穷而无知的人变得富裕而有知识,那么民主也会加强,简直是一个非常理想的社会形态。

说到这里我们不得不提出这么一个问题:中国怎么没有采纳美国人早在中国建国前就提出来的这个温和的路线图,反而搞了比较极端的社会主义?一个最可能的原因当然是美国是发达国家,而早期的中国远远没有达到“快乐经济”。也许中国曾经有可能走这个路线,只是由于一些历史上偶然的原因没有走成。比如根据沈志华教授在《处在十字路口的选择:1956-1957的中国》这本书中的说法,1957年国际上一系列突发事件导致中国走了另一条路。不论如何,先有资本主义,等资本主义富裕到物质极大丰富的时代再搞合作社会,然后再谈搞不搞社会主义,这个路线图绝对不应该让任何社会主义者感到震惊。

不敢质疑经济学的历史学家不是好作者,但此书对剩余利润的担忧和批评显然不是新思想,凯恩斯以降的整个需求派经济学不都这么说吗?最近Daniel Alpert还出了一本 The Age of Oversupply,也说这个问题,而且还被批评其并无新意。也许Livingston在这方面的贡献是用美国经济史给需求派提供了子弹。另一个可能的批评是你如此推动“反节俭”,过度消费会不会导致资源不足和环境崩溃?但Livingston真正推崇的是使用价值。今天的很多政府项目其实已经是投资社会化,不是单纯追求盈利而把各种因素综合考虑。可是如果不是让钱,也就是市场去配置资源,你这个“投资社会化”到底能否有效运行,Livingston没有给我们提供更多论证。还有一点,把财富再分配— 对富人收更多的税来分给穷人— 这一招也不能无限使用,现在美国排在前10%的富人已经承担了过半的联邦税。我认为,提出消费文化是个好东西,是此书的最大亮点,尤其是在这个很多人反对消费文化的时刻。

在我看来,所谓“消费文化”,其实是人类历史上“普通人”的一次进步。过去无论文化、科学、艺术还是政治进步大多是精英推动的,升斗小民整天为最基本的生存条件奔忙对身外之物没什么可说的。普通人在原始社会是奴隶,在封建专制社会是农民,在资本主义社会是工人,换句话说都是给人干活的角色。而这个物质极大丰富的时代,给普通人带来一个新角色:消费者。作为消费者的普通人不必被压迫就有话可说。他们不再仅仅作为劳动力被社会选择,他们做选择。他们的喜好决定哪种艺术能够流行哪种科技能够壮大,以及哪个精英能变富豪。他们变得有思想有个性,他们追求能取得别人认同的使用价值,并因此把同情心用于推动社会进步。

也许消费文化还时不时表现的比较庸俗,也许消费者泛滥的同情心还时不时把政策搞坏,但是在更大的时间尺度上,只要有“more”— 更多的物质、教育和休闲时间,世界必将进化到人人都是贵族的一天。消费文化,才是真正的“庶民的胜利”。

24 Mar 15:21

无重复,不音乐

by 秦鹏

(文/Elizabeth Hellmuth Margulis)音乐是什么?思考过这个问题的哲学家足以把队伍排到海角天边,但是我们大部分人都觉得自己有把握这么说:“我听到音乐时自然就知道那是音乐。”不过,音乐性的判断标准还是出了名地灵活易变。那首一开始不忍卒听的夜总会曲子,说不定听过几遍之后,就好听得让人脚尖蠢蠢欲动了。把对音乐最缺乏兴趣的人与一位正在排练当代音乐演奏会的人关在一个屋子里,等他们出门的时候嘴里面也会吹着利盖蒂·捷尔吉·山多尔(Ligeti György Sándor)的曲子。简单的重复行为就可以成为音乐化的动因,仿佛有某种魔力一般。与其问“音乐是什么”,倒不如问“什么样的声音在我们听来是音乐”更省脑子,而这个问题的答案基本上是:“我再次听到的时候就能知道那是音乐。”

起码自罗伯特·扎荣克(Robert Zajonc)在20世纪60年代第一次证明“重复曝光效应”以来,心理学家便已经了解到,人们更喜欢之前接触过的事物。至于这个事物是三角铁还是图片还是旋律并不重要。人们在第二、三次接触时会表达出更多的喜爱,哪怕他们并不知道自己以前接触过它们。人们似乎会错误地将他们增长的知觉流畅性——对三角铁或者图片或者旋律的处理能力的提高——归因于对象本身的某种品质,而不是先前的经验。他们并不想“我以前见过这个三角铁,所以我才会认识它”,而是想“哈,我喜欢这个三角铁。它能显得我很聪明。”这种效应也会延伸到听音乐的行为中。但是越来越多的证据表明某些重复曝光效应以外的因素决定了音乐中重复的特殊作用。

首先来看一下纯粹的数量。全世界的所有文化都创造了重复性的音乐。伊利诺伊州大学人种音乐学家布鲁诺·内特尔(Bruno Nettl)统计了几种可以用于为全世界的音乐辨别特性的通用概念的重复率。美国收音机里播放的热门歌曲往往都有一段重复演唱的副歌部分,而人们对这些已然翻来覆去的歌曲还要听个不停。俄亥俄州立大学的音乐理论学家大卫·休伦(David Huron)估计人们听音乐时,超过90%的时间其实是在听以前听过的。管理软件iTunes里面的播放次数统计功能揭示出我们听自己喜欢的乐曲有多么频繁。如果这还不能说明问题,有些旋律会盘旋在我们的脑海中一遍遍地回放。简而言之,重复作为音乐的一种属性,普遍得令人惊讶,别管是真实存在的还是想象中的音乐。

事实上,重复与音乐性的联系之紧密,使得它可以用来将显然非音乐的材料戏剧性地转变为歌曲。加州大学圣地亚哥分校的心理学家戴安娜·多伊奇(Diana Deutsch)发现了一个格外有说服力的例证——语音变歌曲幻觉。这种幻觉从一句普普通通的口头话语开始:“The sounds as they appear to you are not only different from those that are really present, but they sometimes behave so strangely as to seem quite impossible.”(意为“声音在你听来不仅不同于其本来面目,有时候还会奇怪到似乎不可能的程度。”)

接下来,这句话的一部分——仅仅几个单词——被重复好几次。最后,原先的讲话录音被完整地播放一遍。当收听者听到曾被循环播放的短语时,感觉仿佛说话者唱了起来,仿佛是迪斯尼动画音乐的风格。

这一转换真的很奇特。你大概会觉得听某人讲话与听某人唱歌是两回事,声音本身的客观特征将两者区分开来。这似乎显而易见:一个人讲话时,我听到的就是她在讲话;一个人唱歌时,我听到的就是她在唱歌。但是语音变歌曲幻觉揭示出完全相同的一段声音序列可以听起来既像是话语又像是音乐,决定因素仅仅是它有没有被重复。重复事实上能够改变你的感知回路,以致把一段声音听成音乐:不是认为其与音乐近似,也不是产生听到音乐的预期,而是实实在在地体验到那些词语是被唱了出来。

这一幻觉展现了什么叫做以音乐的形式听到某种声音。“音乐化”将你的注意力从词语的意义转到了乐章的轮廓(音调高低的模式)和节律(音延长短的模式),甚至吸引你随之哼唱或者打节拍。事实上,以音乐的形式听某种声音的部分含义是在想象中参与其中。

当被听成音乐的时候,多伊奇的录音中的两个词“sometimes behave”几乎不可避免地牵出了后面的两个词“so strangely”。如果试着再听一遍原始录音并在“sometimes behave”之后暂停,你的意识会无法遏止地完成这一模式,自动补上后面的“so strangely”。当你把某种声音听成音乐时,你更多地是在听它的起承转合而不是在辩词达意。

重复是音乐这种参与属性的关键。我自己在阿肯色大学的实验室利用回旋曲进行了一些研究。这是一种曲式反复的音乐作品,在18世纪晚期尤为流行。在我们的研究中,据受试者的报告,相对于那些听到了副歌部分略有改变的回旋曲的人,听到重复部分完全一致的经典回旋曲的人更有打节拍或者跟着唱的倾向。而且,经典回旋曲为听者提供的参与机会很少,值得指出的是,那些明确要求广泛参与的音乐场合通常有着更多的重复——想想在教堂集会合唱中一个短语会被反复吟唱多少回吧。甚至在很多并不明显要求人们参与的音乐场合中(比如独自驾车时听收音机),人们仍然会以某种方式参与其中:从对着幻想中的吉他轻微摇摆,到大声跟着歌唱。

没有重复,音乐还能够存在吗?嗯,音乐不是自然物体,作曲家可以随意违抗它所表现出的任何倾向。其实在上个世纪,一些作曲家明显地开始在他们的作品中避免重复。在音乐认知实验室的最近一次实验中,我们为人们播放了这种音乐的一些样例,它们的作者是卢西亚诺·贝里奥(Luciano Berio)和艾略特·卡特(Elliott Carter)等20世纪享有盛名的作曲家。参与者有所不知的是,这些样例中有一些经过了数码改动。它们当中的部分片段被提取出来再重新插入。这些片段的摘选仅仅考虑了方便而未考虑美学效果。这使得改动过的乐章与原版相比仅仅多了一些重复的部分。

改动过的乐章本应非常难听,毕竟原版是近代最有名的一些作曲家的作品,而且修改版是在未考虑美学效果的情况下拼凑起来的。但是研究中的听众一致认为改动过的乐章更好听、更有趣,而且显然更有可能出自人类艺术家之手而非被计算机随机生成。研究中的听众都是大学本科生,在当代音乐艺术方面没有专门受过训练或者经验。

更有甚者,当我在音乐理论协会2011年年会上展示这些发现时,一些人惊讶地发现修改过的版本具有更强的说服力,尽管我的听众对那些曲目都非常熟悉,尽管他们知道自己在听什么。不可否认,这项研究并未考虑到音乐鉴赏的行家里手们经过特别开发过的欣赏习惯,但它的确揭示出有关听者如何感知陌生音乐的一些信息。重复就像是人类意图的标记。第一次听上去比较随意的一个短语,第二次听起来便有可能好像曾被蓄意编排并且有所表达。

我的实验室里的另一项研究试验了重复是否能使音乐的小片段听起来更具乐感。我们生成了音符的随机序列,以两种方式之一呈送给听者:原始版本或者循环版本。在循环版本中,随机序列被连续演奏了6次。在研究的一开始,人们收听自动依次演奏的音符序列。这些序列有一些是原始形式,有一些是循环形式(具体到各个序列采取何种形式,每一位参与者得到的安排各不相同)。后来,受试者们独自收听每一个随机序列——仅听一次,不再重复——然后评价其音乐感有多强。

他们听过了太多的序列,以至于记忆都有些混乱。他们无法明确地回想起哪些片段是被循环播放的,甚至也不能确定某个序列究竟有没有听过。尽管如此,他们还是一致认为曾被以循环方式听过的序列更具乐感。哪怕缺少明确记忆的帮助,随机序列的重复还是向他们灌输了音乐感。不管组成成分是什么,也不管是一串音节还是一串音阶,看来重复的蛮力便足以将一串声音变成音乐,这给我们聆听它们的方式带来重大改变。

为了理解这一过程是如何作用的,你自己可以尝试一个非常简单的把戏。让一位密友挑选一个词——比如说“棒棒糖”——然后对着你不停地说几分钟。你最终会有一种奇特的体验:声音和它们所代表的意义分离了。这就是100多年前便见诸文献的语义饱和效应。当词语的意义越加模糊,声音的品质却古怪地凸显出来——比如发音的特质、重复的爆破音b、连续3个江阳辙的韵母。简单的重复行为使一种新的聆听方式成为可能,与单词本身的感知属性发生了更加直接的对抗。

重复让音乐带给人特殊的聆听体验。

人类学家读到这里,大概会感觉似曾相识,因为人们现在已经了解到,仪式也是利用重复的力量将注意力集中到直接的感官细节而不是更加广泛的实践意义上。我所说的仪式是指一套千篇一律的动作序列,比如礼节性的洗碗。在这个例子中,重复使人们清晰地发现,清洗的动作并不仅仅是为了达到某种实际目的,比如把碗变得清洁,而是为了为让注意力集中在上面。

2008年,圣路易斯市华盛顿大学的心理学家帕斯卡·博伊尔(Pascal Boyer)和皮埃尔·列那尔(Pierre Liénard)更是宣称仪式创造了一种独特的注意状态。在这种状态下,我们在远比平常更加基础的层次上看待动作。在仪式以外,单一的动作往往不会被解读为其本身意义,它们也融入了我们对更宏大的事件流的理解当中。仪式将注意力从整体性的事件模式转向了构成事件的动作。见证者不仅会注意到碗正在被清洗,还会注意到随着每一次擦拭动作,手在碗边的加速,或者洗碗布被来回拖动时在碗面上的展卷。而且,动作的重复使见证者愈加难以不在想象中模仿它们,体会自己的手做出同样的动作会是什么感觉。正是以完全一致的方式,音乐中的重复令声音中细腻而有表现力的元素愈加易被听者所感知,也使他们参与其中的倾向——随之运动或者哼唱——愈加难以遏制。

了解到这些相似的情形之后,就不难理解那么多仪式其实都要依赖音乐方能进行了。而音乐本身似乎也是一种使人兴奋的有力工具。瑞典心理学家阿尔夫·加布里埃尔松(Alf Gabrielsson)曾经让几千人描述他们最强烈的音乐体验,然后调查他们对常见主题的反应。很多人报告称,他们最尖峰的音乐体验含有一种超然物外的感觉,仿佛约束不复存在,他们逃离了身体的限制,与他们正在收听的音乐融为一体。这种非常深刻动人的体验可以部分地归因于重复引起的注意力转移和参与感的提升。事实上,赫尔辛基大学的心理学家卡洛斯·卡雷拉(Carlos Pereira)及其同事证明了当我们收听熟悉的音乐时,脑中的情感区域就会表现出更强的活动,不管我们是不是喜欢听到的音乐。

哪怕是与我们自己的音乐偏好相悖的非自主重复也有着强大的力量。这就是为什么我们不喜欢但曾经听过很多遍的音乐有时候也会在我们脑海中阴魂不散,为什么我们可能在公交车上热情洋溢地跟着哼了半天才意识到自己听的其实是嘉丝婷·碧波(Justin Bieber)的《宝贝》(Baby)。再三的聆听使得一个声音似乎无可避免地与另一个连在了一起,所以当“苍茫的天涯是我的爱”响在耳边,“绵绵的青山脚下花正开”立即闪入脑际。很少有口述的话语在两部分之间存在这种无法抗拒的关联。当我们确实希望言语的不同部分能够以这种方式紧密相关——比如我们打算记下来所有的美国总统——我们或许会将它们编成曲子,然后重复吟唱。当此刻的声音片段似乎要注定引出接下来的片段时,耳中的乐感油然而生。重复加强了这种效应。

你能通过重复把任何事物变成音乐吗?不能,声音似乎有些特殊的性质。在几项研究中,诸如节奏、重复和频率等音乐的特质被注入了非听觉的领域——比如闪光,结果表明当基础的材料并非声音时,与音乐相关的独特心理过程更难以被激活。

还有一点值得指出:音乐的很多属性并非由重复所引发。把话语转变成歌曲或许是可能的,但是小提琴上拉出的单一音符无需任何特殊帮助,听上去便是确凿无疑的音乐。重复无法解释为什么小和弦听起来忧郁而减和弦听起来凶险。不过,它应该还是能够解释为什么一串这样的和弦就会带来催人奋起和无法逃避的感觉。

通过在音乐的空间里来回折返,重复令一段声音的序列听上去不再像是内容的客观呈现,而更像是令你跟随的某种拉力。它掌控了负责排序的回路,让你感觉是在参与音乐而非感知音乐。我们对音乐的这种认同感,这种物我相偕而非主客分明的聆听体验,如此明确地限定了何为我们所认为的音乐,也多半归功于重复的聆听。

重复在全世界的音乐中那令人讶异的流行并非偶然。音乐需要重复性并不是因为它不如语言复杂,而iTunes记录下你听喜欢的专辑听了347次也不是病态强迫症的证据——那只是音乐施展其魔力的关键伎俩。重复其实使我们感觉自己所聆听到的东西具有乐感。它在我们的意识中开挖出一条熟悉而有回报的通路,使我们在聆听时对每个乐句都立刻有所预期并参与其中。那种被音乐演奏的体验在我们与声音之间以及——当我们摘下耳机——我们与他人之间创造了共享主体性的感受:一种至少与喜欢的歌曲相伴始终的超然联系。

编译自:Elizabeth Hellmuth Margulis. One more time:Why do we listen to our favourite music over and over again? Because repeated sounds work magic in our brains. aeon.co

文章题图:shutterstock友情提供

 

相关的果壳网小组

 

你可能感兴趣

  1. 鸟鸣虽美,并非音乐
  2. 耳聋怎样改变了贝多芬的音乐
  3. 格莱美大赢家阿黛尔的情歌为何催人泪下?
  4. 唱歌永远不在调——你患了“失歌症”
  5. 小海狮,你节奏感这么强,地球人知道吗?
  6. 听Arduino在唱歌~
  7. 音乐中的非欧几何
  8. “莫扎特效应”没什么用?
  9. 为什么音乐让我们如此感动?
  10. 语言和音乐注定会出现吗?
  11. 玩网页游戏,教电脑给音乐分个类
  12. 有些大脑是音乐盲
14 Mar 11:06

Data Structures for Entity Systems – Contiguous memory

by Adam Martin

This year I’m working on two different projects that need an Entity System (ES). One of them is a non-game app written natively on iOS + Android. The other is an FPS in Unity3D.

There are good, basic Open-Source ES’s out there today (and c.f. the sidebar there). I tried porting a few, but none of them were optimized for performance, and most of them were too tightly coupled to a single programming language or platform. I’ve started a new ES of my own – Aliqua.org – to fix these problems, and I’m already using it in an app that’s in alpha-testing.

I’ll be blogging experiences, challenges, and ideas as I go.

Please note, the CSS on this site is broken at the moment. You may find this easier to read on this mirror (working CSS)

Background: focus on ES theory, or ES practice?

If you’re new to ES’s, you should read my old blog posts (2007 onwards), or some of the source code + articles from the ES wiki.

My posts focussed on theory: I wanted to inspire developers, and get people using an ES effectively. I was fighting institutionalised mistakes – e.g. the over-use of OOP in ES development – and I wrote provocatively to try and shock people out of their habits.

But I avoided telling people “how” to implement their ES. At the extreme, I feared it would end up specifying a complete Game Engine:

Give a man a game engine and he delivers a game. Teach a man to make a game engine and he never delivers anything.

— Juuso ○° (@sandbaydev) November 20, 2013

…OK. Fine. 7 years later, ES’s are widely understood and used well. It’s time to look at the practice: how do you make a “good” ES?

NB: I’ve always assumed that well-resourced teams – e.g. AAA studios – need no help writing a good ES. That’s why I focussed on theory: once you grok it, implementation concerns are no different from writing any game-engine code. These posts are aimed at non-AAA teams: those who lack the money (or expertise) to make an optimized ES first time around.

For my new ES library, I’m starting with the basics: Data Structures, and how you store your ES data in memory.

Where you see something that can be done better – please comment!

Aside on Terminology: “Processors, née Systems”

ES “Systems” should be batch-processing algorithms: you give them an array/stream of homogeneous data, and they repeat one algorithm on each row/item. Calling them “Processors” instead of “Systems” reduces confusion.

Why care about Data Structures?

There is a tension at the heart of Entity Systems:

  • In an ES game, we design our code to be Functional: independent, data-oriented, highly efficient for streaming, batching, and multi-threaded execution. Individual Processors should be largely independent, and easy to split out onto different CPU cores.
  • With the “Entity” (ID) itself, we tie those Functional chunks together into big, messy, inter-dependent, cross-functional … well, pretty much: BLOBs. And we expect everything to Just Work.

If our code/data were purely independent, we’d have many options for writing high-performance code in easy ways.

If our data were purely chunked, fixed at compile-time, we’d have tools that could auto-generate great code.

But combining the two, and muddling it around at runtime, poses tricky problems. For instance:

  1. Debugging: we’ve gone from clean, separable code you can hold in your head … to amorphous chunks that keep swelling and contracting from frame-to-frame. Ugh.
  2. Performance: we pretend that ES’s are fast, cache-efficient, streamable … but at runtime they’re the opposite: re-assembled every frame from their constituent parts, scattered all over memory
  3. Determinism: BLOBs are infamously difficult to reason about. How big? What’s in them? What’s the access cost? … we probably don’t know.

With a little care, ES’s handle these challenges well. Today I’m focussing on performance. Let’s look at the core need here:

  • Each frame, we must:
    1. Iterate over all the Processors
    2. For each Processor:
      1. Establish what subset of Entity/Component blobs it needs (e.g. “everything that has both a Position and a Velocity”)
      2. Select that from the global Entity/Component pool
      3. Send the data to the CPU, along with the code for the Processor itself

The easiest way to implement selection is to use Maps (aka Associative Arrays, aka Dictionaries). Each Processor asks for “all Components that meet [some criteria]“, and you jump around in memory, looking them up and putting them into a List, which you hand to the Processor.

But Maps scatter their data randomly across RAM, by design. And the words “jump around in memory” should have every game-developer whimpering: performance will be bad, very bad.

NB: my original ES articles not only use Maps, but give complete source implementations using them. To recap: even in 2011, Android phones could run realtime 30 FPS games using this. It’s slow – but fast enough for simple games

Volume of data in an ES game

We need some figures as a reference point. There’s not enough detailed analysis of ES’s in particular, so a while back I wrote an analysis of Components needed to make a Bomberman clone.

…that’s effectively a high-end mobile game / mid-tier desktop game.

Reaching back to 2003, we also have the slides from Scott’s GDC talk on Dungeon Siege.

…that’s effectively a (slightly old) AAA desktop game.

From that, we can predict:

  • Number of Component-types: 50 for AA, 150 for AAA
  • Number of unique assemblages (sets of Component-types on an Entity): 1k for AA, 10k for AAA
  • Number of Entities at runtime: 20k for AA, 100k for AAA
  • Size of each Component in bytes: 64bits * 10-50 primitives = 100-500 bytes

How do OS’s process data, fast?

In a modern game the sheer volume of data slows a modern computer to a crawl – unless you co-operate with the OS and Hardware. This is true of all games. CPU and RAM both run at a multiple of the bus-speed – the read/write part is massively slow compared to the CPU’s execution speed.

OS’s reduce this problem by pre-emptively reading chunks of memory and caching them on-board the CPU (or near enough). If the CPU is processing M1, it probably wants M2 next. You transfer M2 … Mn in parallel, and if the CPU asks for them next, it doesn’t have to wait.

Similarly, RAM hardware reads whole rows of data at once, and can transfer it faster than if you asked for each individual byte.

Net effect: Contiguous memory is King

If you store your data contiguously in RAM, it’ll be fast onto the Bus, the CPU will pre-fetch it, and it’ll remain in cache long enough for the CPU(s) to use it with no extra delays.

NB: this is independent of the programming-language you’re using. In C/C++ you can directly control the data flow, and manually optimize CPU-caching – but whatever language you use, it’ll be compiled down to something similar. Careful selection and use of data-structures will improve CPU/cache performance in almost all languages

But this requires that your CPU reads and writes that data in increasing order: M1, M2, M3, …, M(n).

With Data Structures, we’ll prioritize meeting these targets:

  1. All data will be as contiguous in RAM as possible; it might not be tightly-packed, but it will always be “in order”
  2. All EntitySystem Processors will process their data – every frame (tick) – in the order it sits in RAM
    • NOTE: a huge advantage of ES’s (when used correctly) is that they don’t care what order you process your gameobjects. This simplifies our performance problems
  3. Keep the structures simple and easy to use/debug
  4. Type-safety, compile-time checks, and auto-complete FTW.

The problem in detail: What goes wrong?

When talking about ES’s we often say that they allow or support contiguous data-access. What’s the problem? Isn’t that what we want?

NB: I’ll focus on C as the reference language because it’s the closest to raw hardware. This makes it easier to describe what’s happening, and to understand the nuances. However, these techniques should also be possible directly in your language of choice. e.g. Java’s ByteBuffer, Objective-C’s built-in C, etc.

Usually you see examples like a simple “Renderer” Processor:

  • Reads all Position components
    • (Position: { float: x, float y })
  • Each tick, draws a 10px x 10px black square at the Position of each Component

We can store all Position components in a tightly-packed Array:

compressed-simple-array

This is the most efficient way a computer can store / process them – everything contiguous, no wasted space. It also gives us the smallest possible memory footprint, and lets the RAM + Bus + CPU perform at top speed. It probably runs as fast or faster than any other engine architecture.

But … in reality, that’s uncommon or rare.

The hard case: One Processor reads/writes multiple Component-types

To see why, think about how we’d update the Positions. Perhaps a simple “Movement” Processor:

  • Reads all Position components and all Velocity components
    • (Position: { float: x, float y })
    • (Velocity: { float: dx, float dy })
  • Each tick, scales Velocity.dx by frame-time, and adds it to Position.x (and repeats for .dy / .y)
  • Writes the results directly to the Position components

“Houston, we have a problem”

This is no longer possible with a single, purely homogeneous array. There are many ways we can go from here, but none of them are as trivial or efficient as the tight-packed array we had before.

Depending on our Data Structure, we may be able to make a semi-homogeneous array: one that alternates “Position, Velocity, Position, Velocity, …” – or even an array-of-structs, with a struct that wraps: “{ Position, Velocity }”.

…or maybe not. This is where most of our effort will go.

The third scenario: Cross-referencing

There’s one more case we need to consider. Some games (for instance) let you pick up items and store them in an inventory. ARGH!

…this gives us an association not between Components (which we could handle by putting them on the same Entity), but between Entities.

To act on this, one of our Processors will be iterating across contiguous memory and will suddenly (unpredictably) need to read/write the data for a different Entity (and probably a different ComponentType) elsewhere.

This is slow and problematic, but it only happens thousands of times per second … while the other cases happen millions of times (they have to read EVERYTHING, multiple times – once per Processor). We’ll optimize the main cases first, and I’ll leave this one for a later post.

Iterating towards a solution…

So … our common-but-difficult case is: Processors reading multiple Components in parallel. We need a good DS to handle this.

Iteration 1: a BigArray per ComponentType

The most obvious way forwards is to store the EntityID of each row into our Arrays, so that you can match rows from different Arrays.

If we have a lot of spare memory, instead of “tightly-packing” our data into Arrays, we can use the array-index itself as the EntityID. This works because our EntityID’s are defined as integers – the same as an array-index.

rect3859

Usage algorithm:

  • For iterating, we send the whole Array at once
  • When a Processor needs to access N Components, we send it N * big-arrays
  • For random access, we can directly jump to the memory location
    • The Memory location is: (base address of Array) + (Component-size * EntityID)
    • The base-address can easily be kept/cached with the CPU while iterating
    • Bonus: Random access isn’t especially random; with some work, we could optimize it further

Problem 1: Blows the cache

This approach works for our “simple” scenario (1 Component / Processor). It seems to work for our “complex” case (multiple Components / Processor) – but in practice it fails.

We iterate through the Position array, and at each line we now have enough info to fetch the related row from the Velocity array. If both arrays are small enough to fit inside the CPU’s L1 cache (or at least the L2), then we’ll be OK.

Each instance is 500 bytes
Each BigArray has 20k entries

Total: 10 MegaBytes per BigArray

This quickly overwhelms the caches (even an L3 Cache would struggle to hold a single BigArray, let alone multiple). What happens net depends a lot on both the algorithm (does it read both arrays on every row? every 10th row?), and the platform (how does the OS handle RAM reads when the CPU cache is overloaded?).

We can optimize this per-platform, but I’d prefer to avoid the situation.

Problem 2: memory usage

Our typeArray’s will need to be approimately 10 megabytes each:

For 1 Component type: 20,000 Entities * 50 variables * 8 bytes each = 8 MB

…and that’s not so bad. Smaller components will give smaller typeArrays, helping a bit. And with a maximum of 50 unique ComponentTypes, we’ve got an upper bound of 500 MB for our entire ES. On a modern desktop, that’s bearable.

But if we’re doing mobile (Apple devices in 2014 still ship with 512 MB RAM), we’re way too big. Or if we’re doing dynamic textures and/or geometry, we’ll lose a lot of RAM to them, and be in trouble even on desktop.

Problem 3: streaming cost

This is tied to RAM usage, but sometimes it presents a bottleneck before you run out of memory.

The data has to be streamed from RAM to the CPU. If the data is purely contiguous (for each component-type, it is!), this will be “fast”, but … 500 MB data / frame? DDR3 peaks around 10 Gigabytes / second, i.e.:

Peak frame rate: 20 FPS … divided by the number of Processors

1 FPS sound good? No? Oh.

Summary: works for small games

If you can reduce your entity count by a factor of 10 (or even better: 100), this approach works fine.

  • Memory usage was only slightly too big; a factor of 10 reduction and we’re fine
  • CPU caching algorithms are often “good enough” to handle this for small datasets

The current build of Aliqua is using this approach. Not because I like it, but because it’s extremely quick and easy to implement. You can get surprisingly far with this approach – MyEarth runs at 60 FPS on an iPad, with almost no detectable overhead from the ES.

Iteration 2: the Mega-Array of Doom

Even on a small game, we often want to burst up to 100,000+ Entities. There are many things we could do to reduce RAM usage, but our biggest problem is the de-contiguous data (multiple independent Arrays). We shot ourselves in the foot. If we can fix that, our code will scale better.

es-datastructures-structured-bigarray

In an ideal world, the CPU wants us to interleave the components for each Entity. i.e. all the Components for a single Entity are adjacent in memory. When a Processor needs to “read from the Velocity and write to the Position”, it has both of them immediately to hand.

Problem 1: Interleaving only works for one set at a time

If we interleave “all Position’s with all Velocity’s”, we can’t interleave either of them with anything else. The Velocity’s are probably being generated by a different Processor – e.g. a Physics Engine – from yet another ComponentType.

mega-array

So, ultimately, the mega-array only lets us optimize one Processor – all the rest will find their data scattered semi-randomly across the mega-array.

NB: this may be acceptable for your game; I’ve seen cases where one or two Processors accounted for most of the CPU time. The authors optimized the DS for one Processor (and/or had a duplicate copy for the other Processor), and got enough speed boost not to worry about the rest

Summary: didn’t really help?

The Mega Array is too big, and it’s too interconnected. In a lot of ways, our “lots of smaller arrays – one per ComponentType” was a closer fit. Our Processors are mostly independent of one another, so our ideal Data Structure will probably consist of multiple independent structures.

Perhaps there’s a halfway-house?

Iteration 3: Add internal structure to our MegaArray

When you use an Entity System in a real game, and start debugging, you notice something interesting. Most people start with an EntityID counter that increases by 1 each time a new Entity is created. A side-effect is that the layout of components on entities becomes a “map” of your source code, showing how it executed, and in what order.

e.g. With the Iteration-1 BigArrays, my Position’s array might look like this:

rect3859

  1. First entity was an on-screen “loading” message, that needed a position
  2. BLANK (next entity holds info to say if loading is finished yet, which never renders, so has no position)
  3. BLANK (next entity is the metadata for the texture I’m loading in background; again: no position)
  4. Fourth entity is a 3d object which I’ll apply the texture to. I create this once the texture has finished loading, so that I can remove the “loading” message and display the object instead
  5. …etc

If the EntityID’s were generated randomly, I couldn’t say which Component was which simply by looking at the Array like this. Most ES’s generate ID’s sequentially because it’s fast, it’s easy to debug (and because “lastID++;” is quick to type ;)). But do they need to? Nope.

If we generate ID’s intelligently, we could impose some structure on our MegaArray, and simplify the problems…

  1. Whenever a new Entity is created, the caller gives a “hint” of the Component Types that entity is likely to acquire at some time during this run of the app
  2. Each time a new unique hint is presented, the EntitySystem pre-reserves a block of EntityID’s for “this and all future entities using the same hint”
  3. If a range runs out, no problem: we add a new range to the end of the MegaArray, with the same spec, and duplicate the range in the mini-table.
  4. Per frame, per Processor: we send a set of ranges within the MegaArray that are needed. The gaps will slow-down the RAM-to-CPU transfer a little – but not much

es-datastructures-structured-megaarray

Problem 1: Heterogeneity

Problem 1 from the MegaArray approach has been improved, but not completely solved.

When a new Entity is created that intends to have Position, Velocity, and Physics … do we include it as “Pos, Vel”, “Pos, Phys” … or create a new template, and append it at end of our MegaArray?

If we include it as a new template, and insist that templates are authoritative (i.e. the range for “Pos, Vel” templates only includes Entities with those Components, and no others) … we’ll rapidly fragment our mini-table. Every time an Entity gains or loses a Component, it will cause a split in the mini-table range.

Alternatively, if we define templates as indicative (i.e. the range for “Pos, Vel” contains things that are usually, but not always Pos + Vel combos), we’ll need some additional info to remember precisely which entities in that range really do have Pos + Vel.

Problem 2: Heterogeneity and Fragmentation from gaining/losing Components

When an Entity loses a Component, or gains one, it will mess-up our mini-table of ranges. The approach suggested above will work … the mini-table will tend to get more and more fragmented over time. Eventually every range is only one item long. At that point, we’ll be wasting a lot of bus-time and CPU-cache simply tracking which Entity is where.

NB: As far as I remember, it’s impossible to escape Fragmentation when working with dynamic data-structures – it’s a fundamental side effect of mutable data. So long as our fragmentating problems are “small” I’ll be happy.

Problem 3: Heterogeneity and Finding the Components within the Array

If we know that “Entity 4″ starts at byte-offset “2048″, and might have a Position and Velocity, that’s great.

But where do we find the Position? And the Velocity?

They’re at “some” offset from 2048 … but unless we know all the Components stored for Entity 4 … and what order they were appended / replaced … we have no idea which. Raw array-data is typeless by nature…

Iteration 4: More explicit structure; more indexing tables

We add a table holding “where does each Entity start”, and tables for each Component stating “offset for that Component within each Entity”. Conveniently, this also gives us a small, efficient index of “which Entities have Component (whatever)”:

es-datastructures-structured-megaarray-by-component

Problem 1: non-contiguous data!

To iterate over our gameobjects, we now need:

  • One big mega-array (contiguous)
  • N x mini arrays (probably scattered around memory)

Back to square one? Not quite – the mini-arrays are tiny. If we assume a limit of 128,000 entities, and at most 8kb of data for all Components on an Entity, our tables will be:

[ID: 17bits][Offset: 13 bits] = 30 bits per Component

…so that each mini-array is 1-40 kB in size. That’s small enough that several could fit in the cache at once.

Good enough? Maybe…

At this point, our iterations are quite good, but we’re seeing some recurring problems:

  • Re-allocation of arrays when Components are added/removed (I’ve not covered this above – if you’re not familiar with the problem, google “C dynamic array”)
  • Fragmentation (affects every iteration after Iteration 1, which doesn’t get any worse simple because it’s already as bad as it could be)
  • Cross-referencing (which I skipped)

I’ve also omitted history-tracking – none of the DS’s above facilitate snapshots or deltas of game state. This doesn’t matter for e.g. rendering – but for e.g. network code it becomes extremely important.

There’s also an elephant in the room: multi-threaded access to the ES. Some ES’s, and ES-related engines (*cough*Unity*cough*), simply give-up on MT. But the basis of an ES – independent, stateless, batch-oriented programming – is perfect for multi threading. So there must be a good way of getting there…

…which gives me a whole bunch of things to look at in future posts :).

PS … thanks to:

Writing these things takes ages. So much to say, so hard to keep it concise. I inflicted early drafts of this on a lot of people, and I wanted to say “thanks, guys” :). In no particular order (and sorry in advance if final version cut bits you thought should be in there, or vice versa): TCE’ers (especially Dog, Simon Cooke, doihaveto, archangelmorph, Hypercube, et al), ADB’ers (Amir Ebrahimi, Yggy King, Joseph Simons, Alex Darby, Thomas Young, etc). Final edit – and any stupid mistakes – are mine, but those people helped a lot with improving, simplifying, and explaining what I was trying to say.

09 Mar 05:39

侯世达:教计算机如何思考的人

by

侯世达对计算机程序的描述不仅准确,还富于创意,他对“我们大脑中的秘密软件结构”的描绘,开启了整整一代年轻人对AI的探索。图片来源:theatlantic.com

(文/James Somers)“这要看你说的‘人工智能’指的是什么。”侯世达(Douglas Hofstadter)站在印第安纳州布鲁明顿的一家杂货店里,边挑选沙拉原料边说。“如果有人眼中人工智能指的是理解心智的努力,或者创造类似人的心智,他们可能会说——也许话不会说得这么绝——但是他们可能会说,这是仅有的几样好研究之一。”

侯世达这句话说得很随和,但也很有数。他这么说,是因为他坚信,IBM的问答游戏超级电脑Watson、苹果公司的语音助手Siri 这些现代人工智能领域里最激动人心的项目,被公众视为通往科幻世界阶梯的东西,其实跟人工智能并没有多少干系。在过去的30年中,侯世达和他的研究生一直在钻研一个被人遗忘的课题:通过写出会思考的计算机程序来弄清人类的思维是如何工作的。

他们的操作前提很简单:思维是一个非同一般的软件,而理解一个软件工作原理的最好方法,就是自己写一个。电脑拥有足以模拟人类神经回路的灵活性,但是却仍旧只对精确的指令有所响应。因此,如果侯世达的研究成功了话,将是一举两得的胜利:我们将终于得知我们思维的工作机理,同时制造出智能的机器。 

一鸣惊人,“集異璧之大成”

改变侯世达一生的想法,是他“在路上”时得到的。当时,在美国俄勒冈大学粒子物理系读研究生的侯世达,由于博士论文进展无望而感到“深深地迷失了”。于是,在1972年夏天,他决定收拾好东西,塞进一辆被他称为“水银”的车里,从西向东穿越美国。每天晚上都在一处新的地方(“有时在林间,有时在湖畔”)架起帐篷,就着手电筒的光看书。他可以自由思考任何他想思考的事情,侯世达选择了思考思维本身。在他大约14岁的时候,侯世达发现他最小的妹妹莫莉因为“大脑深处的一些问题”而无法理解语言(她的神经症状可能是先天的,从未得到诊断),从那时起,他就暗暗地对意识和物质的关系着了迷。心理学之父威廉·詹姆斯(William James)在1890年将其称为“世界上最不可思议的事情”:意识怎么会是基于物质的呢?几斤重的灰质是如何产生思想和自我意识的呢?

在开着这辆1956年的老车漫游的途中,侯世达认为他找到了答案——思维就活在一个数学证明的核心里。1931年,生于奥地利的逻辑学家哥德尔(Kurt Gödel)证明,一个数学系统不仅能描述数字,还能用于描述系统本身。而侯世达想说,意识正是从同样的“跨层级反馈循环”里涌现的。一天下午,他坐下来把自己的这些零碎想法诉诸文字,写信给一个朋友。但30页稿纸写完以后,侯世达决定不把信发出去了;他要让这些想法再酝酿一段时间。7年以后,这些思绪酝酿成了一部重3斤、长777页的巨著《哥德尔、埃舍尔、巴赫:集异璧之大成》(Gödel, Escher, Bach: An Eternal Golden Braid),这本书将为其时年仅35岁、头一次出书的侯世达赢得1980年的普利策非虚构类作品文学奖。 

《集异璧》甫一面世便轰动一时。这本书的成功也得益于《科学美国人》的著名专栏作家马丁·加德纳(Martin Gardner),后者在1979年的一期专栏中详细介绍了此书,并且毫不吝啬溢美之词。加德纳写道:“每隔几十年,便会有个不知名的作者带来一本书,其之深、之明、之广、之智、之美、之新,顷刻便成为文坛公认之幸事。” 第一个获得计算机科学(当时还叫“通讯科学”)博士学位的美国人约翰·霍兰德(John Holland)回忆道,“我认识的人基本上态度都是强烈的敬畏。”

侯世达似乎注定会成为那段文化不可磨灭的一部分。集异壁不仅仅是一部影响力巨大的书,更是一本完全关于未来的书。人们称它为人工智能——计算科学、认知科学、神经科学和心理学交汇处的研究领域——的圣经。侯世达对计算机程序的描述不仅准确,还富于创意,他对“我们大脑中的秘密软件结构”的描绘,开启了整整一代年轻人对AI的探索。 

但后来人工智能变了,而侯世达没有跟着一起变,于是他从公众的视野中消失了。

消失

集异壁的到来正好遇上了AI历史轨迹的拐点。1980年代初,人工智能领域正日益萎缩:长期“基础科研”的资金不断缩水,而研究的焦点也逐渐向实用型系统转移。志存高远的AI研究名声逐渐坏掉了。到处都是好高骛远、虚空夸大的许诺,这一切的源头都可追溯至人工智能领域兴起之时——1956年,达特茅斯会议的组织者,包括人工智能这个词的创造者约翰·麦卡锡(John McCarthy)在内的人宣称,“只要精心挑选出一批科学家干上一夏天”,他们就能在创造出具有以下性能的机器上面取得显著的进展:会使用语言、形成概念、解决只有人类才能解决的问题,并且自我完善。麦卡锡后来回忆说,他们失败的原因是“AI比我们想的要难”。 

随着战争压力的不断积累,人工智能研究的主要赞助方——美国国防部的高级计划研究局(ARPA)缩紧了开支。1969年,美国国会通过曼斯菲尔德修正案,规定国防部只能资助拥有“对某一具体军事行动或计划具有直接和明显的关系”的研究。1972年,ARPA更名为DARPA,“D”代表国防部,凸显其对拥有军事应用潜力的研究计划的侧重。到上世纪70年代中期,DARPA问自己:我们在10年的时间里花了5000万美元用于各种探索性研究,最终得到了哪些具体的国防进展呢? 

到1980年代初,这股压力愈发巨大。本以回答阿兰·图灵“机器能思考吗”这一疑问起家的人工智能,渐渐成熟——或者异化,取决你用什么眼光来看它——变成了软件工程的分支领域,受到实际应用的驱动。相关研究的时间尺度越来越短,常常是已经想好了特定的买主。美国军方偏好那些涉及“命令和操控”系统的计划,比如战斗机飞行员的计算机飞行辅助系统,和自动识别道路、桥梁、坦克和油罐的空中测绘软件。私人产业流行的,则是服务于各种各样专门需求的“专家系统”,比如堆选择系统可以帮助设计者选择建筑地基的合适材料,而自动线缆专家系统可以自行总结电话线维护的相关报告。

在集异壁中,侯世达呼吁将人工智能研究的重点从如何智能化地解决人类问题,拉回到理解人类智力的方向上来,但这个呼声正好是在AI研究因收获甚少而遭到世人抛弃的时节。侯世达的光环很快就消失了,他发现自己在越来越多的时候都置身主流之外,而这个主流正展开双臂,拥抱新的使命:使机器以任何方式运转,丝毫不考虑心智上的合理性。 

以IBM的深蓝计算机为例,深蓝打败了著名的国际象棋特级大师卡斯帕罗夫(Garry Kasparov),完全是凭借计算的蛮力。每走一步棋,深蓝都能思考对手的棋着、自己的回应及对手的回应……如此达到六个来回甚至更多。运用快速评估函数,它能为每一种可能的棋盘局势计算出分数,然后选择能带来最高分数的棋步。让深蓝打败最优秀人类的,是它的纯计算力。它最多能在一秒钟之内评估3.3亿个棋盘局势,而卡斯帕罗夫在下每一步棋前最多只能评估几十个。 

想通过爬树到达月球的人

侯世达想问,没有任何启示的胜利得来又有何用?“没错,”他说,“深蓝下得一手好棋——那又如何?它可曾告诉过你我们如何下棋?没有。它可曾告诉过你卡斯帕罗夫如何看待、如何理解盘上的局势?”不去尝试回答这些问题的AI,无论名号有多亮、能力有多强,在侯世达看来,都偏离了正轨;几乎从他进入AI领域的那一刻起,他就刻意与之疏远了。“对于我这个羽翼未丰的AI研究者,”他说,“这是不言自明的——我不想牵扯到那种花招里面。这很显然,我不想拿来什么华丽的程序、用它的行为冒充智能,如果我明知它和智能毫无关系的话。而我也不知道为什么没有更多的人像我这么做。”

为什么?一个答案是,AI产业在80年代初期大约有几百万美元,而到80年代末已经价值数十亿。(1997年深蓝赢棋之后,IBM的股票份额增长了180亿美元。)工程领域的人工智能越保守,其成果就越大。今天,那些和思维毫无关系的技术正是如日中天。它们的根系深植于所有的重工业、交通运输和金融产业。谷歌的许多核心功能、Netflix的电影推荐、Watson、Siri、无人机和无人驾驶汽车都是它们在推动。

“当莱特兄弟和其他人不再模仿鸟类,开始学习空气动力学时,‘人类飞翔’的探索便成功了。”斯图尔特·罗素(Stuart Russell)和彼特·诺维格(Peter Norvig)在其教科书《人工智能:一种现代方法》(Artificial Intelligence: A Modern Approach)中这样写。当人工智能不再模仿人类,AI便见效了,因为人工智能胜过了人。飞机不扇翅膀也能飞翔,电脑何必需要会思考呢? 

这是一个很有力的观点。但仔细想想我们想要的是什么,它就少了一些说服力——我们想要的,是一个真的知道你想找什么的搜索引擎,正如一个人类能知道的那样。加州大学伯克利分校计算机科学教授斯图尔特·罗素说,“网络上所有的搜索引擎公司,市值加起来能有多少?可能有4000或5000亿美元吧。能够把网络上的所有信息都提取出来并理解的引擎,价值10倍于此。”

而这,就是本领域里的万亿美元问题:如今支撑AI的技术方法,一个没有借鉴思维机制、而是建立在大数据和大工程之上的方法,能将我们带向我们想去的地方吗?如果不懂是如何理解的,你要怎么建造一个能理解的搜索引擎呢?或许,正如罗素和诺维格在他们教科书的最后一章中婉言承认的那样,选择这条实用型道路的结果,就是AI更像是一个想要爬树到达月球的人,“他能不断进步,直到树的尽头”。 

思考是什么?

想想吧,如今的计算机识别一个手写的“A”都困难重重。事实上,这个任务对它们来说难度之大,在此基础上建立起了“CAPTCHA”(区分计算机和人类的完全自动化公共图灵测试)系统,就是比方说你注册网站时要你辨认并写出的那一串七歪八扭的字符。 

侯世达认为这没有什么好大惊小怪的。他在1982年的一篇文章中写道,要了解所有“A”的共同点,就像“了解心智范畴的流体本质” ,而这,在侯世达看来,正是人类智力的核心。 

侯世达喜欢说“认知即识别”。他认为,“看作”是最基本的认知行为:你见到一些线条,把它看作是一个“A”;你见到一堆木头,把它看作“桌子”;你把一场会议看作“皇帝新衣”的场景,把朋友的撇嘴看作“酸葡萄心理”,把一个年轻人的打扮看作“嬉普士”,如此等等,全天持续不断。这就是“理解”。但是理解的机制是什么?30年来,侯世达和他的学生一直在努力寻找这个答案,试图构建“思维基本机制的计算机模型”。

“每时每刻,”侯世达在最新的书、与埃马纽埃尔·桑德(Emmanuel Sander)合著的《表现和本质》(Surfaces and Essences)中写道:“我们都同时面对数目不定的相互重叠交融的情景。”而从无序的混沌中找出意义来,便是我们人类,作为要存活下去的生物必须做的事情。我们通过让恰当的概念浮现在脑海中来完成这一任务。这一切是自动发生的,一直如此。类比是侯世达最常用的词。他的新书(封面是一大堆各式各样的字母A)的主旨,就是类比是“思考的燃料和火焰”,是我们每人每天精神生活的必须品。 

“看看你们的谈话,”侯世达说,“你会一次又一次惊讶地发现,这就是制造类比的过程。”有人说了件事情,让你想到了另外的一件事情;你说了一件事情,又让另一个人想到了又一件事情……这就是谈话。没有比谈话更直观的事情了。但是,就在每一步,我们的大脑都做了一个类比,这一个精神上的跳跃是惊人地复杂,从计算的角度上说简直可以称为奇迹。不知通过何种方法,我们的大脑能够去除其他不相干的枝叶和细节,提出出它的核心,它的“无附赘的本质”,然后从你自己过去的想法和经验中只提取出切题的那一个故事或评论。 

“注意那些平白无奇的短语,比如‘没错,这正是遇到的事情!’”侯世达写道:“在这些看似简单的说法背后隐藏着人类思维的全部秘密。” 

在集异壁出版后的这些年,侯世达和人工智能渐行渐远。如果你现在从书架上抽出一本《人工智能:一种现代方法》,一千多页的书里都找不到侯世达的名字。同行都在用过去式谈论他。集异壁的新读者看到书的初版年代,然后发现作者还活着时都很惊讶。 

当然,在侯世达讲来,故事的发展是这样:当所有其他研究AI的人都开始建造东西,他和他的团队——用侯世达的好友、哲学家丹尼尔·丹尼特(Daniel Dennett)的话说——“耐心地、系统地、高明地”远离聚光灯、一点点攻克真正的难题。“很少有人对人的智力怎么工作感兴趣,”侯世达说:“那正是我们想知道的——思考是什么?——而且我们不会迷失方向。” 

“我是说,谁知道呢?”他说。“谁知道会发生什么。也许有一天人们会说,‘侯世达已经做过这些东西、讲过这些东西了,但我们现在才发现它’。”

这听起来就像是一个迷路家伙的自我安慰。但是侯世达这样的人,就是会引诱你问这样的问题:如果人工智能(“真正的人工智能”,用侯世达的话说)的最好思想,真的就藏在布鲁明顿的一个抽屉里泛黄呢?

“每天都在探索,完全无法不去探索,完全陷在了这些东西里”

如果说有些孩子生来就注定要踏上犯罪的道路,那么侯世达就是生来就要走心智的路。他成长在50年代的斯坦福,家就在大学的校园里,北边的社区连名字都是“教授村”。父亲罗伯特(Robert)是核物理学家,后来获得了1961年的诺贝尔物理学奖。母亲南希(Nancy)对政治颇为热衷,后来成为发育性残疾儿童福利问题的积极倡导者,是艾格纽斯发育中心(Agnews Developmental Center)伦理委员会的委员,同样也是在这所机构里,他妹妹莫莉度过了20多年的时光。按笑话的说法,南希是那种“职业的教员夫人”:在她的组织下,侯世达家的客厅变成了沙龙,一群关系密切的朋友会聚在一起边听爵士乐边谈论启发性话题,“科学和艺术的圆融”,侯世达对我说——这是智力的盛宴。

侯世达照单全收。他痴迷于父母的朋友们,还有他们关于“最微小和最宏大的东西”的奇特谈话。(有一次他说,他八岁时的梦想是成为一个“零质量、自旋1/2的中微子”。)他会留在物理系等四点的下午茶,“就好像我是个12岁的小研究生”。他好奇心旺盛,永不满足,永远不会无聊,“对各种各样的想法着迷”。他的学术研究风格过去是、现在也是,用他的话说,“暴饮暴食”式的:他可以一连弹上7个小时的钢琴,背诵1200行的《叶甫盖尼·奥涅金》全诗。有一次,他花了好几个星期的时间对着录音机自学倒着说话,这样当他反向播放磁带时,听着就是正常语序的英语了。他会一连几个月沉浸在正宗法语里、写计算机程序输出荒谬的故事、或者研究几十种勾股定理的证明,直到他能“看见它成立的原因”。“基本上每天都在探索这些事情”,他说,“无法不去探索。就是完全入魔,完全痴迷于这些东西。” 

侯世达今年68岁了,但他身上仍旧有些东西永不衰老。这是一个活在论文、软件和自己大脑中的人,这样的一个人会如常人般变老吗?尽管鬓发灰白不整、刚刚过耳,身躯脆弱下垂,但侯世达那种把自己当真的劲头、急切的向人表露的坦诚,都还是非常年轻的人的气质。和侯世达交往并不容易,他不是一个平易近人的人。他是那种会让晚宴上的所有人都跟着他一起吃素的素食主义者,那种会公开纠正别人的“性别歧视的语言”的人。“他有好多规矩,”侯世达的老朋友彼得·琼斯说,两人相识已经60年,“比如他特别讨厌‘you guys’这个短语。这是个律令。如果你要和他说话,你最好是别说you guys。”

三十多年来,侯世达一直在美国印第安纳大学布鲁明顿分校担任教授。他和前年9月份刚结婚的妻子林葆芬住在距离校园几个街区的地方,他和前妻的两个孩子丹尼和莫妮卡都已经长大成人。他虽然和认知科学研究项目关系密切,在计算机科学系、心理学和脑科学系、比较文学和哲学系也有担任职务,但侯世达没有任何正式的职责。他说,“我的工作是你能想到最轻松的,我在做的就是我想做的事情。” 

他的大部分时间花在家中二楼的两间书房里,铺着地毯,有点拥挤,整洁程度总是不让他满意。书房是侯世达世界的中心。他在那里读书、听音乐、做研究、画画、写书、写邮件。(侯世达每天要花上4个小时写电子邮件。“对我来说”,他曾说过,“电子邮件和信件是一样的,每一点都跟信件一样正式、一样精细、一样字斟句酌。我重写、重写、重写、重写我的每一封邮件,向来如此。”)书房是侯世达思考的地方,那里也反映出侯世达的思想。每一堵墙都堆满了书籍、绘画、笔记本和文件夹,思想在这里凝固、成形、四处喷洒。这就像是他“暴饮暴食”经历的一座博物馆,是《囤积癖》的某一集里的场景。

“任何我思考的东西都变成了我职业生涯的一部分。”侯世达说。和他共同编辑了《心智中的我》的哲学家丹尼尔·丹尼特说,“侯世达其实就是一个现象学家,一个实践现象学家,而且比古往今来任何其他人都做得好。”侯世达研究的现象是他自己的心智——脑中的感受、脑中的内部活动。“之所以他如此擅长,”丹内特告诉我,“之所以他比任何别人都好,是因为他非常积极地寻求理论来解释后台发生了什么,来解释思考究竟是如何在大脑中发生的。”

侯世达的口袋里总是有一支四色圆珠笔和一个小笔记本,向来如此。他书房边上紧邻的一间屋子曾经是厕所,现在变成了储藏室,里面的书架装满了这样的笔记本。他取下一本——这是50年代末的。里面全是语言错误。从他十几岁起,到现在他已经收集了大概一万个这些错误——把词的位置颠倒了(下电话去接楼)、把字的音发岔了(你把发说完)、把俗语弄串了(事不关己,不谋其政),如此等等;有一半都来自于他自己。他把这些笔记本页复印下来,裁剪并装订好,装在遍布书房的文件柜和标签盒子里。

对他来说,这些都是线索。“论及脑海里那些按定义是潜意识的活动,没有人是可靠的向导,”他曾写道。“而这正是这么多错误记录如此重要的原因。单独看一个错误,其涉及的思维机制只会流露微笑的蛛丝马迹;但是大量的这些痕迹加起来,就能共同形成证实(或证伪)特定机制的证据。”正确的言谈并不好玩,它就像一次成功的魔术把戏——之所以有效是因为它隐藏了实现的具体过程。而侯世达寻找的是“兔子的耳朵尖,或者暗门的痕迹。”

从这个意义上讲,他是当代的威廉姆·詹姆斯,其条分缕析的内省(正是他引入了“意识流”的概念)和清晰明了的解释让他1890年的著作《心理学原理》成为了经典。“我们大部分的思考都永远消失了,毫无找回的希望”,詹姆斯写道,“而心理学只能收集到宴席落下的一点面包屑。”如同侯世达,詹姆斯的一生也是在饭桌下度过的,兴致勃勃地检查这些洒下的面包屑。区别是,詹姆斯只有眼睛,而侯世达却有类似于显微镜的东西。

模拟思维过程的计算机程序

载人飞行器的成功不是归功于莱特兄弟在基蒂霍克的滑翔飞行,而是他们在自己的自行车店里用回收来的破铜烂铁和自行车辐条建造的两米高的风洞。当对手们都在竞相测试机翼设计的同时,莱特兄弟却用少得多的花费专注于研究空气动力学。莱特兄弟的传记作者弗雷德·霍华德(Fred Howard)评论说,他们的风洞试验是“迄今为止,用这么少的时间、这么少的材料和预算进行的最重要且最有成果的空气动力学试验”。

在布鲁明顿北费斯大道的一间旧屋里,侯世达领导着他的灵活类比研究组(Fluid Analogies Research Group),被人亲切地称为FARG。FARG每年的研究经费是10万美元。房间内的氛围很像是居家,漫游其中很容易错过橱柜后面的文件柜、起居室里嗡鸣的复印机或者书架上图书馆员的标签(神经科学,数学,感知)。但25年来,这里一直从事着目标远大的研究:一小群科学家在尝试,“首先,致力于解开创造性的秘密,其次,揭开意识的未解之谜。 ”

计算机对于FARG就如同风洞之于莱特兄弟。人脑中飞速流逝的无意识混沌可以在计算机上放慢下来,回放、暂停乃至编辑。在侯世达看来,这是人工智能最好的工具。程序的一部分可以选择性隔离掉,以便观察没有这些会如何运转;参量可以改变,来看表现是有所改善还是退步。当计算机让人惊讶时——不管是特别有创造力还是特别愚蠢——你总能看到原因。“我一直觉得,如果人类有一天能够彻底理解思维的复杂”,侯世达写道,“唯一的希望就是通过在计算机上模拟思维过程,然后从这些模型不可避免的失败中学习。 ”

把侯世达家中捕获、归档并记录的一种思维过程,变成一个能运行的计算机程序,就这短短的一步也要花一个专心致志的研究生5-9年的时间。这些程序都有着类似的基本架构——其组件和整体风格都可以追溯到“Jumbo”,侯世达在1982年写就的一个用来猜报纸上那种乱序字谜的程序。出题人拿一个单词,把它的字母顺序打乱,问你这个单词本来是什么。

当你听说一个程序的目的是对付报纸上的字谜,你的合理反应应该是:这对计算机来说不是太小儿科了吗?的确如此——我刚写了一个程序能对付任何单词,花了我四分钟时间。我的程序是这样工作的:它拿来被搅乱的单词,穷举尝试每一种可能的组合方式,直到它找到词典里的一个词为止。

侯世达写“Jumbo”用了两年。他关心的不是解开字谜本身,而是在解决这个字谜的过程中大脑究竟做了什么。他一直都在观察他的思维。“我可以感觉倒字母在我脑海中自己在转换位置,”侯世达对我说,“就像是跳来跳去、自己形成小小群体,然后分崩离析组成新的群体——闪烁不定的簇集。操控一切的不是我,是它们在做事情,它们在自己尝试各种事情。”

侯世达开发的架构能够模拟这种自动化的字母调换,而其基础是活细胞体内的反应。不同的字母在不同类型的“酶”的作用下相互结合或者分开,这些酶四处游走,找到对应的结构就附着其上,启动反应发生。有的酶负责重组(“pang-loss”变成“pan-gloss”或“lang-poss”),有的酶负责粘连(“g”和“h”变成“gh”,“jum”和“ble”变成“jumble”),还有的则把现有的拆散(“ight”变成“it”和“gh”)。每个反应的结果都促使新的反应发生,在任意时刻“酶”的数量和种类都会自我平衡,以反映出整个字母组合的状态。

这是一种与众不同的计算方法,最明显的特征是其流动性。侯世达本人当然也给出了一个类比:一群蚂蚁在森林地面上游荡,侦查蚁向四面八方随机探索,把结果汇报给群体,其反馈过程能够带来高效的食物搜索。这样一个群体是“鲁棒”的——一脚踩上一小群蚂蚁,其它的蚂蚁会很快恢复——而因为这种鲁棒性,蚁群是高度有效的。

《流动性概念和创造性类比:思维基本机制的计算机模型》(Fluid Concepts and Creative Analogies: Computer Models of the Fundamental Mechanisms of Thought)所细致描绘的,正是这种架构和使用该架构的程序所遵循的逻辑和机制。读到这本书时,你会想或许侯世达的惊世之作不应该是集异壁,而应该是这本。正如《纽约时报》的一名主笔在1995年的一篇评论文章中说的那样:“读到《流动性概念和创造性类比》的人都不禁会想,印地安那大学的研究小组发现了一些了不得的事情。” 

但极少有人——甚至包括集异壁的崇拜者——知道这本书,或是其中描述的研究项目。或许是因为FARG的研究实在是太过明目张胆地不切实际了。因为他们研究的是微小的、简直像过家家一般的“微领域”。因为这些程序做的事情没有一项比人做得更好。 

现代的AI数据、数据、数据,而Google拥有的数据比任何人都多 

现在这个时代的主流AI大约始于1990年代,一直延续至今。不过,在如今的稳步发展和商业上的成功之前,其实有一段被称为“AI之冬”的时期,这段酷寒几乎扼杀了整个人工智能的研究努力。 

导致这一局面的是一个简单的矛盾。一方面,我们知道怎么写的那种软件是很有序的东西,大部分计算机程序都有着军队一样森严的等级秩序,拥有层层指挥链,每层都把指令传给下一层,进程调用子进程调用子子进程。另一方面,我们希望写出来的那些软件应该是有适应性的——而为了能适应不同场景,层级规则根本就是南辕北辙。“人工智能的全部努力从根本上说就是和计算机的刚性作斗争”,侯世达总结道。上世纪80年代末,主流AI研究的资助、会议出席人数、期刊论文投稿数以及媒体曝光率都在流失——因为在这场斗争中它正在输掉。 

曾经是人工智能研究金饭碗的“专家系统”也在沉没——因为它们太脆了。专家系统的研究方法从根本上是残破不全的。以人工智能领域长期以来的圣杯——语言间的机器翻译为例。标准的做法是让语言学家和译者聚在一个房间里,然后试图将其专业技巧转化为一个程序可以遵守的规则。而这种标准做法失败的原因也很容易想到:没有一套规则可以简单地诠释人类语言,语言是个范畴太大、变化太多的东西,每有一条语法规则得到遵循,就有一条被例外打破。 

如果机器翻译要作为商业项目继续下去——如果AI要得以存续——必须找到另外的道路。或者更好的办法,一条捷径。 

人们确实找到了这条捷径。你可以说这一切的开始是1988年,那一年,IBM启动了一个名叫“老实人”(Candide)的项目。“老实人”是一个机器翻译系统,其背后的思路是承认基于规则的做法需要对语言如何产生、语义句法词形如何运作、词语如何组合成句子和段落有极其深入的理解,这一要求太高了——更不要说理解这些词语作为媒介所指代的那些意义。所以IBM把那个方法丢出了窗外。开发者的代替方法绝顶聪明,但如此简单直接,令人难以置信。 

这个方法被称为“机器学习”。其目标是制作出一个设备,输入英语句子,然后吐出对等的法语来。当然,人脑就是这样的一个设备,但IBM的计划的要旨就是完全绕开人脑的复杂性,所以我们开始要创造一台简单的、几乎毫无用途的机器:比方说,一台机器,输入英语单词后会随机吐出一个任意的法语词。

想象这样一个盒子,上面有数千个旋钮。一些旋钮控制总体的设置,比如输入10个英语单词,对应的法语单词平均该有多少个;另一些则控制更加具体的规则,比如输入英语单词“jump”后,后面跟着单词“shot”的几率有多大?问题就是,光靠调整机器上的旋钮,你能让你的机器把可以理解的英语变成可以理解的法语吗?

事实证明,能。要做的不过是喂给机器一些英语句子,这些句子的法语翻译你已经知道。(譬如说,“老实人”用了220万对句子,大部分来自加拿大国会辩论的英法双语通讯。)你一次处理一对儿。输入一对之后把英语那一半喂给机器,看看法语那半边出来什么。如果出来的和你预期的有所不同——和已知正确翻译有所不同——那你的机器就不太对劲。所以调节按钮,再试一次。经过足够多的输入、尝试和调节,再输入、再尝试、再调节,你会摸索到正确的旋钮搭配,能够得到这个英语句子的正确法语对应物。

当你把以上过程对数百万个句子重复之后,你就能逐渐校准你的机器,直到你就算输入一句你不知道翻译的英语句子后,还能输出一句过得去的法语翻译。而最美妙的地方在于,你完全不用直接为它变成;你完全不用知道,为什么要这样或那样调节这些旋钮。 

机器学习并非“老实人”首创。实际上机器学习的早已经过大量的测试,那是60年代的原始机器翻译。只不过,在它之前,无一测试谈得上成功。它给学界带来的突破,并不在于“老实人”成功地解决了问题,而是让大家发现一个如此简单的程序居然能给出像样的结果。参与了老实人项目的亚当·伯杰(Adam Berger)在项目总结里说,机器翻译是“自然语言处理、甚至整个人工智能领域里公认最难解决的问题之一,因为准确的翻译若无对文本的完整理解,看起来简直是不可能的。”可是像老实人这样直截了当的程序都能有不错的发挥,证明有效的机器翻译其实不需要理解——只需要海量的双语文本。正因此,“老实人”证明了,征服AI的另一条道路原则上也是可行的。

“老实人”以惊人的效率所实现的,是把“理解一个复杂过程”这个问题转换为“寻找许许多多这一过程运作的实例”。这个新问题,和模仿大脑实际运作不同,随着时间推移只会变得越来越简单——特别是当九十年代到来,原本是物理学家专享的书呆子避风港以几何级数扩张成了万维网。

AI在90年代得到了快速发展,这绝非巧合;Google——全球最大的网络公司——成为“世界上最大的AI系统”(彼得·诺维格语),这也不是巧合。彼得·诺维格与斯图亚特·罗素合著了《人工智能:一种现代方法》,同时也是Google公司的研究总监之一。诺维格说,现代的AI是“数据、数据、数据”,而Google拥有的数据比任何人都多。 

谷歌翻译基于跟“老实人”同样的原理,现在已经是世界第一的机器翻译系统。谷歌翻译团队的一名软件工程师乔什·埃斯特尔(Josh Estelle)说,“你可以随便拿一个简单的机器学习算法,上人工智能课程的头几周就会学到的那种,学术界早已抛弃、认为没用的那种——但你用100亿条而不是1万条样例去做训练的时候就会发现,这些算法开始见效了。数据胜过一切。”

这技术极其有效,实际上谷歌翻译的团队成员都不会说其产品能够翻译的大部分语言。“光这一事实就能让人口服心服,”埃斯特尔说。“你会想雇更多的工程师,而不是更多的说母语的人。”在翻译不过是大规模数据挖掘练习的这个时代,工程才是顶用的。

这正是使机器学习方法繁荣昌盛的原因:当语法的规则不复存在,理解的过程被各种微调的工程技术所取代,数据、效率成了一切。诺维格说,Google到处都能看到这种现象:如果让这个的运行速度再快10倍,一年就能节省几千万美元,那我们就来做这个项目吧;怎么干呢?好,我们先来看看数据,用机器学习或数据统计的方法做着,之后再想出别的更好的方法。 

Google也有指向更深一级理解的项目,受大脑生物学启发的机器学习的延伸。他们现在正在做的“知识图谱”试图将词汇和人物、事件和地点联系起来。但有10亿客户等着被服务的事实迫使这家公司不得不牺牲理解以满足便利。无需多试便能看出 Google Translate 的工程师为了覆盖率、速度和便利而做出的妥协。尽管Google Translate以它自己的方式代表了人类智力的产物,但它并不是智力本身。就像一块巨大的罗塞塔石碑,Google Translate 只是人类智力工作过的记录。 

为什么要一个叫‘人工智能’的学科制造出‘人的智能’呢?” 

“当我们坐下来建造Waston的时候有没有模拟人类的认知?”IBM的Waston开发小组的负责人戴夫·费鲁奇(Dave Ferrucci)说:“绝对没有。我们只是试图制造一台可以赢得《危险边缘》的机器。”(《危险边缘》[Jeopardy]是美国的一档电视智力竞赛节目,类似央视《开心辞典》。) 

对于费鲁奇,智力的定义很简单:看一个程序可以做什么。深蓝是智能的,因为它可以在国际象棋上击败卡斯帕罗夫。Waston是智能的,因为它可以在《危险边缘》上战胜肯·詹宁斯。“我们叫它人工智能,对不对?这就相当于说不是人类的智能。为什么你会要一个叫做人工智能的学科制造出人类的智能呢?” 

费鲁奇也并非注意不到两者的差异。他总是对人们说,Waston依靠一屋子的处理器和20吨重的空调设备赢得了比赛,而它的对手使用的,用一个鞋盒子就能装走,一个金枪鱼三明治就可以运行上几个小时。这台机器在比赛结束时,还能让它的主人与人对话,吃上一个美味的面包圈,争辩、跳舞、思考,而Waston只能呆在屋子里,嗡嗡作响,从未活着,回答关于总统和土豆的问题。 

这些系统处理的问题只是影子——“其实连影子都不是”,费鲁奇说:“我们一直以来都低估了人脑当中真正发生的事情。”上世纪50年代如此、现在也仍然这样。 

那么,为什么你不来研究人脑中发生的事情呢?这是侯世达想问费鲁奇的,也是他想问所有主流AI研究人的。 

对此,费鲁奇的态度颇为复杂,他说:“人一生能做的事情很有限,当你把时间全部投入来做某件事情的时候,你不得不问自己,这样做是为了什么。而我也这样问我自己,而我得到的答案是,我对人类智力是如何工作的非常感兴趣,能够理解人类认知也很伟大,我很喜欢看这方面的书,也有兴趣了解这方面的情况——但这样做又能怎么样呢?我真正想做的是制造出能做事情的计算机系统,而我不认为认知理论是通往我目的的捷径。” 

身为 Google 研究总监的彼得·诺维格的观点几乎和费鲁奇一样。“我认为侯世达在研究的是极其之难的问题。我猜我想做更容易一些的工作。” 

从诺维奇和费鲁奇的回复中可以看出AI研究的残破之态。早期的基础研究早已荒废,现行的计划目光短浅。对名声的顾虑束缚了一些AI研究员的思想。如果说在上世纪80年代,AI研究者在做的是试图用程序模拟出一部分的真实的数据,那现在则是将其视为任务的全部。 

我们正在允许我们的成功扼杀自己。当机器可以承受更多的数据、更快地计算出结果,人类正放任自己变得越来越笨。这就像用图形计算器计算高中微积分题目一样,这样做简单又便捷,直到你真正理解微积分的那一刻为止。给Goolge Translate输入1万亿而不是100亿个双语文本,它也不见得就会达到人类译者的水平。而同样,搜索、图像识别、问答问题,或者规划、阅读、写作、设计,或任何一个你宁愿人而不是机器来做的领域。 

诺维格和其他同处商业AI的人都清楚,能从数据中得到的终有极限。诺维格说,就像一个钟形曲线,更多的数据可以让系统变得更好,现在我们还在不断提高;但终有一天,我们获得的利益将不如过去。  

侯世达以前的研究生詹姆斯·马歇尔(James Marshall)则认为情况很简单:“最终,只有难走的那条路才能带你走向你想去的地方。” 

侯世达在他35岁时开始了他的第一次认真恋爱(他形容自己生来就“共振曲线窄”)。1980年,在度过15年地狱般惨无爱情的岁月后,他遇到了卡罗尔·布莱什(Carol Brush)。用侯世达的话说,卡罗尔“刚刚好处于他共振曲线的中心”。相遇后不久两人就结了婚,生了两个孩子。但好事不常,1993年他们一起在意大利休假时,卡罗尔突然因脑瘤去世,两个孩子分别是5岁和2岁。好友说卡罗尔去世后侯世达迷失了好长一段时间。 

30年来侯世达一直没有参加过人工智能方面的会议。“我和这些人之间没有沟通,”侯世达这样形容他的AI同行。“我不想跟那些死不妥协的人说话。你知道,虽然我叫他们为同行,但他们几乎算不上是我的同行,我们跟对方搭不上话。”他说。“我不喜欢参加会议,遇见死脑筋而且想法是错误的人,或是不了解我在想什么的人。我只爱跟和我想法更一致的人说话。” 

我只是觉得人的一生很短。我工作,但不公开。我不拼命争取。” 

从差不多15岁开始,每隔十年侯世达就会重读一遍《麦田里的守望者》。2011年秋季学期,他还给本科新生开了一门讲座“为什么J.D.塞林格的《麦田里的守望者》是部伟大的小说?”侯世达对小说的主人公霍尔顿·考尔菲德有很强的认同感。有人认为霍尔顿只知道抱怨发牢骚,侯世达的解释是“他们没有意识到霍尔顿的脆弱”。你能想象侯世达在小说的开头像霍尔顿一样站在山头,独自一人形单影只,看着他的一帮同学活蹦乱跳地在底下踢足球。“我的想法已经有很多了,”侯世达说:“我不需要外界的刺激。” 

而凌驾于战斗的坏处就是,你不是战斗的一部分。侯世达以前的学生、与侯世达交往30年之久的鲍勃·弗伦奇(Bob French)说:“科学里很少有观点是黑白分明,人们一看便说‘哦,好吧,为什么我们没有想到呢?’从板块构造到演化论,所有的这些观点都必须要有人去为之争取,因为总是有分歧。如果你不参加战斗,不去趟学术界的这趟浑水,你的想法终究会退居一旁,让位给那些尽管不如你,但是有人在角斗场中为之奋战的观点。” 

侯世达从未想过要争取,而他工作的好处和坏处也在于,他从来也不需要去真正战斗。35岁那年他赢得了普利策奖,瞬时间就成了学校的宝贵资产,印地安那大学授予了他终身教职(tenure)。侯世达不需要在期刊上发表文章,也不需要提交同行评审,更不用回复评审意见。他有一个出版商,基础丛书(Basic Books)出版公司,只要是他写的东西,一律照单全收。 

斯图尔特·罗素把话说得很直白:“学术界并不是你只要坐在澡盆子里,有了想法后大家就奔走相告、兴奋得不得了的地方。很有可能在50年后,我们会说,‘当初真应该多听听侯世达是怎么说的。’但是,想想该怎么做让人们了解你的想法,是每一位科学家都应该尽到的义务。” 

侯世达爱说“生命短暂,艺术长存”(Ars longa, vita brevis)。他说,“我只是觉得人的一生很短。我工作,但不公开。我不拼命争取。” 

侯世达打了一个比方——爱因斯坦在1905年提出了光量子假说,但是,没有一个人相信他,直到1923年。侯世达说:“18年,爱因斯坦完全是只身一人相信光粒子的存在。” 

“那一定很寂寞。” 


(Ent对本文亦有贡献。)

编译自:《大西洋月刊》,The Man Who Would Teach Machines to Think
文章图片:theatlantic.com

你可能感兴趣

  1. AI,我们创造出来的异类智能
  2. 什么是迷因(Meme)
  3. 能不能让机器像人类一样思考?
  4. 人工智能?有什么好怕的!
  5. 人工智能下一步,通过图灵测试
  6. 计算机真的可以产生智慧吗?
  7. 连线:人工智能的革命
  8. 伟大的思想到哪里去了?
  9. 超级电脑“沃森”找到了第一份工作
  10. 侯世达:大数据不美,没有品位,我不喜欢
  11. AI:不是思考,更胜思考
  12. 侯世达:我宁愿当个独立思考的人,不总是站在人们注意力的最前端
21 Feb 11:46

一个对 Dijkstra 的采访视频

by Yin Wang

(也可以访问 YouTube 或者从源地址下载 MPEG1,300M)

之前在微博上推荐了一个对 Dijkstra 的采访视频,看了两遍之后觉得实在很好,所以再正式推荐一下。如果你不清楚 Dijkstra 的贡献,那么就想一想自己用的程序语言里面司通见惯的“递归函数”是哪里来的。其实当年递归函数是 Dijsktra 和另一个人不顾委员会里众人的反对和怀疑,坚持要放进 Algol 60,所以后来才进入了 Pascal,C,Java 这样的语言的。那个时候 John McCarthy 不在场,不然的话就会有三个人支持了。

现在看来,任何一个语言里面没有递归函数都是不可思议的事情,然而在1950-60年代的时候,居然很少有人知道它有什么用!所以你就发现,所谓的“主流”和“大多数人”一直都是比较愚蠢的。现在,同样的故事发生在 lambda 身上。多年以后,没有 lambda 的语言将是不可接受的。

在这里只摘录他提到的几个要点。某些观点也许不是最好的办法,但我确信其中有非常值得学习的地方。

  1. 软件的版本号 2.6, 2.7, ... 都是胡扯。本来第1版就应该是最终的产品,可是软件公司总是先弄出来一个不完整的版本,骗大家买了,以后再慢慢“升级”。每次升级都要用户再次付钱。
  2. 编程有多种流派,我喜欢把它们归类成“莫扎特 vs 贝多芬”。当莫扎特开始写乐谱时,作品就已经完成了。他的手稿一气呵成,书法也很好。贝多芬不一样,他总是在怀疑和挣扎。他的作品一般是还没有想好就开始写,然后就往上面贴纸条修改。有一次贝多芬改了9遍才把手稿完成,后来有人把这手稿一层层的撕开,发现第一版和最后一版是一摸一样的。(注:我觉得这是指他的9个交响曲)这种改来改去的做法是 Anglo-Saxon 名族的传统,它贯穿了英国式的教育。
  3. 作曲家的工作不是写乐谱,而是构思音乐。最早的时候人们编程都是用汇编语言的,就跟写乐谱差不多。后来他们发明了高级语言,就以为这些语言把编程的问题解决了。但是你仔细一瞧,发现它们只是把编程最微不足道的问题解决了,但是困难的问题仍然困难。这些高级语言与越来越大的野心加在一起,反而让程序员头脑的负担更重了。
  4. 称职的程序员都知道自己头颅的尺寸是有限的,所以他们以谦逊的态度来对待工作,像回避瘟疫一样地回避小聪明。
  5. 当我1970年在法国巴黎讲学如何编程的时候很成功,听众都非常积极。回家的路上我又在比利时布鲁塞尔的一个大软件公司进行了同样的演讲,结果非常失败。那恐怕是我一生中最失败的演讲。后来我发现了为什么:他们的管理层不喜欢无懈可击的程序,因为这公司是靠“维护软件”的合同来维持生存的。程序员对此也不感兴趣,因为最让他们兴奋的事情在于不知道自己在干什么。他们觉得如果清楚地知道自己在干什么,那就没有挑战性了,就是无聊的工作。
  6. 研究物理的人如果遇到不理解的事情,总是可以责怪上帝,世界这么复杂不是你的错。但是如果你的程序有问题,那就找不到替罪羊了。0就是0,1就是1,就是你把它搞砸了。
  7. 1969年,在阿波罗号登月之后不久,我在罗马的北约软件工程会议遇到了 Joel Aron,阿波罗计划的软件负责人。我知道每个阿波罗飞船上面的代码都会比前一个多4万行。我不知道“行”对于代码是个什么单位,但4万行肯定是很多了。我很惊讶他们能把这么多代码做对,所以我问 Joel:你们是怎么做到的?他说:做什么?我说:把那么多代码写正确。Joel 说:“正确?!其实在发射前仅仅五天,我从登月器计算轨道的代码里发现一个错误,这代码把月球的重力方向算反了。本来该吸引的,结果写成了排斥。是一个偶然的机会让我发现了这个错误。”我的脸都白了,说:这些家伙运气真好?Joel 说:“是的。”
  8. 软件测试可以确定软件里有 bug,但却不可能用来确定它们没有 bug。
  9. 程序的优雅性不是可以或缺的奢侈品,而是决定成功还是失败的一个要素。优雅并不是一个美学的问题,也不是一个时尚品味的问题,优雅能够被翻译成可行的技术。牛津字典对 elegant 的解释是:pleasingly ingenious and simple。如果你的程序真的优雅,那么它就会容易管理。第一是因为它比其它的方案都要短,第二是因为它的组件都可以被换成另外的方案而不会影响其它的部分。很奇怪的是,最优雅的程序往往也是最高效的。
  10. 当没有计算机的时候,编程不是问题。当有了比较弱的计算机时,编程成了中等程度的问题。现在我们有了巨大的计算机,编程就成了巨大的问题。
  11. 我最开头编程的日子跟现在很不一样,因为我是给一个还没有造出来的计算机写程序。造那台机器的人还没有完工,我在同样的时间给它做程序,所以没有办法测试我的代码。于是我发现自己做的东西必须要能放进自己的脑子里。
  12. 我的母亲是一个优秀的数学家。有一次我问她几何难不难,她说一点也不难,只要你用“心”来理解所有的公式。如果你需要超过5行公式,那么你就走错路了。
  13. 为什么这么少的人追求优雅?这就是现实。如果说优雅也有缺点的话,那就是你需要艰巨的工作才能得到它,需要良好的教育才能欣赏它。
21 Feb 11:21

起床气,你从哪里来?

by JIDUDU

(文/MARIA KONNIKOVA)又是一个工作日的早晨,也许你和大多数人一样,并不是睡到自然醒,而是靠闹钟铃声把你拖出梦乡。有时连自己究竟身在何方、为啥会有这种该死的闹声都不清楚,迷糊程度取决于前一天晚上几点睡、这天礼拜几、刚才睡得有多香,等等。接着,你伸出胳膊,按住或点击“小睡”按钮,想要再安静地睡个几分钟。就睡几分钟而已,你是这么想的。当然可能不止几分钟。

表面看起来你只是让自己花几分钟清醒清醒,但实际上你所做的是让起床变得更困难、更拖延。假如你还真又睡着了,那你很有可能让大脑又一次进入睡眠周期的开端,这是叫醒你的最坏时刻。对我们来说越是感到起床困难,就越是感到自己睡得不好。

猛然醒来或醒得太早带来的后果之一是“睡眠惯性”,这种现象得名于1976年,指的是刚醒来到完全清醒之间,有段迷迷糊糊的时间。醒得越猛,睡眠惯性越大。尽管我们感觉自己清醒得挺快,很轻松就从睡眠模式转换到了清醒模式,但实际上这是个非常循序渐进的过程。我们脑干中的觉醒中枢(脑干是负责基础生理功能的脑组织)几乎瞬间被激活。但我们的大脑皮层区域,尤其是前额叶皮层(与制定决策和自我控制有关的大脑部分)却需要多花一点时间才能启动。

刚醒来的几分钟里,人的记忆、反应速度、基础数学运算能力、警觉性和注意力都比较弱。就连找到电灯开关,把灯打开之类的简单任务,都会显得好复杂。于是乎,这时候做出的决定既不理智又不理想。要知道,根据神经生物学家、时间生物学家肯·赖特(Kenneth Wright)的研究,“人的认知能力在日常入睡点之前的几个小时里最好,在日常起床时间前后最差。”睡眠惯性使然,人会做一些明知道不该做的事情。要不要按下小睡按钮大概就是我们需要做出的第一个抉择。难怪在这个问题上我们并不是每次都能做出合理的选择。

还有研究发现,睡眠惯性能持续两个小时甚至更久。哈佛医学院研究睡眠的查尔斯·切斯勒(Charles Czeisler)和梅根·朱厄特(Megan Jewett)等人完成了一项研究,他们在对被试进行了连续三天的监测后发现,无论在哪,睡眠惯性都要2-4小时才完全消失。尽管参与实验者自认为在醒来后40分钟已经清醒,但他们的认知能力却要在好几个小时后才完全恢复。吃早饭、洗澡、把家里所有的灯都打开给自己一个明亮无比的早晨,这些事情都无法改变结果。不管你做什么,大脑醒过来加速运转所要花的时间都长过我们以前的估计。

有时我们能睡到自然醒,比如某个闲适周末的上午,这主要是靠两个因素:外界环境的光照度和“体内闹钟”——昼夜节律的设定。人体内的时钟与外环境时钟并不精准匹配,因而平时我们要用外部时间线索,也即“授时因子”来调整生物钟,来模拟每天发生的日夜明暗变化。

因为上课、上班等社会事务不能迟到而设定的起床时间与身体完全靠自然醒的起床时间不一样,慕尼黑大学的时间生物学教授罗内伯格(Till Roenneberg)把这个时间差叫做“社交时差”。社交时差的长度与你睡了多久没关系,而与你睡觉的时间点有关系:有没有在身体最适合睡觉的时间段睡觉?就在最近,罗内伯格根据65000多人的数据做出了新的估测,大约三分之一的人有严重社交时差,自然醒的时间与社交强制的起床时间平均有2小时以上的差距。有69%的人社交时差稍轻,也起码有1小时。

罗内伯格和心理学家惠特曼(Marc Wittmann)还发现,生物睡眠时间和社交睡眠时间的不协调会带来高额成本:酒精、烟、咖啡因的使用增加──还有,1小时社交时差,发胖可能性相应提高约33%。“在‘非自然’时间入睡和起床可能是现代社会最为普遍的高风险行为。”罗内伯格说。他还说,睡眠时程不佳给身体系统造成的巨大压力是增加夜班工人患癌症、潜在心血管疾病以及糖尿病、代谢综合征等慢性病风险的原因之一。另一项稍早之前发表的研究是对医学院学生进行的,结果发现,睡眠时间点对学生的课堂表现以及临床医师职业资格考试成绩的影响比睡眠长短以及睡眠质量的影响要更大。实际上,睡了几个小时和自己是不是晨起型对结果来说都没多大关系,有关系的是几点入睡,以及几点起床。睡得太少是不好的,但要知道,天都黑了才起床也不好,可能还更糟。

好消息是,睡眠惯性和社交时差的影响看来能够调整。赖特要求一组青年展开为期一周的露营之旅,结果发现了一个意想不到的情况:在一周时间快要结束的时候,先前他观察到的消极睡眠模式消失了。在旅行开始前的几天里,他曾注意到实验参加者会在入睡前2小时左右,也就是大概10点半的时候,体内分泌睡眠激素──褪黑激素;而在起床之后,大约早上8点,激素量下降。露营结束后,这一模式大大改变:差不多在太阳落山时褪黑激素水平就增加了,而太阳刚升起就激素水平就降低了,比起床时间平均要早50分钟。另一方面,在过着“野外时间”时,人在缺少人工光源和闹钟的情况下不仅会更容易入睡,也更容易起床:受试者的睡眠节律会在日出后为醒来着手准备,这样一来到了起床的时间,他们就比过去以其他方式醒来时的状态要清醒得多。睡眠惯性基本上没有了。

赖特得出结论,我们之所以大早上会感到昏昏沉沉,大部分是由错位的褪黑素表达水平造成的,是因为在目前的社交时差情况下,这种激素总是要到起床2个小时后而不是还睡着的时候消退。要是我们能让自己的睡眠时间与自然光的明暗模式同步,那起床就会变得非常轻松。这并不是史无前例的事。19世纪早期,美国曾划分过144个时区。各个城市都设定了自己的当地时间,好让正午恰好是太阳在最上方的时刻。曼哈顿正午时,费城还只有5点。不过,到了1883年11月18日,美国全国设立了四个标准时区,因为先前的时间对于铁路和州际商业活动非常不方便。1884年,全球划成24个时区。退回到过去那种划分超细的时区,乍看之下会引起生产效率大大退步。但是,要是人人都没有了认知能力是渣的2小时时差而开始工作,谁知道会怎样呢?

美国诗人迪奥多·罗特克(Theodore Roethke)曾在他那首著名的诗中这么说: “为了睡去,我醒了,慢慢醒,不着急。”我们醒来,陷入这样一场睡眠,一场不太警觉、更像是梦游者无意识自动行走的睡眠,而非自己所希望的小心谨慎。而慢慢醒,不靠闹钟铃声只靠日夜节律、生物节律的慢慢醒,或许是对抗睡意朦胧的大脑的最佳防守。完完全全清醒过来时,我们做出的是自己真正想要的决定,而慢慢醒的方式会为我们保证这一点。

本文编译自New Yorker Elements:SNOOZERS ARE, IN FACT, LOSERS

你可能感兴趣

  1. 达芬奇睡眠法可行吗?
  2. 失眠,简单也不简单
  3. 【世界睡眠日】睡不着,看果壳
  4. 发作性睡病:在白天游荡的睡魔
  5. 你到底要睡多久?
  6. 午睡有益,但睡久了白睡
  7. 在开始新的一天前,请喝一杯闹钟温好的水吧
  8. 鬼压身,只缘脑醒身未醒
  9. 萌系机器熊枕头拯救打鼾人士
  10. 睡觉,向左躺还是向右躺?
  11. 睡不着?打开睡眠开关吧!
  12. 我们为什么要睡觉?
16 Feb 15:52

我为什么不喜欢过节

by Yin Wang

逢年过节人们总是说“新年快乐!”“节日快乐!”可是有多少人真的因为新年和其它节日而快乐呢?从科学上讲,节日并不是什么特殊神奇的日子,它们甚至也许是你并不喜欢的某个古人的生日。如果你有烦恼,你照样会继续烦恼,甚至会感觉更糟。因为其他人都跟你说“节日快乐”,所以你好像“应该”快乐,但却快乐不起来。反之如果你本来就快乐,你并不会因为节日的到来而更加快乐,你有可能反而会少一些快乐。对于很多人来说,节日带来的更多是麻烦,是交通拥挤,旅途颠簸,是累,无聊,是虚度光阴,低级趣味,是花一些不该花的钱,而不是快乐。

可是一旦有了这些节日,有人就开始被惯坏了,就像小时候看到别人有自行车,就哭着闹着也要父母买,不买就赖在商店门口不走。所以节日就成为了如此一个具有“强制性”的人类活动,商家的促销活动也在那个时候有了绝对的威力,有时候会让你不得不服从。现在的很多节日(比如情人节,万圣节),其实几乎已经完全成为了商家促销的幌子。当你有了节日而不能按照他们“规定”的方式度过,就会觉得自己错了似的。

所以呢,很多人其实是被迫在过节,而不是真的喜欢它们。节日的作用,其实只是把你“喜欢”做的事变成你“必须”做的事。节日成为了一种负担,而人们的关系也变得肤浅。

春节

我不明白为什么中国人这么重视春节。对于我来说,春节只不过是我会被迫看一些我不喜欢的电视(春晚)的时候,它也是一年中最不方便出远门或者旅游的时候—因为“春运”。所以我觉得喜欢春节的其实不是中国人民,而是 CCTV 和铁道部,民航局吧。是他们在给我们洗脑,让我们觉得自己必须喜欢春节,必须要过春节。

我很不明白,为什么全国那么多人,非得要在那同一个时间进行家庭聚会。如果真的关系好要聚会,随便商量个时间不就行了。人多了不好约时间,然而真的有人喜欢一大家子人“团圆”吗?我感觉那只不过是姑父姨妈之类的能够聚在一起道东家长西家短的时候。如果你运气不好生在一个封建保守的家庭,也许还会被弄去相亲,让你屈服于中国那自古吃人的传统。每个人都有自己喜欢见到的朋友,可是亲戚,特别是稍远一点的亲戚,关系却不一定好(当然也有的是好朋友)。不是朋友没有关系,但为什么一定要在那一天,付出高昂的代价,坐着拥挤的交通工具,千里迢迢来相会呢?

春晚,啊,春晚,貌似就成了春节的一切。CCTV 要在那个时间占据你的头脑,进行一些煽情的宣传。网络上好看的节目多的去了,所以春晚成为了我认为最没品位的东西,传播肤浅思想的主力军。开头只有相声还比较有意思,可是后来相声都不行了。

中国人还很喜欢在这种节日的时候办同学会,因为过年总是在老家,大部分都是高中同学聚会吧。这么多年没见到的人,经过不同的教育和经历,就因为以前是同学而再次聚在一起,真的是好主意吗?真的朋友会跟你保持联系,而很久没有联系的人就像少年闰土一样,天知道会变成什么样。我怎么觉得同学会只是互相攀比,八卦别人隐私,看到旧情人已经跟别人结婚或者变成肥婆的时候呢。同学会引起的绝大部分不是友谊,而是沾沾自喜,嫉妒,失落,对自尊心的伤害,以及美好回忆的破灭。

但是人的心理真是难以琢磨,有人过年不能回家团圆就感觉很失落,感觉自己是个罪人,可一旦回到老家,恐怕大部分时候是觉得亲戚们的话题太无聊,在他们面前玩手机吧。这就是中国的传统,它逼迫你假装喜欢其实并不一定喜欢的人,做你不想做的事情,然后把这些延续到下一代。

这其实貌似也是美国的传统,美国人的心其实并不那么自由,太多的从众心理,缺乏批判性思考和自由主义精神。英国哲学家和数学家罗素(Bertrand Russell)早就指出了这个关于家人和亲戚的问题。我觉得他说得很对,我们可以喜欢,但却没有义务喜欢自己的亲戚甚至父母,否则那种喜欢就变成了一种负担,这些违心的事情就会对社会产生毒素,甚至可以吃人。可是中国和美国的传统相对欧洲还是相对保守的,所以罗素当年出了一本《婚姻与道德》,指出婚姻是历史遗留产物之后被很多美国人批判为放荡分子。然而我却看到,罗素在 80 多年前的很多看法,越来越被历史发展证明为正确,而在科技和生产力高度发达的今天,就更加具有指导意义。(推荐:《闲散颂In Praise of Idleness

圣诞节

圣诞节不是中国的节日,可是中国人貌似因为太无聊了,很喜欢引进外国的节日。到了美国之后,觉得圣诞节确实像个很大的节日。跟中国的春节类似,你非得在这一天千里迢迢飞去与家人团聚。我不知道美国人的交通系统会不会因此出现一些拥挤,不过对于一个在美国举目无亲的外国人来说,圣诞节其实只意味着你必须在那一天之前囤积一些粮食。因为在那一天所有的商店饭店全都关门了,你会找不到吃的,必须自己做饭。所以值得庆幸的是,圣诞节只有那么一天他们关门。不管你是单身还是成双成对,都会感觉这个时候非常冷清,没有人气。

说白了圣诞节是什么呢?是耶稣的生日。我不想引起宗教冲突,但耶稣是好是坏,他到底做了什么,其实没有人知道,也不关中国人什么事。犹太人认为耶稣其实不是上帝,是假冒的。穆斯林说他们的真主才是真正的上帝。所以呢,我们才有了这么多的中东问题,恐怖分子……

有谁意识到,他们过圣诞其实是在庆祝这样一个有争议人物的生日呢?

情人节

在 2.14 约会恐怕是地球上最不浪漫的事情。情人节是我觉得最愚蠢的节日了,你不愚蠢也要被逼着做愚蠢的事情。它对恋爱中的人来说意味着天价的鲜花,紧张的筹划,拥挤的餐厅…… 这就叫做爱情吗?

如果你爱一个人就应该特殊的对待她,为何要让商家,Google 和社会舆论来决定你该做什么,来告诉你什么是爱情?这个世界对爱的理解是如此肤浅,你不愿意跟风。可是在这一天,所有的商店,饭店,社交网络,全都铺天盖地的来告诉你,你应该买这个,应该去这里,应该做这个…… 女人们看了这些就被洗脑了,你不买这个,不带我去这里就是不爱我,于是成为了商家用来撬动男人钱包的杠杆。情人节,真的是对爱情的侮辱和利用。

而对于单身的人来说,它往往意味着寂寞,压力,羡慕,被歧视。我从来不在这一天看国内社交网络上的东西,因为有些单身人士老是贴一些伤感的,封建的东西,仿佛人到一定的时间就非得处于什么状态一样。这就是传统在吃人。比如说你必须在某个年龄以前结婚,还要无条件什么什么的一生一世,…… 都是他妈的胡扯。我从来不屈服于传统,不管是中国的还是美国的。人生只活一次,难道就是为了让别人告诉自己该做什么吗?

我比较幸运,从来没经历过情人节这些麻烦。我遇到过一个善良,可爱又漂亮的女人,以至于现在我身上仍然带着她传染给我的善良和可爱。那时候我们一年 364 天都像在过真正意义的,浪漫的情人节,唯独 2.14 不过,不跟这帮傻逼抢座位。天下貌似没有不散的宴席,很多年以后我又单身了。但每当在 2.14 看到很多人的忙碌相,其实一点也不觉得羡慕。看到有人在路上拿着鲜花就觉得挺白痴的,甚至差点笑出声来。因为那些人在做别人让他们做的事情,是做给别人看的,而不是自己想做的事情。我希望在不是 2.14 的时候也能在街上看到有人拿着鲜花。

能够不用被一些愚蠢的雌性动物逼着在别人指定的日子去排队买花吃晚饭,自己在家里点着蜡烛,喝着热茶,自由自在创造我热爱的东西,是多么浪漫的事情。我非常非常非常喜欢单身!

万圣节

万圣节跟情人节差不多肤浅,甚至更加肤浅一些。我怀疑是一些农民想处理卖不出去的南瓜,所以制造了这个节日。有人说,万圣节是一年里唯一一天你可以穿着情趣内衣走在大街上而不被认为是鸡,还真是的。我只参加过一次万圣节的聚会,觉得非常肤浅和无聊。做过一次南瓜灯,然后就没有第二次。

因为这些原因,我已经很久没有庆祝过节日了。我现在每天都差不多,基本是快乐而惬意的,比过节那几天还要开心一些,我的心仿佛不会老。我以为我是一个特殊的人,直到今天我才发现自己其实不是世界上唯一不过节的人。有个以前 Indiana 的瑞典同学居然其实也不过节。他是我印象中最独立,最自由,最浪漫的一个人。这是他对情人节的看法:

嗯,英雄所见略同 :-)

11 Feb 13:25

程序员的心理疾病

by Yin Wang

由于程序员工作的性质,他们长期以来受到的所谓“黑客”式的“熏陶”,形成了一种行业性的心理疾病。这里我就简单的把我所观察到的一些症状总结一下。

无自知之明

由于程序员的工作最近几年比较容易找,工资还不错,所以很多程序员往往只看到自己的肚脐眼,看不到自己在整个社会里的位置其实并不是那么的关键和重要。很多程序员除了自己会的那点东西,几乎对其它领域和事情完全不感兴趣,看不起其他人。这就是为什么我的前同事 TJ 作为一个资深的天体物理学家,在一个软件公司里面那么卑微。貌似会写点 node.js,iOS 软件的人都可以对他趾高气昂的样子,而其实这些东西的价值哪里可能跟 TJ 知道的物理知识相提并论。很多科学家其实都可以轻而易举的掌握程序员知道的那点东西,有人却认定了他们不是这个专业的,不懂我们的东西,或者故意把问题搞复杂,让他们弄不明白。让人感觉是在阴沟里翻了船被老鼠欺负。

其实对于一个物理学家,他心目中知识的价值是这样排序的:

COBOL 在那么靠前的位置我觉得是用来搞笑的,不过你大致看到了很多 IT 技术的价值和它们的有效期。

如果力学工程师犯了错误,飞机会坠毁;如果结构工程师犯了错误,大桥会垮塌;可是如果软件工程师犯了错误,大不了网站挂掉一小时,重启一下貌似又好了。所以所谓“软件工程师”,由于门槛太低,他们的工作严谨程度,其实是根本没法和力学工程,结构工程等真正的工程师相提并论的。实际上“软件工程”这个名词根本就是扯淡的,软件工程师也根本不能被叫做“工程师”。跟其他的工程不一样,软件工程并不是建立在科学的基础上的,计算机科学也根本不是科学。

垃圾当宝贝

按照 Dijkstra 的说法,“软件工程”是穷途末路的领域,因为它的目标是:如果我不会写程序的话,怎么样才能写出程序?

为了达到这个愚蠢的目的,很多人开始兜售各种像减肥药一样的东西。面向对象方法,软件“重用”,设计模式,关系式数据库,NoSQL,大数据…… 没完没了。只要是有钱人发布的东西,神马垃圾都能被吹捧上天。Facebook 给 PHP 做了个编译器,可以编译成 C++,还做了个 VM,多么了不起啊!其实那种东西就是我们在 Indiana 第一堂课就写过的,只不过我们是把比 PHP 好很多的语言翻译成 C。我们根本不想给 PHP 那么垃圾的语言做什么编译器,让垃圾继续存活下去并不能证明我们的价值。

其实软件里面有少数永恒的珍宝,可惜很少有人理解和尊重它们的价值。这在其它的工程领域看来是不可思议的,然而这却是事实。由于没有科学作为理论的基础,没有实验作为检验它们的标准,软件行业的很多东西就像现代艺术一样,丑陋无比的垃圾还能摆在外表堂皇的“现代艺术博物馆”里面,被人当成传世大作一样膜拜。

为了凸显自己根本不存在的价值,又提出一些新的“理念”,就像有些现代艺术家一样,说“艺术的目的不是为了美,而是为了自由。”哦,这就是为什么你们可以自由地把那些让人反胃的东西放在博物馆里,还要买门票才能参观?

宗教斗争

当然了因为没有实质的技术,为了争夺市场和利益,各种软件的理念就开始互相倾轧。一会儿说软件危机啦,面向对象方法来拯救你们!一会儿又提出设计模式。过了一会儿又有人说这些设计模式里面有些模式是“反模式”,然后又有人把函数式编程包装起来,说是面向对象编程的克星,一会儿是关系式数据库,一会儿是 NoSQL,一会儿是 web,一会儿是 cloud,一会儿又是 mobile…… 每个东西都喜欢把自己说成是未来的希望。

这就是为什么有人说在软件行业里需要不停地“学习”,因为不断地有人为了制造新的理念而制造新的理念。在这样一个行业里,你会很难找到一个只把程序语言或者技术当成是工具的人。如果有人问你对某个语言或者技术的评价,是非常尴尬甚至危险的事情,所以最可靠的办法就是不做评论,什么都不要说。

引难为豪

在 IT 行业里批评一个技术难用,是一件非常容易伤自尊的事情,因为立马会有人噼里啪啦打出一些稀奇古怪的命令或者一大篇代码,说:就是这么简单!然后你就发现,这些人完全不明白什么叫做设计,他们以自己能用最快的速度绕过各种前人的设计失误为豪,很多程序员甚至以自己打字快为豪。

当遇到这样的人,我的经验是,千万不要恭维他们。你必须大声地嘲笑这些东西的设计,并且指出它们的失误之处,否则你不但助长了这些人的气焰,而且将来自己的自尊也难保了。很可惜的是不是每个人都有这种勇气把这些话说出来,这就造成了今天的局面,纷繁复杂的垃圾充斥着世界。爱因斯坦说,你需要很多的天才和非常大的勇气,才能追求到简单。

非常大的勇气…… 也许就是这个意思。

去读文档!

不知从什么时候开始,人们开始引用 Eric Raymond 的一篇叫做《提问的艺术》的文章,这篇文章后来就成为了对提问者没礼貌的借口。由于这篇文章的误导,当你希望同事能给你一个手把手的演示的时候,他们往往会丢给你一篇不知道什么时候写的文档,让你自己去读,仿佛文档就可以代替人之间的直接互动。况且不说这文档可能已经过时,里面有很多地方已经不符合最新的设计,而这意味着在潜意识里,他们觉得高你一等。

对于这种现象有一个专门的词汇,叫做 RTFM(Read The Fucking Manual):

有的人稍微委婉一点,当你提问的时候,他们会二话不说打开一个浏览器窗口,在里面用 Google 搜索,然后指给你:看,就是这样。貌似比较礼貌,但那其实意味着他们在教训你:Google 一下就找到了的,自己不动脑筋!有谁不会用 Google 呢?提问的人恐怕是想得到 Google 不能给他的答案。真正有礼貌的人在不知道答案的时候是不会当面去帮你搜索的,他会承认自己不知道:“这个我也不知道…… 要不你搜索一下?”

在 IRC 的聊天室里,由于隔着网络的屏障,这种对提问者没礼貌的现象就更加嚣张。我曾经有几次去 Java 的聊天室问一些貌似基础,而其实很深入的语言设计问题,结果没有一次不是以收到像“去读 API!”这样的回答而结束。API 谁不会读,然而我需要的是一个有血有肉的人对此的理解。所以后来我根本不去 IRC 这种地方了,因为那里面对你打字的基本上已经不是人类了。他们觉得你问问题浪费了他们的时间,好像他们一天到晚泡在 IRC 里面就是在做什么正事似的。不想回答问题,不开口还不行吗。后来你发现,原来在 IRC 里面训斥新手就是这些人唯一的乐趣,所以其实他们是非开口说话不可的。然而这次他们遇到的却不是个新手,而是一个可以把 Java 整个造出来的人。

像 Haskell 之类的聊天室貌似稍微友好一点,然而后来你发现他们显得友好是有所企图的。因为当时 Haskell 还没有很多人用,他们需要吸引新手,所以竭尽所能的诱导他们。而一旦它用户稍微多了一点,有声势了,那些积极分子就成了专家一样的人物。他们就开始写书,然后就开始牛气哄哄的了。然后你就会发现当对 Haskell 的设计提出异议的时候,这些“id”们是多么的不友好,有理也说不清。所以最后你发现,其实所有语言的所谓“社区”都一个德行。如果 Haskell 有一天像 Java 一样如日中天(当然不大可能),肯定对大部分问题的答案也就是“去读API!”其实它已经在向这一步发展了。

不得不指出,《提问的艺术》等介绍“黑客文化”的文章对于这种现象的出现有着极大的责任。说穿了,写这些文章的人一般都是 Unix 的跟屁虫。这种文章试图抹去人类文明几千年来传承的文化,而重新给“礼貌”做出定义。其结果是,人类的文明因为这些文章,在程序员的世界里倒退了几十甚至几百年。很多外行人人不喜欢跟程序员说话,叫他们是 nerd,就是这个原因。

不要提问,不要谦虚,不要恭维

跟上面的症状相似,程序员世界里的一条重要的潜规则是:只有菜鸟才会问问题。所以如果你有任何机会可以自己得到答案,就不要试图向人“请教”,尤其不要显得好奇,否则你就会被认为是菜鸟。我有几次不耻下问的经历,最后导致了我被人当成菜鸟。我只是觉得那问题有趣,也许能够启发我设计自己的东西,所以吃饭时觉得是个话题可以说一下,结果呢就有人忙着鄙视你,那么小的问题都没搞清楚。正确的态度应该是诚实,直接,见惯不惊,那有什么大不了的,我什么没见过,我很怀疑。

随之而来的引论就是:不要谦虚!那些“职场经验”之类的文章告诉你的进入新的公司工作,要谦虚好问,对 IT 公司这种不讲美德的地方是不管用的。有的大 IT 公司有所谓的“文化”,比如叫你要“Googley”,要“humble”,其实只是用来贬低你价值的借口。他们要你向他们“学习”,但其实他们没有什么值得学习的地方。他们只是想让你安于“本分”,做一些微不足道,不能发挥你才能的工作。看看那些叫你要 humble 的人,他们 humble 吗?所以跟江湖一样,在 IT 公司里面一件很重要的事情是,亮出自己的宝剑和绝招,给人下马威。介绍自己的东西一定要自豪,这就是世界上最好的,无敌的,没有其他人能做到!不能有任何保留。不要像科学家一样介绍自己技术的局限性,否则随之而来的就是有些人对你价值的怀疑和对你自信心的打击。

另外要注意的是对于别人介绍的东西,不要轻易地表扬或者点头,否则有人就更有气势了。你要问这样的问题:这里面有什么新的东西吗?这个事情,另外一种技术早就能做了啊,没觉得有什么了不起。

哎,总之这样还是很累,所以最好是能不跟程序员讲话就不讲。

以语言取人

你的软件是什么语言写的,告诉别人的时候是千万要小心的,不到万不得已最好不要说。因为十有八九,对方会立即在心里对你的软件的价值做出判断,光凭你用的是什么语言。

很多程序员都以自己会用最近流行的一些新语言为豪,以为有了它们自己就成了更好的程序员。他们看不到,用新的语言并不能让他们成为更好的程序员。其实最厉害的程序员无论用什么语言都能写出很好的代码。在他们的头脑里其实只有一种很简单的语言,他们首先用这种语言把问题建模出来,然后根据实际需要“翻译”成最后的代码。这种在头脑里的建模过程的价值,是很难用他最后用语言的优劣来衡量的。

有时候高明的程序员用一个语言并不是因为他只会用那种语言,而是其他的原因。他们的头脑里有着万变不离其宗的理念,可以让他们立即掌握几乎任何语言或者工具,所以他们对所谓的“新语言”都不以为然。可是很多人误以为他们不愿意学习“新东西”,从而从心里鄙视他们。其实计算机的世界里哪里有很多新的东西,只不过是有人给同样的东西起了很多不同的名字而已。如果连这样的程序员都不能理解你的技术,就说明你的技术设计有问题,而不是他们有问题。就像 Seymour Cray 说的,我只能理解简单的东西,如果它太复杂了,我是不能理解的。

早些年的时候,大家都认为招募某种特定语言的程序员是一种浮浅的做法,很多公司看重的都是解决问题的能力。可是近些年我发现这些浮浅的做法越来越普遍。可以说现在像 Google 这样的公司面试员工的方式和态度,其实还不如八年前我的第一份国内工作。而这种现象在使用 Python,Ruby 等“流行语言”的公司里就更为普遍。

之前有很多使用 Python 的公司因为我 LinkedIn 上的“Python 技能”很多人 endorse,所以来联系我,我一直没搭理他们。离开 Coverity 之后我觉得不妨去看看,说不定是好地方呢。可惜的是面试的时候,他们问的都是一些极其肤浅甚至愚蠢的问题,比如 "Is everything an object in Python?" 经过面试,他们最后都认定了我是 Python 的菜鸟。然而如果你知道 PySonar 的技术含量就会明白,这样的东西需要水平高过 Python 的创造者 Guido van Rossum 很多的人才能造出来。在制造了 PySonar 之后,他对程序语言的理解,他的每一个错误都被我看得清清楚楚。当然,Ruby 就更烂了,我可以说,Matz 这人其实根本不知道他在干什么。由于这些经历,我把自己的 LinkedIn 上面的技能全都删掉了,以免再有用 Python 的公司来找我。

说到这些的时候,我惊讶的发现有人来信告诉我,还是等你做出了什么“成就”再来说这些话吧。从这里我看到了“竞争”和“攀比”的思想在有些人心里是多么的根深蒂固,我也看出了这些“大牛”在他们心里是个什么地位。然而我根本没有跟这些人“比”的意思。说实话吧,就算你打死我,我也做不出有那么多毛病的语言来。我两个小时之内设计个语言都比 Python, Ruby, JavaScript 要好很多。我不可能以“超过 Python 和 Ruby”这么肤浅的目的为动机,来达到别人所认同的“成就”。打个比方,我有什么必要证明我比 Justin Bieber 或者 Lady Gaga 强呢?我根本不明白这些人到底有什么成就,也完全没有必要向他们的粉丝证明我的价值。

跟屁虫

有些程序员对新手和同事是那么的不友好,然而对大牛们拍马屁的功夫可真是出类拔萃。我刚到旧金山的几个月有时候参加一些程序语言的“meetup”,后来我发现这种 meetup 都是宗教气氛非常浓厚的地方,跟传销大会差不多。Scala 的 meetup 里面的人几乎全都对 Scala 和 Martin Odersky 顶礼膜拜,甚至把 Rod Johnson 请来说一堆胡话。Clojure 的,当然基本上把 Rich Hickey 当成神,甚至称他为“二十一世纪最重要的思想家之一”。各种 talk 总是宣扬,哇,我们用 Scala/Clojure 做出了多么了不起的东西云云,其实只不过是在向你兜售减肥药。

很多人喜欢做这些新的语言和技术的“evangelist”,尽显各种马屁神功,然后就开始写书,写 blog,…… 目的就是成为这个“领域”的第一批专家。这就难怪了,再垃圾的语言也有一大批人来鼓吹。因为这些没真本事的人,随便把一个东西捧上天都有自己的好处。

由于受到这些“先知”的影响,有些人开始在他们自己的公司里“布道”。比如有人在 Python 的 meetup 集会时告诉我,他试图在自己的小组里推 Python,可是一些老顽固一定要用 Java,认为 Java 才是王道。很鄙夷不高兴的样子。我并不认为 Java 是很好的语言,然而 Python 也好不到哪去。它们在我眼里只不过是临时拿来用一下的工具,可是我仍然能用它们写出一流的代码。

看到这些宗教性质的聚会,我终于理解了一些地区是如何被从一个国家分裂出去,最后沦落为另外一个国家殖民地的。最早的时候,一般是派传教士过去“传经”,然后就煽动一小部分人起来造反。到后来就可以名正言顺的以“保护传教士”,“保护宗教自由”,“维持和平”等理由把军舰开到别人家门口……

02 Feb 08:05

Seven Things for February 1, 2014

by Eric

Here we go:

26 Jan 14:51

程序语言与……

by Yin Wang

程序语言的设计类似于其它很多东西的设计。有些微妙的地方只有用过更好的设计的人才能明白。现在我就简要介绍一下我自己的体会。

程序语言与微波炉

有的程序语言就像左边的,现在中国市场上流行的微波炉。布满了花哨的一年都用不到一次的专用菜单,却连最基本的 0-9 数字键都没有。输入个时间都要费脑筋组合一下,按键位置不顺手,不能一次按到位,而且还不能达到需要的精度。

有的程序语言就像右边的,美国市场上常见的微波炉,几十年不变的设计。虽然按键很少,但十个数字键总是少不了,而且采用标准的“电话键盘”排列。十个数字能够组合产生出任意的时间,所以不管是在自己家里,别人家里,公司或者学校,你总是可以按照自己的经验,食物包装或者菜谱上的说明,迅速而精确的输入想要的时间。

可惜的是,在中国你已经买不到这么简单实惠的微波炉了。我们中国人学会了美国的很多糟粕,却没有把这么简单,这么好的设计思想学过去。

中国的微波炉厂商之所以放上这么多的花样,是因为商家抓住了中国人的贪便宜心理。看,一个微波炉可以煮米饭,烤肉串,还可以蒸排骨,那其他的厨具都可以不用买啦!可惜因为所以,科学道理,微波就是微波。加热牛奶剩饭之类的事它做得很好,可是要做美味佳肴它就不行了。煮米饭不如电饭煲,烤肉串不如烧烤架,蒸排骨不如蒸锅,炖东西不如砂锅…… 美国人和稍微有点经验的中国人早就知道这个道理,所以从来不期望微波炉能做超越它所擅长的事情。

虽然美国人在这些硬件上非常精明,可是在软件上还没发展到那种地步,很多时候对一些不可救药的软件技术寄予太多的希望。左边的微波炉就好像某些程序语言,本来当初设计就是给标准没那么高的人用来处理很简单的网页的。可是后来有人忽然想让它成为一个“万能语言”,用来做复杂的,对性能和可靠性都很高的服务器程序甚至机器人控制程序。然后你就发现类似微波炉的问题,因为一些不可逾越的设计差别决定了它是不可能把那些事情做好的,而且对有些应用还有严重的安全隐患。当然你可以缓慢的“改进”这语言,让它慢慢的提高做这些事的水平。可是这种改进的终点也许只是另一种早已存在的语言。而且由于不想破坏已有的代码和特性,所以每一步的改进都异常艰难。这种方式远远不如直接针对需要选择不同的语言,或者设计新的语言来的迅速和有效。

程序语言与减肥

很多人都想减肥,就像很多人都想学会编程。姑且不说一味的减肥好不好,现在只谈一下什么是有效的减肥方法。

我自己也有一段时间很胖,也有减肥的经历,而且非常成功。如果有一天我不小心又变胖了,我有非常科学而可靠的办法减回去。我的方法就是一句话:让每天吃进去的热量比消耗的少一些,但是不至于难受,另外适当运动来增加热量的消耗。很显然嘛,根据热力学定律,每天消耗的能量比摄入的多,多出来的部分只能通过分解你身上的物质(脂肪)来产生。我的减肥方法就像某些程序语言教会我的编程理念,是不随潮流而改变的真理。它让我的程序不管用什么语言写都优美而精悍。

我不是自私的人,我希望大家都健康一点,养眼一点。我已经轻易地告诉了你减肥的终极真理,一分钱都不收,可是你不相信我。你觉得肯定没那么简单,或者你觉得那样太辛苦,自己不可能照办。这就像很多人对编程的希望:要是我不学编程也能编程该多好啊!

很多程序语言就是针对这群人而产生的,它们大部分的工作花在了研究人的心理和做广告上面。它们就像电视广告里铺天盖地的减肥药:不需运动,不用节食,一个星期瘦 20 斤!它们提出各种新的术语,什么减肥茶,片,胶囊,螺旋,燃脂,纤维,宫廷,祖传,秘方,各种生化术语…… 再加上一些 PS 出来的前后效果对比图,你痛快地花不菲的价钱买了这药,然后每天好几次的像做化学实验一样精确的按时按量服用。这时候任何人跟你说这药不灵的话你都不会相信,你觉得这些人都是想跟你争夺异性的目光故意想让你继续胖下去而其实她(他)们自己背地里也吃这药,所以你对此减肥药必胜的信心有增无减。

当然你不会成功。在持续服用好多个月,甚至好几年之后,你按照广告里说的“无效退款”条例要求退款。可是减肥药公司说,是你自己没有按说明书服用,或者你吃药之前肯定比现在还胖很多。你拿不出证据,后悔当初没到公证处开你当时体重的证明。可是你仍然相信,世界上一定会有真正有效的减肥药。你觉得国内的公司喜欢骗人,所以你到了美国,寻找传说中那世界一流的减肥药……

程序语言与棋

有人说好的程序语言就像国际象棋(chess),在了解简单的规则之后,你就可以用它们组合出变幻无穷的棋局。而我认为,好的程序语言应该像国际象棋去掉像“王车易位”(castling)一类复杂古怪的规则。实际上,好的程序语言会更加近似于中国象棋,而不是国际象棋。中国象棋只有一条规则比较特殊—“蹩脚马”,可是它其实很直观,容易理解。其它的规则,比如兵卒过河才能横行,几乎都画在棋盘上了。

可不要小看国际象棋里这少数几个特殊规则,它们需要在好几个非常特殊的条件满足之后才会生效,而且路线诡异。比如,王车易位必须满足:

  1. 王和跟他换位的车都没有移动过
  2. 王和车之间没有其它棋子
  3. 王不能处于被“将军”的状态而且王在换位之后不能处于被攻击的位置但是车可以在换位后处于被攻击位置
  4. 王和车处于同一条水平线上

另外换位的时候王和车不是直接互换位置那么简单,而是这样的路线:

一条这样的特殊规则就够伤脑筋了,据我所知国际象棋还有至少其它两条类似的规则。它们跟其他的规则组合在一起的时候就产生了组合爆炸效应,你发现每走一步,甚至貌似无关的动作都得检查它们是否会出现。你不得不随时把这么复杂的规则放在脑子里。没事找事也不要找这么麻烦的事啊。

这些规则就像是要你记住 C 语言里的 ++i+i++ 或者 if (x = "foo") {...} 是什么意思。经过多年的痛苦经历之后,你多希望不再需要理解这样的代码。可是一旦这样的规则被加到语言里面,总会有人为了显示自己的水平和记忆力去用它们。不得已,你只好陪他们玩。

如果你觉得多了这些无厘头的规则会让国际象棋比中国象棋难度大或者更加有趣,那你就低估了中国象棋了。中国象棋的“游戏树复杂度”其实比国际象棋还要高,高达 10150,而国际象棋只有 10123。这跟中国象棋的棋盘要稍微大点有关系,但是总比记忆那些麻烦的规则好多了。所以相对来说中国象棋既简单又耐玩。

如果国际象棋还凑合算是简单的话,大部分的程序语言就像是魔鬼棋,飞行棋,或者三国杀。它们几乎完全由类似的特殊规则构成。哇,那么多的人物,道具和特殊技,好玩!可是会玩象棋或者国际象棋的人都会觉得它们无聊透顶。

那么是不是规则越简单越少的棋越好呢?围棋就比中国象棋还简单,那么围棋是不是更好玩呢?我觉得不是的。围棋对我来说太慢,太单调,棋盘太大,耗时太多,而且胜负居然不能一眼就看出来,要数好一会儿!这哪里是在玩,纯粹就是在做组合优化题嘛。我觉得这种任务适合交给电脑去做。所以其实简单也有一个界限,超过了这个界限对于人就没有很大区别了,反而会开始感觉缺少一些东西。

我觉得中国象棋和围棋一样简单,它的规则虽然比围棋多,但是仍然处于人脑容易记忆的范围,而且每条规则都很直接了当,没有很隐晦的条件。中国象棋的长距离武器(车和炮)让它比围棋多了很多乐趣,而对于象,马和王的走法的限制,让它比起国际象棋多了几分安心和舒适。国际象棋的后,两个车,两个相的攻击距离和范围太大,让人觉得眼睛很辛苦,因为每一个位置都可能被从太多个方向远距离攻击。而那个王,由于可以到处乱跑,以至于你感觉不是在抓一个住在戒备森严的城堡里的人,而是一只在野外乱跑的老鼠。

什么游戏会让人觉得有趣,真是一个值得研究的问题。我觉得象棋和我以前推荐过的一个游戏 Braid 里面含有同一种吸引人的设计:屈指可数但又有足够变化的简单规则,组合起来制造出许许多多的变化。这种特征其实也是鉴别一个优秀的程序语言的标准。

程序语言与音乐

程序语言就像音乐。当听过很好的音乐之后,你会自然而然的厌倦以前曾经喜欢过,为之疯狂过的那些,觉得它们很无趣,甚至很惊讶自己以前怎么会喜欢它们。当有人问你为什么不喜欢他们推荐给你的音乐,你却说不出来。你只是自然而然觉得太单调,不入耳,不对劲,甚至扰乱你美好的心情。你的判断完全是依靠声波对鼓膜的震动而引起的脑电波的起伏,而不带有任何的成见。完全根据这音乐自己,而不需要知道它的作者是谁。就像玩过像《Braid》之类的游戏之后,你再也不想玩像《生化危机》那种搞不清楚到底是自己在玩游戏还是游戏在玩自己的。你的脑子里有一种对“趣味”的新定义,但是你却说不出来它到底是怎么回事。

每当有人问我喜欢什么样的音乐我都不好说出口,因为我最喜欢的音乐家是巴赫(J. S. Bach)。他的音乐不知道为什么,一天听一百遍也不会腻。只有当巴赫的音乐一天放了实在太多遍之后,我才会开始放肖邦的,不过肖邦的一般比较快就腻了。听饱听撑着了,我才开始考虑用 Dream Theater 和少数几首 hiphop 来调一下味。很奇怪的是,我根本不喜欢很多人推崇的贝多芬之类的交响乐,不管是多好的世界级的乐队,不管是现场演奏还是听录音,每次听都打瞌睡,觉得罗里吧嗦滥竽充数的。

巴赫音乐的演奏者里面我最喜欢 Glenn Gould,他演奏的巴赫曲目里面我最喜欢 Goldberg Variations,而他的两次 Goldberg 录音(1955 和 1981 年)里面我只喜欢 1955 年的,虽然当时录音技术落后一些,有一些杂音和 Gould 的哼哼声。我怎么能够区别出自己喜欢哪一个?因为最初的时候它们每个都被我听了几百遍,然后我的心就自然而然做出了选择。

哇,这样说出来总是有附庸风雅之嫌,所以我每次都说“嗯,什么乱七八糟的从古典,rock 到 hiphop 都听点……”倒也差不多是事实,但是我喜欢巴赫显然超过其他人一千倍以上。我每天晚上听着巴赫的音乐睡觉,白天用耳机听着他的音乐上班,没有人知道我在听什么。我每个月付给 Spotify $9.99 享受可以听尽世界上所有音乐的服务,可惜的是过了一两年我仍然每天反复地听那一两张巴赫的唱片,还不如直接把它们买下来。

其实我基本不识谱而且五音不全,也不会乐器,但是听了一阵子巴赫的音乐之后,我再也不想听流行音乐甚至很多其他古典音乐家的作品了。巴赫在我心里丝毫没有名气或者“高雅”的成分,如果他是在街头卖 CD 的音乐演奏者,我一样每天晚上听着他的音乐睡觉。我喜欢他的音乐的原因很简单:有趣听不腻。我反倒觉得大部分人喜欢流行音乐是因为名气和喜欢显示自己,那才是真正意义上的附庸风雅。这里的科学道理就是,绝大部分流行音乐是 4/4 拍,容易唱,但是太单调了,跟一二一齐步走似的,很难做出什么好音乐。然而就是因为这些附庸流行音乐的“风雅”的人,巴赫如果活在今天说不定也就是在街头演奏卖 CD 的命。

程序语言是同样的感觉,这是一个“流行语言”招摇过市的年代。每当有人问我喜欢什么程序语言我都不好跟他说,因为一旦说出来就有显摆之嫌,而其实真正显摆的是其他人。很多人期望你的回答是他所膜拜的那个最近很热门的语言,你一旦告诉他你喜欢的语言就会被冷嘲热讽,因为你的语言不热门。他们会说你是“学院派”,而他们是“工程派”,而其实这只是给垃圾的存在找借口。他们利用你害怕自己被认为是附庸风雅或者居高临下的心理来变相地压制你,让你不敢直率的袒露自己的兴趣。你不敢显示对有些东西的不屑,而他们却可以任意的显示对真正优秀的技术的不屑。你觉得应该手下留情一些,谦虚一些,结果最后一些垃圾一样的语言就骑到你头上来,让你不得不用它们。

用过很好的语言,然后自己设计过程序语言之后,我再也不对很多新的语言,或者有些人很崇拜的古老的语言感兴趣了。我完全是凭自己的感觉来判断,一些所谓的“新特性”其实是老酒换新瓶,或者是勾兑的假酒。程序语言本来就只有那么点东西,为什么有人仍然像对那些扮相的流行歌手一样热衷和疯狂。

我知道这些话说了也白说,因为他们没有用过我用过的语言,他们只看到名字却感觉不到本质,他们靠别人的评价来判断,而不是靠自己的心。所以像音乐一样,只有等有一天他们忽然觉悟,就像很多年前的我一样。

程序语言与武器

前段时间 AK-47 的设计者 Kalashnikov 去世的时候,我从一篇文章了解到他设计 AK-47 的故事,发现 AK 跟我喜欢的程序语言设计有异曲同工之妙。

AK 简单得就像一把锤子。它身上没有太空时代的材料。大多数汽车修理店都有可以制造出 AK 的工具。

这篇文章首先提到,AK 的高可靠性最主要来自于它的简单,而其实简单也是程序语言最重要的东西。程序员需要解决的问题一般都挺复杂,如果他们的工具再被设计得复杂,那么他们大量的脑力就被浪费在解决这语言的问题,而不是真正需要解决的问题了。

Kalashnikov 开始的时候把任何有可能出问题的设计都排除在外了。

与简单的设计背道而驰,现在很多程序语言为了赶潮流或者吸引眼球,喜欢标新立异,喜欢加入很多“特性”,可是这些特性很有可能不但不解决问题,而且会制造问题。绝大部分程序员都不理解这个道理,所以有些人听说我在设计自己的语言就问我:“它有什么新特性吗?”我没法回答他们,因为我的设计几乎没有新的特性。我现在所做的一切思考和试验都是在去掉不必要的麻烦。一个语言缺少一些好的特性,以后还可以加进去,可是它如果多了一些问题特性,那一旦有人开始用就没法去掉了。

AK 上面没有袖珍和娇气的部件。这样你就不用费事在草丛里,泥地上或者溪流里找它们了。

士兵是人,会摔跤犯错误,程序员也是人,所以程序员的武器应该像士兵的武器一样,方便他们找到问题。可是很多程序语言让程序员犯错误之后花很多时间和精力才能找到错误的所在,浪费大量本来可以用来解决问题的时间。我的前同事 TJ 说他刚进入博士学习的时候花了好几个月,就为了找到 C 代码里面一个指针计算错误,导致内存结构破坏和莫名其妙的错误结果,而出现指针计算错误的位置跟错误结果出现的位置毫无关系。我也遇到过类似的问题。C 语言的指针不就像是某些武器上面的袖珍部件吗?一不小心掉在地上就找不到了。

AK 只有一个复杂一点的部件,那就是它的弹夹。弹夹的设计很大程度上影响到枪的整体性能,所以 Kalashnikov 在上面花了很多设计时间。

这个工程经验其实对于程序语言的设计者有启发意义,因为弹夹与枪主体的接口,和程序语言的函数接口很类似。Tony Hoare 在他的《给程序语言设计的建议》中也提到,函数的调用必须简单而且快速,因为调用的开销会累积起来形成很大的性能问题。可惜的是很多语言没有注意到这个问题,函数调用时总是有一堆的动态检查和重载要做,很大程度的影响了它们的性能。

AK 的美,在于它身上没有部件具有不必要的精确性。

这对于程序语言或者编程来说也是有启发意义的。有些人为了所谓的程序“正确性”,损害了它的简单性。他们的代码异常复杂,而且喜欢写很多测试,让自己感觉对程序的“质量”有个底。然而这其实是自欺欺人。这些测试不但不能保证程序的正确,它们阻碍了程序员对程序进行彻底性的修改,防止了他们看到更加简单,甚至一眼就知道是正确的解决方案。

程序语言的设计也是。有些语言(特别是所谓 dependent type 的语言)想达到程序的完全正确,加入了很多很多的限制条件,要求程序员写很多的辅助声明甚至机器证明。结果很简单一个问题都需要很长的代码才能写出来,这些辅助的逻辑代码严重的影响了程序的阅读和转换。而且由于数理逻辑本身的局限性,它们经常迫使程序员的思路绕弯子。其实起到了相反的结果,让他们看不到更简单的方法。

Kalashnikov 不是天才,他不是为了发明而发明,他解决不了问题的时候就高兴地从别人那里学过来。

这是非常值得我们程序语言设计者学习的。很多程序语言专家都有盲目排斥“对手”的心理,“自己人”的东西就不假思索的表扬,对手的东西就一味的批评。最后的结果是没有把敌人的好东西学过来,让自己人吃亏。在操作系统和数据库等领域也有类似的思维方式,这是非常有害的。

直到被更好的东西取代,AK 会继续和我们在一起。什么才是“更好”,这是由历史和民族来定义的,而不是枪支设计专家。

在计算机的世界里也是一样,程序语言,操作系统,数据库…… 它们的好坏不应该是由它们的设计者决定的,而是看它们是否经得起时间的考验。很多几十年前以为是好的设计,到现在已经很明显的显示出了它们的缺点。这就是为什么我喜欢批评一些语言,操作系统和数据库的设计,因为我看到了它们在历史的长河中已经快要到达终点。自欺欺人的掩盖这些缺陷只会让我们输掉战争。

程序语言与法律

法律是一种非常类似程序的东西。法律的语言也很像程序语言。完美的法律应该像一个程序,把案例输入进去,它就会告诉你这件事是合法还是不合法,如果不合法该怎么弥补。当然,法律处理的事情比起程序处理的问题困难太多了,所以没有任何国家的法律可以达到以上的标准。但总而言之,我觉得像程序一样工作就是法律的精髓。

相对而言,有些国家的法律比起另外一些国家要好很多。美国就是这样一个所谓“法制健全”的国家。美国的法律条款都很详细,对每一个名词(变量名)都有精确的定义和“作用域”。每一个条件之下应该怎么办,都有详细的规定(条件语句)。所以法律条文一般都是大条小条的很长,而且所用语言为了严格而显得古怪,一般人不容易读完。但美国有大批经过严格训练的律师和警察,他们就像 CPU 一样,专门学习和执行这些条文。所以一旦有人被指控违法,很容易就能根据法律条款作出判断。如果有人犯法被捕,就算是再有钱的人,也没法绕过法律而不受惩罚。

美国所有的警察,就算交警都是身强力壮,荷枪实弹。对就算家庭暴力这种事情都是说一不二,严格无误,搜身,录口供,谁对谁错毫不含糊。美国法律执行的单位是个人,而不是家庭。中国人几乎都不明白这个道理,这就是为什么有些中国夫妇到美国之后打架,导致其中一方被驱逐出境。相比之下,中国的法律就是一个充满了 bug 和未定义变量的程序。如果遇到家庭纠纷,几乎没有人叫警察,叫来了也不过是做好好先生。哎呀,两口子打架,过一会儿就好了,叫什么警察。中国的法律空子太多,执法又不严,所以很多人钻法律的空子,让老百姓吃亏,甚至警察都腐败掉。

在美国你经常收到银行和商场之类的地方寄来的通知,说法律要求我们告诉你,我们的隐私政策变了。我们根据某商业法规第X条,可以跟以下范围的商家分享关于你的以下信息。说白了,就是说跟他们结盟的商家会得到你的地址之类的信息,方便往你的邮箱里寄广告。在你租房子之前,房东的租约里面都会附带一些公告,比如说,法律要求我们告诉你,我们的房子由于年代久远,有些公寓的大门(而不是内门)上的油漆里面还有 nn 浓度的铅。请参考以下网址了解铅对人体特别是婴幼儿的危害。

为什么银行,商场和房东要告诉你那些呢?不是因为他们好心,而是因为如果他们没有告诉你,一旦有人指控他们就麻烦了。法律不管你是多么有钱的公司,只要违反了法律条文,一律按条例处罚。有时候你会觉得这些东西麻烦,多此一举,然而等到你跟中国的情况相比,才会理解其中的好处。

程序语言也是一样。很多人觉得写类型是一件麻烦事,所以他们用动态语言。有些动态语言(比如 Ruby, JavaScript,PHP)在变量没有定义的时候居然也能输出结果而不当掉。这就有点像中国的法律,过于灵活和柔弱,结果导致错误不能及时被检测,检测到了了也很难找到具体在什么位置。

程序语言与政治

很多人都曾经妄想着所谓的“社会主义”和“共产主义”能拯救全人类,就像很多程序员都妄想着某一种最近流行的语言能够把他们从繁琐的编程工作里拯救出来一样。几十年前,所谓的“革命者”为了这些很酷很炫的名词,试图把从前的一切文化都焚毁掉。腐朽的资本主义!吃人的旧社会!这就像现在很多 Scala/Clojure/Go 的狂热分子对 Java 之类的语言充满了敌意,一提到这些语言心里就是火。腐朽的 Java,不思进取的 Lisp,Scala/Clojure/Go 就是你们的掘墓人!然而他们没有发现,那些他们试图完全抛弃的语言里面其实有科学合理之处。他们没有看到这些东西之所以存在于那些语言里,是经过了历史的经验教训。这些教训如果不被理解和吸取,当遇到同样的问题,这些新语言就会一样的堕落掉。

有些程序员妄想着 Clojure 和 Go 所谓的 “纯函数式数据结构”,“transactional memory”,“goroutine”等酷毙帅呆的新概念能够一劳永逸的解决并发计算的重重困难,妄想着 Scala 能够让面向对象和函数式编程完美的结合。可是他们没有看到的是人心的险恶,他们就像那些革命者和红卫兵一样,被别有企图的人利用了。在经过深入的研究之后,你会发现这些炫名词其实并不能解决并发计算的关键问题。并行计算之所以困难,是因为物理决定了信息通道的性质。信息只能是单向的,顺序的通过这些信道,而信道的通过速率是有限的。不管你用多么炫的新方式让信息通过它,都不会改变这个事实。所以这些新名词其实不能解决我们的关键问题,要想解决它只有依靠程序员自己的领悟和设计。这就像再先进的工具也不可能帮助你设计出马力翻倍的引擎一样,你必须自己动脑筋,做实验,就像爱迪生发明灯泡一样。

21 Jan 17:05

你的注意力能换钱?

by PanSci
(文/洪朝贵)麦克·高德哈伯(Michael Goldhaber)所写的《注意力经济:网络的自然经济》详细说明了为什么网络正在改变人类社会的货币。如果以“注意”为核心,试图列举它与金钱之间或它与创意之间的关系,会发现诸如植入性营销(embedded marketing)、伪草根运动(astroturfing)、群众募资(crowdfunding)、群众外包(crowdsourcing)、自由软件与自由文化等等当红的网络现象,其实就是两两之间的“兑换”活动。而科莱·史基(Clay Shirky)所谈的“认知剩余”,其实就是扣除工作时间之后所剩下的“全民可用注意力总额”。
 
“创意”与“金钱”之间的直接汇兑,就是“知识产权”一直要强调的概念。不过,因为这个概念与网络为敌——网络有利于他人窃取我的智慧财产——势必要节节败退。随着越来越多人觉醒,“位于长尾的我,干嘛要支持短头的政策”?智财权的概念(台湾法律中的概念,即大陆所说的“知识产权”)将逐渐被边缘化。
 

注意力 v.s. 金钱

一般正常的商业广告(例如购买搜寻关键词,或是脸书的24种广告选项),就是生产商花钱购买潜在消费者的注意力。事实上,任何成功的网络公司,其主要商业模式都是卖广告。另外,在日本与台湾这样高度工业化、人口密集的社会,实体广告的密度极高,是不是也代表着这类的国家正在从货币经济走向注意力经济呢?(请想象“贝壳货币年代过渡到金属货币年代”,贝壳贬值,金属升值的对应场景。)如果你仍旧认为“注意力经济”是一个可笑的概念,那么最好能够另外找到一个理论来解释这些现象。
 
一个令人惊艳的奇特思考案例是:四川航空提供免费接驳服务,却能赚入上亿人民币。成功的原因固然很多,与本文相关的其中一项,就是四川航空看见别人没看见的重要资源/资产:既然本公司暂时掌握了坐在车上(多半没办法做正事的)乘客的注意力,那为什么不在两相情愿的前提之下,把他们的注意力卖给车商?
 
但如果为了提高广告效果,而采取欺骗“注意力提供者”的手段,那就会引起公愤了。将广告以“植入”的方式误导读者,使人们以为读的是新闻,主流媒体这种风气让前中时记者黄哲斌自嘲“自认观念落伍告老还乡”,高调辞去工作。这引发传播学界“反收买、要新闻”的呼声,最后终于“促成修法”,禁止进行植入性营销(embedded marketing)。不过在商业界,“含有欺骗成份的花钱购买注意力”现象依旧存在——例如美丽湾新闻植入事件。而在学术界,教授采购期刊版面的疑云,则一直没有人出面处理。
 
政府与大企业也发现网络强大的力量,所以并不以置入主流媒体为满。植入性营销如果发生在意见市场的长尾——例如博客与留言板——那就是“伪草根运动(astroturfing)”。网络水军颠覆舆论走向,让人以为是民意使然。只不过这里的“金钱与注意力”互换不那么直接。
 
在这个年代,“媒体识读”的能力很重要,因为如果误信植入文、误信伪草根运动,你可能不只浪费了自己保贵的注意力资产,还被误导洗脑。(特别推荐辅仁大学大陈顺孝老师的高中生媒体识读讲义、黄哲斌先生的演讲稿还有我的《谣言止于搜寻》。)
 
另一个“获取注意力之后,可以换得金钱”的最具代表性的现象,就是群众募资(crowdfunding),而最具代表性的网站,就是 Kickstarter 与 Indeigogo。我个人觉得最有兴趣、最想付钱给提案者的是“kickstarter modular robot”、“kickstarter modular phone”、“kickstarter Internet of things”所找到的诸多计划。当然,为了吸引最多人的注意力,其中绝大多数的项目都强调:伴随项目所研发的软件,将以自由软件授权方式释出。不过上面还有很多影片/音乐等等无关科技的项目——例如最终荣获奥斯卡金像奖的纪录片Inocente(推荐访谈中译)。
 

注意力 vs 创意

我在多年前解释“作品要创用CC释出对自己有什么好处”的时候,就已指出:分享创意,可以换取注意力。提出“开放原始码”一词的埃里·克雷蒙德 (Eric S. Raymond)指出:在富足的社会里,分享礼物可以提高自己的社会地位,大约也是这个意思。除了自由软件界之外,还有许多分享音乐/音效、画作、相片的社群或网站。
 
另一个相反的方向“吸引大众注意力,可以获得创意”正好描述了群众外包 (crowdsourcing) 现象。维基百科跟OpenStreetMap 都提出了一个极具吸引力的愿景,于是大家开始贡献创意。直接征求创意的 OpenIdeo 跟 InnoCentive 更是 众多群众外包案例当中的代表典范。
 
这两个方向也不能说是相反,只是有些项目比较适合从“创作者”的观点来理解,另一些项目则比较适合从“提案者”的观点来理解。当你换个观点来理解上述项目时,会发现:重要的自由软件计划也会吸引来程序高手(所以也算是群众外包);维基百科里,频繁贡献的一些人也很受尊重(所以也算是“分享创意,换取注意力”)。翻译各国博客文章的全球之声好像从两个观点去看都很合适。
 

注意力 vs 隐私

顺带一提的是另一个重要的趋势:网络时代隐私的流失。两年前已经写过“注意力匮乏”之下的隐私流失一文,从“注意力稀”的观点解释隐私流失的原因:
  1. 为了争取别人有限的注意力,我们有意识地出卖自己的隐私;
  2. 因为自己的注意力濒临破产,导致自己的隐私无意识地流失。

扎克伯格(Mark Zuckerberg)的姐姐兰迪(Randi)不满私人相片被转贴,更说明了脸书隐私设定规则的复杂。再加上脸书隐私规则日趋宽松,一般人根本不可能随时注意如何保护自己的隐私,更别提每遇政策改变就回头更改旧数据的隐私设定。此外,在真正私密通讯场合,采用具有加密功能的自由软件是终极解;不过这太麻烦了(浪费好多注意力),所以一般人的隐私越来越不安全。

复杂的网络世界里,我们的隐私越来越不保。图片来源:shutterstock
 

 视注意力为个人/企业/社会的重要资产

如果注意力经济真的是网络时代最重要的经济现象,那么“注意力”就是一种极重要的资产。不过从个人的角度来看,这个资产包含着完全不同的两大区块:上面谈的,多半是 “我的名声”,也就是存放在别人脑海里的印象/ 别人对我的注意力。“隐私”一段中2所谈的,则是“我自己有限的注意力”。如果“扎克伯格定律(Zuckerberg’s Law)”(每年网友所分享的讯息将会是前一年的2倍)是真的,如果你在网上从来就只加不删朋友,那么你真的应该开始思考如何像我一样删减朋友, 避免注意力破产
 
从社会整体的角度来看,如何善用大众整体有限的注意力,会是一个很重要的课题。扣除工作时间之后所剩下的“全民可用注意力总额”正是 科莱·史基在下班时间扭转未来一书当中所说的认知剩余。如果高德哈伯所说的“注意力经济势将取代工业时代的货币经济”是事实,那么一个洞视网络现象、有远见的社会,不应该再迷信即将过时的金钱经济指标。(请再次想象“贝壳货币年代过渡到金属货币年代”,贝壳贬值,金属升值的对应场景。)真正值得我们认真思考与追求的,变成是:
  1. 在既有的金钱/工作体制下,有许多工作量除了产生表格与数字绩效之外,并没有真实的意义。如何减少员工浪费在这上面的注意力?
  2. 用什么机制鼓励那些被释放出来的注意力(失业者、无薪假者、工时减少者)把他们的认知剩余投入真正能够改进社会(同时能够顺便协助企业组织自身提升形象)的活动?
不见得只有总统才有能力做这两件事。例如从大学到小学的各级校长,他们所掌握的(内是师生/外是社会)注意力资源,让他们有很大的发挥空间;即使只是一家小公司,如果改以注意力经济的角度思考,可能自身都会受益。不要忘记:我们身处于一个“位于长尾的每个人都可以是一位‘产销合一者(prosumer)’的顺网络者昌,逆网络者亡”的有趣年代。
 
如果您同意网络世界将无可避免地变成生活的重要部分,那么我们最好选择一个能够自然符合这个世界的经济定律。这部新的定律和旧的经济学者所教的,或是“信息年代”所带来的想象,极不相同。真正有价值的东西,是今日的稀有财富——也就是注意力。注意力经济有它自己一套财富原则,有它自己一套阶级区分——明星v.s.粉丝——以及它自己的一套财产原则。这一切都将令它与工业时代的货币经济格格不入;却又势将取代工业时代的货币经济。最能够适应这个新典范的人,将是最成功的人。

— 麦克·高德哈伯

 

本文转载自PanSci,转载时文字部分有修改。

果壳相关小组

你可能感兴趣

  1. 走神,认知给我们的礼物
  2. 做回婴儿又如何?
  3. 为什么我们需要新的经济思维?
  4. 三心二意,助你好运?
  5. GDP为什么不可靠?
  6. 走走神儿,有创意
  7. 就业指标为什么没有用?
  8. “经济复苏”是生活水平的改善还是数字改善
  9. 谁干掉了传统媒体?
  10. 假期到了,请帮大脑脱宅
  11. “免费”也有商业价值吗?
  12. 网络时代货币的新面貌
15 Jan 10:34

[饭文]为何高等教育在制造失业?

by 辉格

为何高等教育在制造失业?
辉格
2013年12月29日

社科院最近发表的一份报告显示,几年前就开始显露出来的大学毕业生就业困难依然没有缓解的迹象,今年毕业的大学生中,有17.6%在毕业后两个月时仍未找到工作,随着毕业生人数的持续增长,这一情况看来至少还要维持三四年,直到毕业生开始下降才有望缓解。

这一困难,与总的就业状况是背道而驰的,总体上,劳动力市场从五六年前就开始进入了一个供给下降的阶段,年轻劳动力的供给萎缩尤为剧烈,随之而来的是,企业大面积用工荒,蓝领工人工资迅速提高,劳动密集型产业开始外移。

大学生与总体就业状况的反差,也体现在上述报告的另一项数据中:高级职业学校的学生,在毕业两个月后没找到工作的只有8.1%,不到大学生的一半;这一反差实际上表明了,在大学生的职业期望和当前产业体系所提供的工作岗位之间,存在巨大的错位,而这种错位很大程度上正是现有教育体系所造成的,教育经历一方面让学生对自我身份定位和未来职业生涯产生了不切实际的预期,同时也没有帮助他们获得当前市场真正需要工作技能。

特别引人注目的是,这种不切实际的期望,不仅仅是甚至主要不是有关工资水平的,实际上,在许多情况下,蓝领岗位的工资已经大幅超出白领岗位,但多数大学生仍宁愿拿着两千多的月薪,忍受高昂的物价和恶劣的生活条件,挤在大城市的群租房甚至地下室里,也不愿接受一份月薪五六千或者更高的蓝领工作,仅仅因为他们认为自己理所当然的已经成为一名白领。

每个人对自己的社会地位有个认知,除非这个地位已低得无法再低,他都会希望他人认可其地位,并竭力避免从这地位跌落,当跌落前景出现时,如何避免它就成了头等优先的大事,重要性甚至超过生命,许多人在破产、失业、事业失败、罪行丑闻败露后自杀,都是因为意识到自己的社会地位已经不保。

那么,为何普通高等教育会让学生对白领地位形成自我认知,而职业技术教育则很少有这样的效果呢?或许这与中国文化中的士大夫价值观和科举传统有关,士大夫历来是蔑视技术专长和专业价值的,而科举制不仅与这种价值观完全配合,还搭建了一部反专业化的社会地位爬升阶梯,这部阶梯在国家资源分配能力的作用下,在整个社会流动结构中取得了压倒性影响力。

所以,尽管传统中国社会的流动性很高,即便最贫困阶层的青年也有机会向上爬,但他们借以向上爬升的通道,(至少在和平年代)只有科举一途(尽管在某些时代做太监也是条上升出路,但与科举相比,这条通道毕竟太狭窄),而凭借各种专业技术能力向上爬升的空间极为有限,许多王朝甚至通过将许多技术性职业列入贱籍而刻意堵死这些通道。

结果是形成了这样一种价值氛围,在其中,像厨师、木匠、技工、家政、护理这样的技术性职业,尽管收入可以很高,但在流行价值观中却被视为是卑下而缺乏吸引力的,不是一条可借以向上爬升的适当阶梯;而与之相应的后果是,在这些职业圈子的内部,也始终未能发展出一种丰厚的专业价值观,让从业者从中获得价值满足和自豪感,并以此吸引年轻入行者。

所谓专业价值观,就是从事某项专业、拥有相应技能、用这些技能作出好东西——这些活动本身带给从业者金钱和实物报酬之外的满足,这种价值观的形成,需要专业化分工的稳定存在,需要专业圈子内对产品和技能进行持续的比较、评价和讲述,通过师徒传承关系而形成传统和流派,这种文化要发育得比较丰厚,还需要一部分从业者成为专业的评论、研究和传播者。

比如餐饮行业要形成自己的价值观,不仅需要专业厨师,还要美食评论家去评论和传播他们的作品,要有历史学家去追溯烹饪技术的发展沿革和传承关系,要有食品和营养专家去研究各种食材,要有行业组织和专业刊物为知识和传统的积累提供交流和组织平台。

然而,或许是因为我们的文化有着排斥专业价值观的传统,或许是因为历史上就缺乏发育成熟的专业价值观,或许是因为缺乏专业领域发展自身组织和文化传统所需要的恰当制度环境,此类价值观的缺失,是中国文化与西方文化的一个显著差异,这一点,在诸如维基词条编辑和开源代码贡献这样没有金钱回报的活动中,表现的最清楚。

很明显,职业学校和大学原本都可以参与这些发展过程,但实际上,国内的职业学校大都停留在技能培训,而缺乏向上发展的动力,而普通高校则多半对这些领域不屑一顾,视之为难登大雅之堂的市井杂学,难入象牙之塔的奇技淫巧,在前些年的学校合并升级改造浪潮中,许多学校甚至迫不及待的摆脱这些成分而把自己标榜成所谓综合性大学。

由政府设计和经营的公立教育与科研产业,实际上构造了一个由普通教育、大学教职、院士、核心期刊、诺贝尔奖等元素构成的现代科举系统,以及相应的价值观,白领成了现代士大夫,那些宁可受穷也不愿放下架子从事蓝领职业的人,则成了现代范进,这个系统在未来将继续源源不断的创造失业。

对于现代范进们,更为不幸的是,当前产业结构的变化,正在消灭越来越多的白领岗位,在市场全球化和信息技术革命的推动下,许多产业已经和正在经历扁平化改造,比如在新型连锁企业对传统零售服务业、电子商务对实体零售店的替代过程中,企业层级结构变得高度扁平,大量中间层级被取消,而正是这些中间层级提供了大量白领岗位。

 

06 Jan 04:43

我和权威的故事

by Yin Wang
???

不喜欢被称作大神的大神又更新了

每个人小时候心里都是没有权威的,就像每个人小时候也都不相信广告一样。可是权威就像广告,它埋伏在你的潜意识里。听一遍不信,听两遍不信,……,直到一千遍的时候,它忽然开始起作用了,而且这作用越来越强。

消灭广告所造成的幻觉,最好的办法就是去尝试,去实地的考察它。有些虚幻的东西只要你第一次尝试就会像肥皂泡一样破灭掉。可是如果你不主动去接触它,它就会一直在你脑海里造成一种美好神圣的假象。越是得不到的越是觉得美好。很神奇的一个现象就是,权威对人思想的作用其实也跟广告一样。

上大学以前的人因为没有专业,所以还不怎么崇拜权威,大不了追追歌星,影星,球星啥的。而进了大学之后,就会开始对本领域的权威耳濡目染。一遍,两遍,一千遍的听到同学们仰慕某“牛人”或者“大师”的名字,虽然从来没亲身见过,不知不觉就对这人产生了崇拜心理,然后自愧不如。不知不觉的,自己也开始附和这些说法,不自觉地提到这些大师的名字,引用他们说的话作为自己的行动指南。

Donald Knuth, Dennis Ritchie, Ken Thompson, Rob Pike, ... 就是通过这些途径成为了很多计算机学生的权威。以至于几十年以后,他们的一些历史遗留下来的糟糕设计和错误思想还被很多人奉为神圣。

Donald Knuth

很多人(包括我)都曾经对 Knuth 和他的 The Art of Computer Programming (TAOCP) 极度崇拜。在我大学和研究生的时候,有些同学花了不少钱买回精装的 TAOCP 全三卷,说是大概不会看,但要供在书架上,镇场子。当时我本着“书非借不能读也”的原则,再加上搬家的时候书是最费力气的东西,所以坚决不买书。我就从图书馆把 TAOCP 借了来。说实话我哪里看的下去啊?那里面的程序都是用一个叫 MIX 的处理器的汇编语言写的。一个字节只有6位,每位里面可以放一个十进制数(不是二进制)!还没开始写程序呢,就开始讲数学,然后就是几十页的公式推导,证明…… 接着我就睡着了。但我总是听说有人真的看完过 TAOCP,然后就成为了大师。比尔盖茨也宣称:“要是谁看完了 TAOCP,请把简历投给我!” 在这一系列的号召和鼓吹之下,我好几次的把 TAOCP 借回来,下定决心这次一定看完这旷世奇书。每次都是雄心勃勃的开始,可从来就没看完过开头那段 MIX 机器语言和数学公式。

看不懂 TAOCP 总是感觉很失败,因为看不懂 TAOCP 就成不了“大师”,可我仍然认为 Knuth 就是计算机科学的神,总能从他那学点什么吧,所以又开始折腾他的其他作品。这就是为什么我开始用 TeX,并且成为中国 TeX 界的主要“传教士”之一。为了 TeX,我把 Knuth 的 TeXbook 借回来,从头到尾看了两遍,做完所有的习题,包括最难最刁钻的那种“double bend”习题。接着又开始看 MetaFont Book,开始使用 MetaPost 进行绘图。开头还挺有成就感,可是不多久就发现学会的那些 TeX 技巧到了临场的时候就不知道该怎么用,然后就全都忘记了。这就是为什么我把 TeXbook 看了两遍,可是看完第二遍之后不久还是忘记得一干二净。

师兄师姐看到我用 TeX,说怎么折腾这么过时的玩意儿。我很气愤他们以及国内学术界居然都用 Word 排版论文,就开始针锋相对,写出一系列煽动文章鼓吹 TeX 的种种好处,打击“所见即所得”这种低智商玩意儿。这还不够,又开始折腾 Knuth 设计的 MMIX 处理器,并且认为 MMIX 的寄存器环就是世界上最先进的设计。发现一些无关紧要的小错,就给 Knuth 发 email,居然拿到两张传说中的“Knuth 支票”,并且一度引以为豪。当然像所有拿到 Knuth 支票的人一样,你是不会去兑现它的,甚至有人把它们像奖状一样放在相框里。我还没那么疯狂,那两张支票一直在它们原来的信封里。多年以后我到美国想兑现那支票的时候,发现它们已经过期了,后来就不知道丢到哪里去了。

当你心里有了这样的权威,其他人的话你是不可能听得进去的,就算他们其实比你心目中的权威更具智慧也一样。在清华的时候我有时候去姚期智的小组听客串讲座。有一次请来了美国某大学一个教授讲算法,不知道怎么的我就跟他聊起 TAOCP,大概是想请教他如何学习算法。他跟我说 Knuth 的书已经比较过时了,你可以看看 MIT 的那本《算法导论》。可是这位教授的名气怎能和 Knuth 相比,这话我恁是没有听进去,仍然认为 TAOCP 隐藏了算法界最高的机要,永恒的珍宝。

在清华的时候我很喜欢一门叫做“计算几何”的课,就经常跟那门课的老师交流思想。有一次我在 email 里面提到 Donald Knuth 是我的偶像,那位老师很委婉的回复道:“有偶像很好啊,Knuth 也曾经是我的偶像。” 我对“曾经”这两个字感到惊讶:难道这意味着 Knuth 现在不是他的偶像了?在我执意的询问之下他才告诉我,其实世界上还有很多更聪明的人,Knuth 并不是计算机科学的一切。你应该多看看其他人的作品,特别是一些数学家的。然后他给了我几个他觉得不错的人的名字。

现在回想起来,这些话对我是有深远作用的。那位老师虽然在系里的“牛人”们眼里是个研究能力(也就是发 paper 能力)不强的人,但是他却对我的人生转折有着强有力的作用。他引导了我去追寻自己真正的兴趣,而不是去追寻虚无的名气。我发现很多人都在为着名气而进行一些自己其实不感兴趣的事情,去做一些别人觉得“牛气”的事情。我真希望他们遇到跟我一样的好老师。

在现在看来,Knuth 的 TAOCP 就是所谓的“神圣的白象”(white elephant)。大家都把它供起来,其实很少有人真的看过,却要显得好像看过一样,并且看得津津有味。这就让试图看懂它的人更加自卑和着急,甚至觉得自己智商有问题。别人都看过了,我怎么就看不懂呢?其实 TAOCP 里面的大部分算法都不是 Knuth 自己设计的,而且他对别人算法的解释经常把简单的问题搞得很复杂。再加上他执意要用汇编语言,又让程序的理解难度加倍。

有一句话说得好:“跟真正的大师学习,而不是跟他们的徒弟。”如果你真的要学一个算法,就应该直接去读那算法的发明者的论文,而不是转述过来的“二手知识”。二手的知识往往把发明者原来的动机和思路都给去掉了,只留下苍白无味,没有什么启发意义的“最后结果”。确实是这样的,多年以后当我看见 Knuth 计划中的几卷新的 TAOCP 的目录时,发现其中大部分的东西我已经通过更容易的方式学到了,因为我找到了这些知识的源头。

所以之前的那位访问清华的教授说的其实是实话,Knuth 真的落伍了,可是就算在美国也少有人知道或者承认这个情况。有一次看一个对世界上公认最厉害的一些程序员的采访,包括总所周知的一些大牛,以及 ML 的设计者 Robin Milner,Haskell 的设计者之一 Simon Peyton Jones 等人。也不知道采访者是什么心理,在对每个人的采访中他都问,你看过 TAOCP 吗?大部分人都说看过,真是了不起的巨著,很重要啊云云。只有 Robin Milner (如果我没记错的话)比较搞笑,他说我希望我看过,但是可惜实在没时间。我一直把 TAOCP 垫在我的显示器下面,这样我工作时就可以一直看着它们 :)

Knuth 说“premature optimization is the root of all evil”,然而他自己却是非常喜欢用 premature optimization 的人。他的代码里到处是莫名其妙的小聪明,小技巧。把代码弄得难懂,实际上却并没有得到很多性能的提高。有一次看 MMIX 处理器的模拟程序,发现他用来计算一个寄存器里的“1”的个数的代码非常奇怪。本来写个循环,或者用那种从末位减 1 的做法就可以了,结果他的代码用了 Programming Pearls 里面一个古怪的技巧,费了我半天时间才看懂,后来我发现这个技巧其实还不如最简单的方法。就是这些细小却又蹊跷的做法,使得 Knuth 的代码用细节掩盖了全局,所以到最后我其实也没从大体上搞懂一个处理器的模拟器应该如何工作。直到后来到 Indiana 学习了程序语言的理论之后我才发现,其实处理器模拟器(以至于处理器本身)的工作原理很简单,因为它就是一个机器代码的解释器。使用跟高级语言解释器同样的结构,你可以比较容易的写出像 MMIX 模拟器那样的东西。

Knuth 最重要的一个贡献恐怕是程序语言的 parsing (语法分析),比如 LR parsing,然而 parsing 其实是一个基本不存在的人造问题。它的存在是因为人们的误解,以为程序语言需要有跟人类语言一样的语法,所以把程序语言搞得无端的复杂和困难。如果你把语法简化一下,其实根本用不着什么 LR,LALR。我最近给我自己设计的语言写了一个 parser ,从头到尾只花了两个小时,500 行 Java 代码,包括了从 lexer 一直到 AST 数据结构的一切。完全手写的代码,根本没用任何复杂的 parsing 技术和 YACC 之类的工具,甚至正则表达式都没有用。之所以可以这样,因为我的语法设计让 parsing 极其容易,比 Lisp 还要容易。Knuth 过度的强调了 parsing。他的误导使得很多人花了几十年时间来研究 parser,到现在还在不时地提出新的技术,用于设计更加复杂的语法。何必呢?这只会让程序员和编译器都更加痛苦。如果这些人把时间都花在真正的问题上,那今天的计算机科学不知道要美好多少。

几乎每一本编译器教材都花大量篇幅来讲述 DFA, NFA, lexing, LL, LR, LALR…… 几乎每个学校的编译器课程都会花至少 30% 的时间来做 parser,折腾 LEX,YACC 等工具,而对于编译器真正重要的东西却没有得到很多的训练。这就是为什么 Kent Dybvig 的编译器课程如此有效,因为 Scheme 的语法非常简单,我们根本没有花时间来做 parser。我们的时间用在了思考真正的问题:做优化,实现尾递归,高阶函数…… 很多语言梦寐以求却又做不好的东西。这样的课程给了我可以发挥自己潜力的余地,我的课程编译器里面具有大量的独创写法,我的 X64 机器代码生成器生成极其短小的代码,让 Kent Dybvig 都在背地里琢磨是怎么回事。这些东西到现在也许仍然是世界上最先进的技术。

一个人的思维方式似乎决定了他设计的所有东西。Knuth 的另一个最重要的发明,文学编程(Literate Programming)其实也是多此一举,制造麻烦。文学编程的错误在于认为程序语言应该像人类语言,应该适应所谓的“人类思维”。然而程序语言却是在很多方面高于人类语言的,它不应该受到人类语言里的糟粕的影响。把程序按照 Knuth 的方式分开在不同的文章段落里,造成了代码之间的关系很难搞清楚,而且极其容易出错。这个错误与“Unix 哲学”的错误类似,把程序作为一行一行的文本,而不是一个像电路图一样的数据结构。我不想在这里细说这个问题,对此我专门写了一篇文章,讲述为什么文学编程不是一个好主意。

TeX 其实也是异常糟糕的设计。它过度的复杂,很少有人搞得懂怎么配置。经常为了一个简单的效果折腾很久,然后不久就忘了当时怎么做的,回头来又得重新折腾。原因就是因为 TeX 的设计缺乏一致性,特殊情况太多,而且组合(compose)能力很差。所以你需要学太多东西,而不是跟象棋一样只需要学习几个非常简单的规则,然后把它们组合起来形成无穷的变化。

在程序语言设计者看来,TeX 的语言是世界上最恶劣的设计之一,但如果没有这个语言,它也许会更加糟糕。其实 TeX 之所以有一个“扩展语言”,有一个鲜为人知的小故事。在最早的时候 Knuth 的 TeX 设计里并没有一个语言。它之所以有一个语言是因为 Scheme 的发明者 Guy Steele。Knuth 设计 TeX 的那个时候 Steele 碰巧在斯坦福实习。他听说 Knuth 要设计一个排版系统,就建议他设计一个语言,以应付以后的扩展问题。在 Steele 的强烈建议和游说之下,Knuth 采纳了这个建议。可惜的是 Steele 并没能直接参加语言的设计,在短短的一个夏天之后就离开了斯坦福。

Knuth 的作品里面有他的贡献和价值,TeX 的排版算法(而不是语言)也许仍然是不错的东西。可是如果因为这些好东西爱屋及乌,而把他所推崇的那些乱七八糟的设计当成神圣的话,那你自己的设计就逃脱不出同样的思维模式,让简单的事情变得复杂。仍然对 TeX 顶礼膜拜的人应该看一下 TeXmacs,看看它的作者是如何默默无闻的,彻彻底底的超越了 TeX 和 Knuth。

在我看来,Knuth 是个典型的精英主义者,他觉得自己做的都是最好,最有“格调”的。他利用自己的权威和特立独行来让用户屈服于自己繁复的设计,而不是想法设计出更加易用的工具。TeX 的版本号每次更新都趋近于圆周率π,意思是完美,没有 bug。他奖励大额的支票给发现 TeX 代码里 bug 的人,用于显示自己对这些代码的自信,然而他却“冰封”了 TeX 的代码,不再填加任何新东西进去,也不再简化它的设计。当然了,如果不改进代码,自然就不会出现新的 bug,然而它的设计也就因此固步自封,停留在了几十年以前。更奇怪的是,“TeX”这个词居然不按照正常的英语发音逻辑读成"teks"。每当有人把它“读错”,就有“高手”打心眼里认为你是菜鸟,然后纠正:“那个词不读 teks,而要读‘特喝’,就像希腊语里的 chi,又像是苏格兰语的 loch,德语的 ach,西班牙语的 j 和俄语的 kh。”也许这就叫做附庸风雅吧,我是纯种的欧洲人!;-) 当一个软件连名字的发音都这么别扭,这么难掌握,那这个软件用起来会怎样?每当你提到 TeX 太不直观,就有人跟你说:“TeX 是所想即所得,比你的所见即所得好多了!”可事实是这样吗?看看 TeXmacs 吧,理解一下什么是“所见即所得+所想即所得”二位一体。

我跟 Knuth 的最后一次“联系”是在我就要离开清华的时候。我从 email 告诉他我觉得中国的研究环境太浮躁了,不是做学问的好地方,想求点建议。结果他回纸信说:“可我为什么看到中国学者做出那么多杰出的研究?计算机科学不是每个人都可以做的。如果你试了这么久还不行,那说明你注定不是干这行的料。”还好,我从来没有相信他的这段话,我下定了决心要证明这是错的。多年的努力还真没有白费,今天我可以放心的说,Knuth 你错了,因为我已经在你引以为豪的多个方面超过了你。

Unix

Unix 的创造者们是跟 Knuth 非常类似的权威,他们在我的心目中也曾经占据了重要的位置,以至于十年前我写了一篇文章叫《完全用 Linux 工作》,大力鼓吹 Unix 的“哲学”,甚至指出 Linux 不能做的事情就是不需要做的,并且介绍了一堆难用的 Unix 工具,引得很多人去折腾。可如果你知道我现在对 Unix 的态度,肯定会大吃一惊,因为在经过努力之后,我成功的“忘记”了 Unix 的几乎一切,以至于本科刚毕业的学生都会以为我是脑盲,并且以为可以在我面前炫耀自己知道的 Linux 技巧。他们不会明白,在我心里 Unix/Linux 的设计是计算机软件界目前面临的大部分问题的罪魁祸首,而他们显示给我看的,只不过是 Unix 的思想和精英主义给程序员造成的精神枷锁。其实我并不会忘记 Linux 的设计,但我已经下意识的以熟悉 Linux 的奇技淫巧为耻,所以很多时候我即使知道也要装作不知道。因为我是机器的主宰,而不是它的奴隶,所以我总是想办法让机器去帮我做更多的事,帮我记住那些无聊的细节,而不是去顺从它的设计者所谓的“哲学”。

评论 Unix 和它的后裔们总是一件尴尬的事情,因为你提到它们的任何一个缺点,都会被很多人认为是优点。GNU 的含义是“GNU is Not Unix”,但很可惜的是 GNU 和 Linux 的设计从来没有摆脱过 Unix 思想的束缚。Unix 的内存管理,进程,线程,shell,进程间通信,文件系统,数据库…… 几乎都是很蹩脚的设计。所谓的“Unix 哲学”,也就是进程间通信主要依靠无结构字符串,造成了一大批过度复杂,毛病众多的工具和语言的产生:AWK,sed,Perl,…… Unix 的内存管理是按“页”而不是按“结构”分配,相当于把内存分配的任务完全推给应用程序。而且允许任意的指针操作,这就像给每个老百姓一把爱走火的枪。可是又想要“安全”,自相矛盾。没办法,不得不强制进程数据空间完全隔离,使得进程间无法直接传递数据结构。进程和线程上下文切换开销过大,造成了使用大规模并发或者分布式计算的瓶颈,导致了 goroutine 和 node.js 等“变通方法”的产生。把数据无结构的存储在文件里,无法有效的查找数据,造成了关系式数据库等过度复杂的数据解决方案的产生。再加上后来 WEB 的设计,现在的网站基本上就是补丁加补丁,一堆堆的 hack。

“Unix 哲学”貌似也有好的部分,比如“每个程序只做一件事,多个程序互相合作。”然而,这个所谓的哲学其实就是程序语言(比如 Lisp)里面的模块化设计。它当然是好东西,然而这些思想被 Unix 偷来之后,有其名而无其实。很少有 Unix 程序真正只做一件事的,而且由于字符串这种通信机制的不可靠,它们之间其实不能有效地合作。有时候你换了一个版本的 make 或者 sed 之类的工具,你的 build 就莫名其妙的出问题。这就是为什么有的公司请了专门的所谓“build engineer”,因为高级别的程序员不想为这些事情操心。Lisp 程序员早就明白这个道理,所以他们尽一切可能避免使用字符串。他们设计了 S 表达式,用于结构化的传输数据。实际上 S 表达式不是“设计”出来的,它是每个人都应该首先想到的,最简单的可以表示树结构的编码方法。Lisp 的设计原则里面有一条就是:Do not encode。它的意思是,尽量不要把有用的数据编码放进字符串。Unix 的世界折腾来折腾去,XML,CORBA,…… 最后才搞出个 JSON,然而其实 JSON 完全不如 S 表达式简单和强大。Unix 就像一个脑瘤,它让人们放着最好的解决方案几十年不用,不断地设计乌七八糟的东西用来取代乌七八糟的东西。这些垃圾对人有很大的洗脑作用。前段时间我说 S 表达式比 JSON 简单,有人居然跟我说 JSON 好些,因为它结构的 field 是“无顺序”的。这让我相当无语,因为一个编码方式有没有顺序完全取决于你如何解释它。从这个意义来讲,S 表达式可以是有顺序,也可以是没有顺序的。

Unix 喜欢打着“自由”和“开源”的旗号,可是它的历史却充满了政治,宗教,利益冲突和对“历史教科书”的串改。几乎所有操作系统课本的前言都会提到 Unix 的前身 Multics,而提到 Multics 的目的,都是为了衬托 Unix 的“简单”和伟大,接下去基本上就是按部就班的讲 Unix 的设计,仿佛 Unix 就是世界上唯一的操作系统一样。 课本会告诉你,Multics 由于设计太复杂,试图包罗万象,最后败在了 Unix 手下。可是如果你仔细了解一下 Multics 的历史,就会发现最后一台 Multics 机器直到 2000 年还在运行,拥有 Unix/Linux 到现在还没有的先进而友好的特性,并且被它的用户所爱戴。Multics 的设计并不是没有问题(对比一下 Lisp Machine 和 Oberon),但是相比之下,Unix 的设计一点都不简单。Unix 抄了 Multics 最好的一些思想,有些没有抄得像,然后又引入了很多自以为聪明的糟粕。可是 Unix 靠着自己病毒一样的特征,迅速占领了市场。Unix 最开头是开源和免费的,但是后来 AT&T 发现这里面有利可图,所以就收回了使用权,并且开始跟很多人打官司。AT&T 的邪恶比起微软来,真是有过之而无不及。

Unix 的很多设计是如此龌龊,很多人却又由于官僚的原因不得不用它。以至于 Unix 出现的早期怨声载道,有人甚至组织了一个 mailing list 叫“Unix 痛恨者”(Unix Haters)。你很有可能把这些人当成菜鸟,可是这些人其实都用过更好的操作系统,有的甚至设计实现过更好的操作系统甚至程序语言。最后他们的叫骂声被整理为一本书,叫做 Unix Hater's Handbook。让人惊讶的是,这本书有一个“反序言” (anti-foreward),作者正是 Unix 和 C 语言的设计者之一,Dennis Ritchie。这个反序言说,Unix 这座设计缺乏一致性的监狱会继续囚禁你们,聪明的囚犯会从它里面找到破绽,可惜的是自由软件基金会(FSF)会建造跟它完全兼容的监狱,只不过功能多一些。拥有三个 MIT 学位的记者,微软的研究员,Apple 的高级科学家可能还会对这座监狱的“规矩”贡献一些文字。从这些文字里,我看到了一个炫耀武力的暴君,看到了赤裸裸的权威主义和教条主义。

可惜的是在软件的世界里任何糟糕的设计都可以流行,只要你的广告做得好,只要你的传教士够多。一知半解的人(比如十年前的我)最喜欢到处寻找“新奇”的东西,然后开始吹嘘它们的种种好处,进而成为它们的布道者。再加上大学计算机系的“紧跟市场”的传统,不幸的事情发生了:Unix 和它的后裔们几乎垄断了服务器操作系统的市场。由于 Unix 的垄断,现在的软件世界基本上建立在一堆堆的变通之上,并且固化之后成为了“珍珠”。公司里,学校里,充满了因为知道一些 Unix 的“巧妙用法”而引以为豪的人,殊不知他们知道的只是回避一些蹩脚设计的小计俩。程序员有太多的特例和细节需要记忆,不但不抱怨,还引以为豪。很少有人想过如何从根本上解决问题,历史的教训很少有人吸取,以至于几十年前犯过的设计错误还在重现。Unix 的最大贡献,恐怕就是制造了大量的工作岗位—因为问题太多太麻烦,所以需要大量的人力来维护它的运行。

现在看来,Unix 当初就是依靠《皇帝的新装》里织布工的办法封住了大家的嘴。皇帝的织布工们说:“愚蠢或者不称职的人都看不见这件衣服。”Dennis Ritchie 说:“Unix 是简单的,但只有天才才能理解这种简单。”看出来了吗?你不敢说 Unix 的设计太乱太复杂,因为这话一出口,立马会有人引用 Dennis 的话说,是你自己不够天才,所以不理解。当然了,这就意味着他比你聪明,因为只有天才才能理解这种简单嘛。哎,这种喜欢显示自己会用某种难用工具的人实在太多了。你不敢批评这些工具对用户不友好,因为你立即会被鄙视为菜鸟。

Dennis Ritchie 去世了。死者长已矣,可是有些他的崇拜者在那个时候还要煽风点火,拿他的死与 Steve Jobs 的死来做对比,把像这样的照片四处转帖,好像 Steve 死错了时间,抢了 Dennis 的风头似的。然后就有人写一些这样的文章,把世界上的所有系统,所有语言都归功到 Dennis 和 Unix 身上。看到这些我明白了,所谓的“天才”就是这样被造出来的。在我看来这些是很滑稽的谬论,就像是在说有人拿一把很钝的剪刀做出了一件精美的衣服,所以这剪刀立下了汗马功劳。其实这人一边裁布一边在骂这剪刀,心想妈的这么难用,快点做出这衣服,卖了钱买把好点的!

用了这么久 Apple 的产品,平心而论,虽然它们并不完美,然而它们并不是 Unix 的翻版,它们做出了摆脱 Unix 思想束缚的努力。它们本着机器为人服务的原则,而不是把人作为机器的奴隶。Mac 的很多内部设计跟 Unix 有着本质的不同。然而就是这样的系统,被 Dennis Ritchie 在他的反序言里面蔑称为“以 Sonic the Hedgehog 作为智力主题和交互设计基础的系统”。

有谁知道,在那同样一段时间里,Lisp 的发明者 John McCarthy,ML 的发明者 Robin Milner,都相继去世了呢?那个时候我只是在 mailing list 看到有人发来简短的消息,然后默默地思念他们给我带来的启迪。我们没有觉得 Steve Jobs 的死抢了他们的风头,因为他们不需要风头。死就是要安安静静的,让知己者默哀已经足矣。出现这种事情恐怕不能怪 Dennis Ritchie 自己,然而这些 Unix 的崇拜者们,真的应该反省一下自己的做法了。

Unix 的设计者们曾经在我的心里占据了一席之地,可是现在觉得他们其实代表了反动的力量,他们利用自己的影响力让这些糟糕的设计继续流传,利用人们的虚荣心,封住大部分人的嘴,形成教条主义,让你认为 Unix 的设计是必须学习的东西。很多人成为了 Unix 的传教士和跟屁虫,没有什么真实水平,就会跟着瞎起哄,把 Unix 设计者的话当成教条写进书里。可是他们的权威和名气是如此之大,让我在很多人面前只能无语。

Go 语言

现在,同样这帮 Unix “大牛”们设计了 Go 语言,并且依仗自己的权威和 Google 的名气大力推广。同样的这帮跟屁虫开始使用它,吹捧它,那气势就像以为 Go 可以一统天下的样子。真正的程序语言专家们都知道,Go 的设计者其实连语言设计的门都没摸到。这不是专家们高傲,他们绝不会鄙视和嘲笑一个孩子经过自己的努力做出一个丑陋的小板凳。他们鄙视,他们嘲笑,因为做出这丑陋小板凳的不是一个天真的小孩,而是一些目空一切的人,依仗着一个目空一切的公司。他们高举着广告牌,试图让全人类都坐这样丑陋的板凳。

跟当年设计 Unix 时一个德行,不虚心向其它语言和系统学习经验教训,就知道瞎猜瞎撞。自己想个什么就是什么,但其实根本就不知道自己在干什么。把很多语言都有的无关紧要的功能(比如自动格式化代码)都吹嘘成是重大的发明,真正重要的东西却被忽略。Go 语言的设计在很多方面都是历史的倒退,甚至犯下几乎所有其他语言都没有的低级错误。在语法上大做花样,却又搞得异常丑陋,连 C 和 Java 都不如。自己不理解或者实现难度大点的东西就说是不需要的,所以连很多语言支持的 parametric type(类似 Java generics)都没有,以至于没法让程序员自定义通用数据结构,只好搞出一堆特例(比如 map,make,range)来让程序员去记。这些做法都跟 Unix 如出一辙。

Go 语言最鲜明的特征就是 goroutine,然而这个东西其实每个程序语言专家都知道是什么。有些语言比如 Scheme 和 ML 提供了 first-class continuation(call/cc),可以让你很容易实现像 goroutine 这样的东西,甚至实现硬件中断的“超轻量线程”。至于 Go 那种“基于接口”的类型系统设计,我在很多年前就已经试验过,并且寄予了很大的希望。结果最后经过很多的研究和思索后发现有问题,于是放弃了这个想法。很显然,我不是第一个在这个问题上失败的人,很多语言专家在使用 parametric type 以前都试图过做这种基于接口的设计,结果最后发现不是什么好东西,放弃了。然而 Go 的设计者却没有学到这些失败教训,反而把它当成宝贝。一个很显然的问题是,在 Go 里面你经常会需要使用“空接口”(interface{}),用来表示所有类型。这就像使用 C 的 void 指针一样,有着静态类型系统的麻烦,却失去了静态类型系统的好处。

每当你提到 Go 没有 parametric type,Go 的拥护者们就说“我看不到这有什么用处”,就像一些非洲土著跟你说“我看不到鞋子有什么用处”一样。他们利用人们对 Java 的繁复和设计模式的仇恨,让你抛弃了它里面的少数好东西。其实 Java generics 不是 Java 首先有的。它的主要设计者其实包括 Haskell 的设计者之一 Philip Wadler。这种 parametric type 很早就出现在 ML,Haskell 等语言里面,是非常有用的东西。

每当受到批评,Go 的拥护者们就托词说,Go 是“系统语言”(systems language)。这里潜在的前提就是,认为 Unix 就是唯一的“系统”,而 C 就是在 Go 以前唯一的“系统语言”,好像其他语言就写不出所谓的“系统”似的。而事实是,在 C 诞生十年以前,人们就已经在用 Algol 60 这样的高级语言来写操作系统了。由于先天不足却又大力推广,所以 Go 的很多缺陷基本已经没法修补了。这样的语言一旦流行起来就会像 Unix 一样,成为一个无休止的补丁堆。如果像 Java 或者 Haskell 这样的语言还值得批评的话,对 Go 语言的设计者我只能说,去补补课吧。

Cornell

可是权威和名气的威力还是很大的。虽然 Knuth 在我心目中的位置不再处于“垄断地位”,世界上可以占据我心里那个位置的人和事物还很多。在离开清华之后我申请了美国的大学。也许是天意也许是巧合,只有两所大学给了我 offer:Cornell 和 Indiana,而我竟然先后到了这两所大学就读。

说实话,Indiana 给了我比 Cornell 更好的 offer。Cornell 给我的是一个 TA 的半工读职位,而 Indiana 给我的是一个不需要工作白拿钱的 fellowship。说实话我从来没有搞明白 Cornell 这样的“牛校”怎么会给我这样的人 offer,GPA 一般,paper 很菜,而 Indiana 却是真正在乎我的。Indiana 的 fellowship 来自 GEB 的作者 Doug Hofstadter。他从 email 了解到我的处境和我渴求真知的愿望之后,毅然决定给我,一个素不相识的人写推荐信。后来我才发现那 fellowship 的资金也是他提供的。

可是 Indiana 和 Hofstadter 的名气哪里能跟 Cornell 的号称 “CS前五” 相比啊?Indiana 的 offer 晚来了几天。当收到 Indiana 的 offer 时,我已经接受了 Cornell。Hofstadter 很惊讶也很失望,因为他以为我一定会做他的学生,可是听说我接受了 Cornell 的 offer,他也不知道该怎么办。我只隐约的记得他告诉我,学校的排名并不是最重要的东西……

名气和权威的力量是如此之大,它让我不去选择真正欣赏我并且能给我真知的人。有时候回想起来,我当时真的是在寻找真知吗?我明白什么叫做真知吗?

Cornell 给了我什么呢?到现在想起来,它给我的东西恐怕只有教训,很多的教训。TA 的工作可不是那么好做的,基本就是苦力,你甚至会怀疑他们录取你就是为了利用你的廉价劳动力。我第一次做 TA 就是一个 200 多人在阶梯教室上的大课,教最基本的 Java 编程。虽然有好几个 TA,但任务还是很繁重。讲课的人不是教授,而是专职的讲师。这种讲师一般得靠本科生的好评来谋生,所以虽然在学术上没什么真本事,对学生真可谓是点头哈腰,服务周到。这就苦了各位 TA 了,作业要你设计,还要设计得巧妙,要准备好标准答案,之后还要批作业,批得你头脑麻木,考试要监考,之后还要批试卷。每周还得抽好几个小时来做 office hour,给学生答疑。然后你还有自己要上的课,自己的作业,自己的考试。每当考试的时候都很紧张,因为你得准备自己的考试,还要为学生的考试多做很多工作。

如果真的学到了东西,这么辛苦也许还值得,可是那些教授真的是想教会你吗?有人打了个比方,说 Cornell 说要教你游泳,就把你推到水池里,任你自己扑腾。当你就要扑腾上岸时,他在你头上用榔头一砸,然后继续等你上岸。当你再次快要扑腾上岸时,他又举起一块大石头扔到你头上,这样你就可以死了,可是 Cornell 仍然等着你游上岸…… 这就是对我在 Cornell 的经历的非常确切的比喻。

我在一篇老的博文里面提到过,Cornell 的学生,包括博士生,一上课就抄笔记,一天到晚都在赶作业。可其实 Cornell 不只是爱抄笔记的学生的天堂,而且是崇拜权威者的天堂。即使你不是那么的崇拜权威,你不可避免的会被一群像朝圣者一样的人围在中间,在你耳边谈论某某人多么多么的牛。不管你向同学打听哪一个教授,得到的回答总是:“哇,他很牛的!” 然后你就去上了他的几节课,觉得不咋的嘛,可是人家就说那是因为你不理解他的价值。这种气氛我好像在另一个地方感觉到过呢?啊对了,那是在 Google。这样的气氛也许并不是偶然,Cornell 的大部分 PhD 同学当时的最大愿望,就是毕业后能去 Google 工作。当然,后来 Facebook 上升成为了他们的首选。值得一提的是,Indiana 其实是更有个性的地方。我在 Indiana 的同学们一般都把去 Google 工作作为最后的选择之一。有一次一个刚来不久的学生问,如何才能进入 Google 工作?有个老教授说,那个容易,Google 招收任何能做出他们题目的人!

Cornell 的研究可以用“与时俱进”来形容,什么热门搞什么。当时 Facebook 和社交网络正在“崛起”,所以系里最热门的一个教授就是研究社交网络的。我去听过他几堂课,他用最容易的图论算法分析一些社交网络数据,然后得出一些“理论”。其中好些结论实在太显然了,我觉得街上的卖菜大妈都能猜到,还不如研究星际争霸来得有意思点。可是 Facebook 名气之大,跟着这位教授必然有出路啦,再加上有人在耳边煽风点火,所以有好多的学生为做他的 PhD 挤破了头皮,被刷下来的就只好另投门路了。每次新来一个教授都会被吹捧上天,说是多么多么的聪明,甚至称为天才。然后就有一群的人去上他的课,试图做他的学生。结果人家每节课都是背对学生面朝黑板,喃喃自语,写下一堆堆的公式和证明,一堂课总共就没回过几次头。下面的人当然是狂抄笔记,有的人甚至带着录音笔,生怕漏掉一句话。上这样的课还不如干脆把板书打印出来让大家自己回家看。人多了竞争也就难免了。上课的同学们就开始勾心斗角,三国演义的战术都拿出来了。作业做不出来就来找你讨论,等你想讨论了就说自己也没做出来。没听懂偏要故作点头状,显得听懂了,让你觉得有压力。自己越是喜欢的教授就越是说他不咋的,扯淡,然后就自己去跟他。自己不喜欢的教授就告诉你他真是厉害啊,只可惜人家不要我。直到两年后我离开 Cornell 之前,还有好些同学因为没找到教授而焦头烂额。因为两年内没有找到导师的 PhD 学生,基本上等于必须退学。

当我离开 Cornell 之后,有一位国内的学生给我发 email 套磁(从系里主页上找到我的地址),问我 Cornell 情况如何。我告诉他我都已经走人了,并且告诉了他我的感觉,一天到晚抄笔记赶作业之类的。然后又问我一个刚毕业的 PhD 的情况,我说他水平不咋的,博士论文我看过了,很扯淡,解决一个根本不存在的问题。他对我说的话有点惊讶,但还是将信将疑。为了确保万无一失,他在 visiting day 的时候专程去 Cornell 考察了一下。回去又给我 email,说见到好多牛人啊,大开眼界,哪里像你说的那么不堪。还说跟那位 PhD 的导师谈过话,真是世界级的牛人那,他的博士论文也是世界一流的。我就无话可说了,仁者见仁,智者见智,随他去吧,哎。

结果两年之后,我又收到这位同学的 email,说他在 Cornell 还没找到导师,走投无路了,问我有没有办法转学。

图灵奖

说到这里应该有人会问这个问题,我是不是也属于那种没找到导师走投无路的人。答案是,对的,我确实没有在 Cornell 找到可以做我导师的人。然后我就猜到有人会说,就知道王垠水平不行嘛,没搞定导师,被迫退学,哈哈!可是事情其实没他们想象的那么简单。作为一个 PhD 学生,不仅必须精通学术,而且要懂得政治和行情。哦错了,其实不精通学术也行的,但是一定要懂得政治和行情!可是由于学生之间的窝里斗,他们之间的信息互通程度,是没法和教授之间的信息互通程度相比的。这就造成了“学生阶级”在这场信息战上的劣势,总是被动的被教授挑选,而不能有效地挑选适合自己的教授。

进入 Cornell 之后我上了一门程序语言的课,就开始对这些东西入迷。可是由于“与时俱进”,Cornell 的研究方向并不是那么平衡的发展的,其实是很畸形的发展。程序语言领域的专家们早已因为受到忽视而转移阵地,剩下一群用纸和笔做扯淡理论的。说实话,在历史上程序语言方向曾经是 Cornell 的强项,出现了一些很厉害的成果。可是当我在 Cornell 的时候,只剩下两个名不见经传的教员,一个助理教授,一个副教授。其实 Robert Constable 也在那里,可惜的是他做了 dean 之后已经没空理学生了,以至于我两年之后都不知道这个人的存在。我当时也不知道 Cornell 有过这段历史,看不到它的研究重心的移动趋势。

我不喜欢那个副教授搞的项目,大部分是在 Java 上面加上一些函数式语言早就有的功能。可是人家做的是热门语言,所以拉得到资金,备受系里亲睐,他的学生们也比较趾高气昂。初次见面的时候,我跟他的一个学生说了我的一个想法,他说:“你那也能叫研究吗?待会儿我给你看看什么是真正的研究!” 其实那只是我的一个微不足道的想法,我也没说那是研究啊。只是随便聊一下而已就这么激动 -_- 何况你们那些 Java 的东西能算是研究?我是不可能跟那样的人合作的,所以我就跟那个助理教授做了一点静态分析的项目。当然我们分析的也不是什么好东西,是用 Fortran 写的 MPI 程序。不过说实话,那个助理教授其实挺有点真知灼见,他有几句话现在仍然在指引我,防止我误入歧途。其中一句话是针对我对 π-calculus 的盲目崇拜 说的:“那些理论其实不管用的。最好是针对自己的问题,自己动脑筋想。” 他也是很谦虚很善良的人,可是好人不一定有好报的。后来他没有拿到 tenure 职位,不得不离开 Cornell 加入了工业界,而我就失去了最后一个有可能在程序语言方向做我的导师的人。

没办法,我就开始探索其它相关领域的教授,比如做数据库的,做系统的,看他们对相关的语言设计是否感兴趣。可惜他们都不感兴趣,而且告诉我程序语言领域太狭窄了。我当时还将信将疑,甚至附和他们的说法,可是现在我断定他们都是一知半解胡说八道。如果这些人虚心向程序语言专家请教,现在数据库和操作系统的设计也不会那么垃圾,关系式,SQL,NoSQL,…… 一个比一个扯淡。没有办法,我就开始探索其他的方向,开始了解图形学和数值分析等东西,进展很不错。可是终究我还是发现,我不喜欢图形学和数值分析所用的语言。我想制造出更好的程序语言来解决这些问题。可是跟教授们谈这些想法的时候就感觉是在对牛弹琴,他们完全不能理解。后来我发现,教授们貌似不喜欢有自己想法的学生,他们更希望找到愿意“打下手”的学生,帮助实现他们自己的想法。

这就让我走到了跟那位向我打听 Cornell 情况的同学差不多的局面,真是心里有许多的苦却没有人可以理解。这时候我想到了系里的一些德高望重的教授,比如得过图灵奖的人,也许这些顶级的大牛会给我指出方向。于是我就联系到一位图灵奖得主,说想找他聊聊。我说我感兴趣的东西 Cornell 貌似并不重视和发展。Cornell 的校训是“any person, any study”,而我想 study 的东西却得不到支持。最后我谈了一下我对 Cornell 的总体感受。我说我觉得大家上课死记硬背,不是很 intellectual,我不是很确定学术界是否还保留有它原来的对智慧和真知的向往。

我很诚恳的告诉了他这些,只是希望得到一些建议。结果他不但没有理解任何一点,而且立马开始用质问的语气问我,你成绩怎么样?考试都通过了没有?哎,说白了就是想搞清楚你是不是成绩不好没人要。怎么就跟高中教导主任一样。于是乎那次谈话就这样不了了之。可是没有想到,这次谈话就造成了我最后的离别。在学生们互相之间勾心斗角,不通信息的同时,系里的教授们其实背后都是“通气”的。他们根本不懂得如何教学,就知道拿作业和考试往学生头上砸,幸存下来的就各自挑去做徒弟,挨不住的就打发掉。这算盘打得真是妙啊。我也不知道他们是什么机制,每个学生对哪些教授感兴趣,表现如何,他们貌似都了如指掌,貌似背后有个什么情报网。然后系里的教授们不知道怎么的,仿佛就都知道有这样一个不知趣的学生,居然敢说学术界的坏话!

大地震前夕的天空总是异常的美。我竟然在过道里看到那位图灵奖教授对我点头致意并且微笑,以前做 TA 时把我呼来唤去还横竖不满意的教授也对我笑脸相迎。我仿佛觉得那一席话打动了那位德高望重的教授,再加上在图形学和数值计算的扎实进展,也许我的学术生涯有了转机。可是,我那一次真正的领悟了什么叫做所谓的“笑里藏刀”。

由于那个学期上的图形学还有矩阵计算的课成绩都不错,我心想应该能找这两门课的授课教授的其中一个做导师吧。再加上那些貌似友好的笑容…… 所以没想很多,居然过了一个非常快乐的寒假。没有任何前兆,没有任何直接的通知(email,电话),一封纸信不知道是什么时候默默地进到了我在系里的“信箱”—一个我基本上从来不看的,系里用来塞广告信息的信夹子里,直到下一个学期开始的时候(2月份)我才发现。信是系主任写的,大概就是说,由于你的表现,我们觉得 Cornell 不是适合你的地方……

说得对,我也觉得 Cornell 不适合我。我本来就有想走的意思,可我一般呆在一个地方就懒得动。如果你们早一点告诉我这个,比如12月以前,我还可以申请转学到其它学校。可是都 2 月份了才收到这样的东西,Cornell 啊 Cornell,你让我现在怎么办?我想我可以说你不仁不义吧?

在这个万分窘迫的时候,我想起了曾经关心过我却又很失望的 Hofstadter。我告诉他我在 Cornell 很不开心,我很想研究程序语言,可是 Cornell 不理解也不在乎这个领域。他回信说,没有关系,你能找到自己喜欢的东西就应该去追寻它。Indiana 的 Dan Friedman 正好是做程序语言的,你可以联系他,就说是我介绍你去的。

于是给 Friedman 发了 email,很快得到了回信说:“Yin,两年前我们都看过你的材料,我们觉得你是非常出众的学生,可惜你最后没有选择我们。你要明白,人生最重要的事情不是名利,而是找到你愿意合作的人。你的材料都还在我们这里。现在招生已经快结束了,但是我会把你的材料提交给招生委员会,让他们破例再次考虑你的申请。” 我和 Dan Friedman 的故事就从这里开始了。

我在 Cornell 的遭遇貌似不可告人的耻辱和秘密,然而我今天却可以把它公之于世,因为 Cornell 不再有任何资格来评价我。依靠自己的努力和 Indiana 的老师们的培养,我的水平已经超越了 Cornell 计算机系的大部分教授。现在我觉得自己就像那个到 Cornell 学“游泳精髓”人,本来就是会游泳的,可是每到岸边 Cornell 就搬起大石头来砸我,还说我不会游。于是我钻到水底下钻了一个洞,把水放干。

由于曾经与多位图灵奖得主发生不大愉快的遭遇,再加上在自己的研究中多次受到其它图灵奖得主的理论的误导,而且许多位图灵奖得主最主要的贡献仍然在给软件行业带来混乱,图灵奖这个被许多计算机学生膜拜的神物,其实在我心里已经没有任何效力了。很多人可能对此难以想象,可是对图灵奖是这种态度的不只我一个人。我认识的几乎所有程序语言专家几乎都不拿图灵奖当回事,而且其中很多人甚至不拿图灵本人当回事,觉得他设计了一些非常丑陋的东西。虽然我现在觉得图灵的研究成果确实有一定价值,但由于上面的原因,拿图灵奖来开玩笑还是成为了我的家常便饭。我甚至觉得 ACM 应该停发这个奖,因为它是一种非常虚幻和政治的东西。每当人们谈起这些“大奖”煞有介事的时候,就让我看到了他们的愚昧。

常青藤联盟和“世界一流大学”

我在 Cornell 的经历应该不是偶然,不是因为我比较特殊。跟我同时进入 Cornell 的博士生有好几个没有拿学位就离开了。其中有一个是非常聪明的少年班,18岁就读 PhD 了,我根本听不懂的理论课他还能拿A。可是四年后他退学去了 Facebook,说真是太难毕业了,神马都是扯淡。有些本科生也告诉我类似的经历,说被一个叫做“笑面虎”的教授“整了”。Cornell 的自杀率居美国大学前列。离开以后的有一天,忽然看到新闻报道说一周之类有三个 Cornell 学生从瀑布旁边的那座桥跳下去,结果派了警察在桥上日夜巡逻。我觉得自己在 Cornell 所感受到的压力确实超乎想象,是有可能把人逼上绝路的。现在回想起来真是可笑,因为下意识里在乎权威和名气,我给予了一群根本没有资格来教育我的人莫大的权力,让他们可以向我施加无端的压力。

应该指出,这种现象应该不是 Cornell 所特有的。我对清华,还有 Princeton,Harvard,MIT,Stanford,Berkeley,CMU 等学校的学生都有了解。这些所谓的“世界一流大学”或者“世界一流大学 wannabee”差不多都是类似的气氛。你冲着它们的名气和“关系网”挤破了头皮进去,然后就每天有人在你耳边对其它人感叹:哇,他好牛啊!发了好多 paper,还得了XX奖。跟参加传销大会似的,让你怀疑这些人还有没有自尊。然后就是填鸭式的教育,无止境的作业和考试,让你感觉他们不是在“教育”你,而是在“筛选”你。这种筛选总是筛掉最差的,但也筛掉最好的。因为最好的学生能意识到你在干什么,他们不给你筛选他们的机会。一旦发现其实没学到东西,中途就辍学出去创业了。所以剩下来的就是最一般的,循规蹈矩听话的。在这样的环境里,你感觉不到真正的智慧和真知的存在。GRE 考试所鼓吹的什么“批判性思维”(critical thinking )在美国大学里其实是相当缺乏的。学生们只不过是在被培训成为某些其他人的工具,他们具有固定的思维定势,像是一个模子倒出来的。他们不是真正的创造者和开拓者。

人们在这些大学里的时候都是差不多感受的,可是一旦他们出来了,就会对此绝口不提。自己身上挂着这些学校的镀金牌子,怎么能砸了自己的品牌,长别人的威风?所以每当我批判 Cornell 就有些以前的同学一脸的着急相,好像自己没有吃过那苦头一样。

程序语言专家

虽然我在 Indiana 得到了思想的自由,但这种自由其实是以孤独为代价的。我并不是一个自高自大不合群的人,但是我不喜欢跟一群像追星族一样的人在一起。应该说在 Indiana 的日子里,权威主义的影子也是经常出现的。Indiana 学生们的权威比较特殊一点,不然就是 Dan Friedman,不然就是 Kent Dybvig。Friedman 的身边总是围绕着一群自认为是天才的本科生,喜欢拍他的马屁,喜欢在人面前炫耀。博士生们开始时貌似还比较酷,可是后来发现其实也有很多类似现象,急于表现自己,越是研究能力弱的人越是爱表现。所以你就发现有人开头为了混进这个圈子拍你的马屁,过了两年就开始自高自大,而且经常想这样来压倒你:“Kent 说过……”我很尊敬 Dan 和 Kent,但我其实在很多方面已经超越了他们。我看到他们的一些思维方式并不是那么的正确,我也从来不引用他们的话作为理论依据。对权威的崇拜其实显示了一个人心理的弱小。如果你对自己有信心,有自己的想法和判断力,又何必抬出个名人来压制别人呢?

在我自己心里毫无疑问的是,我是 Indiana 最厉害的程序语言(PL)学生。由于我不断地动手尝试新的想法,所以几乎没有任何其他人的研究逃脱过我的探索。我从来不记录自己的半成品和失败(因为太多了),而且我对自己的标准异常的高,所以我经常看到有人做演讲或者写论文,里面其实是我很久以前尝试过又抛弃了的想法。有时候我去听别人的演讲,就会立即看出破绽,问一些演讲者答不出来的问题。其实很多时候我只是怀疑自己,我试图给那些想法再一次的机会来证明它们的价值,而且问得相当委婉,但那样的问题仍然是不受欢迎的,所以同学们甚至一些助理教授看到我在场都是心惊胆战的。吃饭的时候我也不喜欢旁边的人讨论问题,因为他们经常显示出对理论提出者的膜拜心理,而且煞有介事,可惜那些经常是我早就知道不管用的理论。他们有时候其实也知道那些是扯淡的,但却又怕我捅破这窗户纸,所以就像鸵鸟一样把头埋在沙子下面。

我也想合群一点,但是屡试不爽,所以后来我就基本是孤立的做自己的研究了。最开头是不得已,但后来就越来越喜欢独自一人。这是不可避免的,因为创造力和孤独几乎是双胞胎。因为免去了跟人讨论的时间,我有了大把的时间来做自己的探索。然后我才发现当年期望的那种 common room 其实没什么用,因为那里根本不会有人理解你在说什么。现在即使有这样的地方我也不会去了。

我从一开始进入 Indiana 就没想过要拿博士学位,我只是在玩弄这个系统以达到我求知的目的。所以除非危及到我的存在,我把学校对学位的各种要求都抛到了九霄云外。给教授做 RA 几乎总是被要求研究各种毫无前途的东西,与我自己的思考相冲突,所以我后来干脆都做 TA 了。虽然累点,但不怎么费脑力。其结果是,在短短的一两年时间之内,我利用自己抠出来的时间,独自摸索出了这个领域大部分的理论。我经常不看书不看论文,在一个星期之内解决别人十多年才完成的研究。让人惊讶的应该不是我有多么聪明,而是这些研究者们十年来到底在干什么。我从来不认为自己比别人聪明,我只是觉得很多人的脑子被禁锢了而已。我有非常简单的头脑,我看不懂复杂的公式,听不懂高深的术语。可正是因为这一点,让我脱离了已有理论的困扰。

可以说,这个领域在过去一个多世纪的研究,很少有逃脱过我的洞察力和直觉的。这些研究最早可以追溯到 1870 年代。我一般很少看论文,因为自己想清楚一个问题其实花不了那么多时间的。看别人的论文一般都枯燥乏味,所以与其花那么多时间读论文还不如自己思考。当我看论文的时候,一般是想搞清楚自己琢磨出来的问题有没有人已经研究过了,所以很多论文只需要扫一下就够了。我看到一个东西一般很快就会知道它到底会不会管用。我经常发现一些被认为很艰深的理论其实是在解决根本不存在的问题,甚至是在制造问题,而真正的问题却没有得到有效的解决。很多问题其实是权威的阴影造成的,它让人们不敢否认这些大牛思想的价值,不敢揭穿它们,抛弃它们,甚至想让自己寄生在它们上面,所以很多的时间花在了解决一些历史遗留问题,而不是真正的问题。这就是为什么我的英文 blog 标题叫做“Surely I Am Joking”,因为它记录了一些我认为根本不存在,或者是人为造成的问题。

逻辑学家

批评 PL 领域的问题并不意味着其它领域就好一些。恰恰相反,我认为做系统和数据库的领域有更大的权威崇拜和扯淡的成分。有时候程序语言专家看起来很明显的问题,做数据库和操作系统的人却看不到,扯来扯去扯不清楚,还自以为是的认为 PL 的东西他们都懂。

程序语言的理论是计算机科学的精髓所在,可是程序语言专家有他们自己的问题:他们膜拜逻辑学家。几乎每一篇 PL 领域的论文,至少有一页纸里面排列着天罡北斗阵一样的稀奇古怪的逻辑符号,而它们表示的其实不过是一些可以用程序语言轻松做出来的解释器和数据结构。有人(比如 Kent Dybivg)早就发现了这个规律,所以写了一些工具,可以把程序语言自动转换成 LaTeX 格式的逻辑公式,用以对付论文的写作。

有人觉得那些公式有“数学的美感”,可是它们其实是挺有毛病的设计。如果你看看现代逻辑学鼻祖 Gottlob Frege 的原著,就会发现其实最早的时候逻辑学不是用公式表示的。Frege 那篇开创性的论文 Begriffsschrift 里面全都是像电路图一样的图片,只有 20 多页,而且非常容易读懂。不知道是哪一个后辈把电路图改成了一些稀奇古怪的符号。其实他的目的是用符号来表示那些电路图,结果到后来徒孙们以为那些符号就是祖传秘籍的精髓,忘记了那些符号背后的电路图,所以导致了今天的混乱局面。看完了 Frege 的论文,我再一次领悟到了之前那句话:跟真正的大师学习,而不是跟他们的徒弟。

ACM SIGPLAN 的主席 Philip Wadler 有一次写了一篇论文介绍 Curry-Howard corresponce,里面提到,好的点子逻辑学家总是比我们先想到。可是他却没有发现,其实程序语言的能力已经大大超越了数理逻辑,数理逻辑那些公式里面的 bug 其实不少。因为逻辑学家们不用机器帮助进行推理,有些问题搞了一百多年都搞不清楚是怎么回事,然后就弄出一些特殊情况和补丁来。有了一堆逻辑“定理”,却又不能确信它们是正确的,而且存在悖论一类无厘头的东西,所以又掰出一些 model theory 之类的东西来验证它们的正确性。逻辑学家们折腾了一百多年都是在折腾类似的事情,却没怀疑过老祖宗的设计。我之前提到的 Hindley-Milner 系统的问题,很大部分原因就在于它所使用的逻辑里面其实有一个根本性的误解。简言之,就是把全称量词 ∀ 随意乱放,导致输入与输出关系混乱。这也就是我为什么不喜欢 Haskell 和 OCaml 的最主要原因。

现在最热门的逻辑学家莫过于 Per Martin-Löf。他的类型理论 Martin-Löf Type Theory 被很多 PL 人奉为神圣。我一直没有搞清楚这个类型理论有什么特别,直到有一天我把 Martin-Löf 1980 年的那篇论文(其实是演讲稿)拿出来看了一遍。然后我发现他通篇本质上就是在讲一个 partial evaluator 要怎么写,而我早就自己写过 partial evaluator。其实并不是特别神奇的东西,只需要在普通解释器里面改一两行代码就行,可是有人(比如 Neil Jones)却为此写出了 400 多页的书和大量的论文。

之前提到的 Curry-Howard corresponce 也被很多人奉为神圣,它来自数学家 Haskell Curry 和逻辑学家 W.A. Howard 的一些早期发现。他们发现有些程序和定理的证明之间有对应的关系。然后就有 PL 专家开始走火入魔,说“程序就是证明,程序的类型就是定理”。可是他们却没有发现这个说法没法解释操作系统这种程序,因为它被设计为永远不停地运行,所以不能满足一个证明所具有的基本特征。而且很多程序被设计出来根本就不是要证明什么定理,它们是被设计来帮人做事情的。所以我觉得“程序就是证明”是很牵强附会的说法,你不能因为有的程序可以用来证明数学定理,就认为所有的程序都是某个定理的证明啊!把那句话反过来,说成“证明就是程序”还差不多。

但从以上的发现,我很高兴的看到了自己作为一个程序员的价值。很多人瞧不起程序员,把他们蔑称为“码农”,可是程序如果写好了,其实比起那些高深的逻辑学家和哲学家还要强,因为程序语言其实比数理逻辑还要强。有一位数学家说得好:为了真正深入的理解一个东西,你应该把它写成程序。还有人说,编程只是一门失传的艺术的别名,这门艺术叫做“思考”。我觉得很在理。

再见了,权威们

几经颠簸的求学生涯,让我获得了异常强大的力量。我的力量不仅来自于老师们的教诲,而且在于我自己不懈的追求,因为机会只亲睐有准备的头脑。

曾经 Knuth 是我心中唯一的权威,后来我又屈服于 Cornell 和常青藤联盟的权威和名气。在一而再再而三的上当受骗之后,我终于把所有的权威们从我的脑子里轰了下去。也许有时候轰得太猛烈了一些,但总的说来是有好处的。不再是我心目中的权威并不等于我鄙视他们或者不尊敬他们。我只是获得了不用膜拜他们,不用跟一群人瞎起哄的自由。我不尊敬的人都是一些自视过高的人或者他们的跟屁虫。一般来说,权威们在我的脑子里失去的只是他们在很多其他人脑子里的那种被膜拜的地位,那种你可以用“XX人说过……”来压倒理性分析的地位。现在他们在我心目中是一群普通的,由蛋白质形成的生物,有好心肠或者坏心眼的,高傲,谦虚或者虚伪的人。我不会自讨苦吃,他们的想法如果真的好,我当然要拿来用,但是没有任何人的东西我是不加批判全盘接受的。我深深地知道接受错误想法的危害性,所以我也希望大家都具有批判的思维,不要盲目的接受我说的话。我不喜欢“大神”或者“牛人”这种称呼,我也反感那种自称膜拜我的人,因为正是这种人让权威主义现在横行于世。

美国的权威主义胜于欧洲,但也不是每个人都那么的崇拜权威,而中国才是权威主义的重灾区。像“图灵奖得主XX”这样的称呼,恐怕只有在中国才见得到。所以我希望国内的同学们,不要盲目的崇拜国外的所谓“大师”,“牛校”或者“牛公司”。祝你们早日消灭掉心里的各种权威以及对他们的畏惧心理,认识到自己的价值和力量。

后记(关于 IU)

有些人看了我的文章介绍在 IU 的经历,告诉我他们申请了 IU。我觉得有必要免责声明一下:我没想到,也不希望有人因为我的文章而去 IU,YMMV (your mileage may vary)。由于我有所准备,所以对于 Friedman 的教学如鱼得水。很多同学也说学到很多,可是有一些其他人告诉我他们觉得 Friedman 的课他们听起来很吃力,只能说是勉强过关。而且我只介绍了 IU 好的方面,却把不大好的地方一笔带过了。我在 IU 也有很艰难的时候。现在的情况是 Kent Dybvig 已经离开了 IU,加入了 Cisco。他的公司 Cadence Research Systems 和 Chez Scheme 也并入了 Cisco。Dan Friedman 由于年纪原因说不准还带不带学生。最近引进了一些貌似不错的助理教授,但是我跟他们都不熟。我的经验是助理教授一般都会为了研究资金,为了升为正教授而做一些身不由己的事情。其他的 CS 方向我都说不准 IU 是什么水平,所以还请同学们自己斟酌。我可以毫无疑问的一点是,IU 有非常美丽的校园,大大的超过清华,北大,Cornell,Stanford,MIT。

04 Jan 06:54

Avoid Resize()?

by Thomas Young

(Also posted to upcoder.com, number 4 in a series of posts about Vectors and Vector based containers.)

This post is essentially a response to feedback to this previous post.

In that post I talked about a change we made to the initialisation semantics for PathEngine’s custom vector class, and described a specific use case where this can make a difference, with that use case involving calls to the vector resize() method.

In the comments for that post, Herb Sutter says:

Thomas, I think the point is that “reserve + push_back/emplace_back” is the recommended style and the one people do use. Really resize() is not used often in the code I’ve seen; you use resize() only when you want that many extra default-constructed elements, since that’s what it’s for.

In this post I’ll be looking into our use case in a bit more detail, and in particular whether or not a resize() call is actually required.

Avoiding unnecessary initialisation

As a result of writing reader feedback to previous posts (including Herb’s comments) I’ve realised that I’ve got a bit of a bad habit of sizing vectors directly, either in the vector constructor, or with calls to vector resize(), where in many cases it’s actually more efficient (or better coding style) to use reserve().

We can see an example of this in the first post in this series, where I posted some code that constructs a vector with a fixed size and then immediately clears the vector, as a way of avoiding memory allocations:

    std::vector<cFace> openQueueBuffer(20);
    openQueueBuffer.clear();

Memory allocations are bad news for performance, and avoiding unecessary allocations should be the first priority in many situations, but there’s also a secondary semantic issue here related to element initialisation and it’s better general purpose vector code to use the resize() method for this, as follows:

    std::vector<cFace> openQueueBuffer;
    openQueueBuffer.reserve(20);

The difference is that the first version asks for initialisation of 20 cFace objects (with what actually happens depending on the way cFace constructors are set up), whereas the second version doesn’t require any initialisation (independantly of how cFace is defined).

Note that this issue doesn’t just apply to vector construction – exactly the same issue applies to vector resize() operations where vector size is increasing.

In PathEngine, because cFace is a POD class, but also because of the way initialisation is implemented in PathEngine’s vector class, both versions actually work out as doing the same thing, i.e. no initialisation is performed in either case. But could we change the PathEngine source code to avoid calling resize() and so avoid the need for non-standard vector initialisation semantics?

Resize benchmark

In my previous post I posted the following minimal benchmark to shows how initialisation semantics can make a difference to timings.

template <class tVector> static int
ResizeBenchmark()
{
    int sum = 0;
    for(int i = 0; i != 400; ++i)
    {
        tVector v;
        v.resize(100000);
        v.back() += 1;
        sum += v.back();
    }
    return sum;
}

In the comments Herb points out that we shouldn’t actually use code like this in many situations, but rather something like the following (avoiding the resize call and resulting element initialisation):

template <class tVector>
int RealResizeBenchmark()
{
    int sum = 0;
    for(int i = 0; i != 400; ++i)
    {
        tVector v;
        v.reserve(100000);

        for( int j = 0; j <100000; ++j )
            v.emplace_back( GetNextInt() );
        // or replace above loop with "v.assign( src, src+srclen );"
        // if your cVector code would do a bulk memcpy

        v.back() += 1;
        sum += v.back();
    }
    return sum;
}

Lets look at the buffer copy version, specifically (i.e. with the call to v.assign()), and set up an updated version of our benchmark to use this construction method.

We’ll use std::vector to set up some source data, initially, for the buffer copy:

    int sourceDataLength = 100000;
    std::vector<int> source;
    source.reserve(sourceDataLength);
    for(int i = 0; i != sourceDataLength; ++i)
    {
        source.push_back(i);
    }

And then the benchmark can be rewritten as follows:

template <class tVector> static int
ResizeBenchmark(const int* sourceData, int sourceDataLength)
{
    int sum = 0;
    for(int i = 0; i != 1000; ++i)
    {
        tVector v;
        v.reserve(sourceDataLength);
        v.assign(sourceData, sourceData + sourceDataLength);
        sum += v.back();
    }
    return sum;
}

This could be written more concisely with direct construction from iterators but reserve and assign are closer to what we would do in our original use case where we’re actually reusing an existing vector.

There’s a problem, however, when we try and apply this benchmark to PathEngine’s cVector. cVector only provides a subset of the std::vector interface, and doesn’t provide an assign() method, or construction from iterators, (illustrating one of the potential disadvantages of rolling your own vector, btw!), so we end up putting resize() back in for the cVector version of the benchmark:

template <class tVector> static int
ResizeBenchmark_NoAssign(const int* sourceData, int sourceDataLength)
{
    int sum = 0;
    for(int i = 0; i != 1000; ++i)
    {
        tVector v;
        v.resize(sourceDataLength);
        memcpy(&v[0], sourceData, sourceDataLength * sizeof(v[0]));
        sum += v.back();
    }
    return sum;
}

I ran this quickly on my main desktop machine (Linux, Clang 3.0, libstdc++ 6) and got the following results:

container type build time sum
std::vector release 0.0246 seconds 99999000
cVector (default initialisation) release 0.0237 seconds 99999000

I’m not going to go into more benchmarking specifics because (as with the original benchmark) the point is just to show whether or not there is an issue, and the exact timings values obtainer aren’t really very meaningful.

But, yes, with this version of the benchmark (construction as buffer copy), and with std::vector used in this way it’s fairly clear that there’s basically nothing in it, and therefore no longer any advantage to modified initialisation semantics.

But what if..

But what if we want to load this data directly from a file?

With resize(), we can do something like the following (with good old low level file IO):

void
LoadFromFile(const char* fileName, cVector<char>& buffer)
{
    FILE* fp = fopen(fileName, "rb");
    // error checking ommitted for simplicity
    fseek(fp, 0, SEEK_END);
    buffer.resize(ftell(fp));
    if(!buffer.empty())
    {
        fseek(fp, 0, SEEK_SET);
        fread(&buffer.front(), 1, buffer.size(), fp);
    }
    fclose(fp);
}

At first glance this seems a bit harder to rewrite ‘the right way’ (i.e. with reserve() instead of resize()), without doing something like loading each individual element separately, and without explicitly creating a separate buffer to load into. After a short search, however, it turns out we can do this by using stream iterators, as described in this answer on stackoverflow.

Let’s implement another version of LoadFromFile() then, without resize(), as follows:

void
LoadFromFile(const char* fileName, std::vector<char>& buffer)
{
    std::ifstream source(fileName, std::ios::binary);
    std::vector<char> toSwap((std::istreambuf_iterator<char>(source)), std::istreambuf_iterator<char>());
    buffer.swap(toSwap);
}

I used the following code to knock up a quick benchmark for this:

template <class tVector> int
FileSum(const char* fileName)
{
    tVector buffer;
    LoadFromFile(fileName, buffer);
    int sum = 0;
    for(int i = 0; i != buffer.size(); ++i)
    {
        sum += buffer[i];
    }
    return sum;
}

Calling this for a largish file (on my main desktop with Linux, Clang 3.0, libstdc++ 6, release build) gave me the following:

container type Method time
std::vector iterators 0.1231 seconds
std::vector resize+fread 0.0273 seconds
cVector resize+fread 0.0241 seconds

So, on this machine (and build etc.) stream iterators work out to be a lot more expensive than ‘simple’ direct file IO to a buffer.

Note that I threw in a call to the old style file IO version for std::vector as well, for completeness, and it looks like the overhead for extra buffer initialisation in resize() is actually insignificant in comparison to the cost of using these stream iterators.

I guess this could depend on a lot of things, and I haven’t checked this across different machines, different standard library implementations, and so on, and I don’t have any practical experience to draw on with optimising C++ stream iterator use, but this does show quite clearly that it’s nice to have an option for direct low level buffer access for some situations where performance is critical.

Full disclosure

In fact, for portability reasons, the PathEngine runtime doesn’t actually include any direct filesystem accesses like those shown in the code above. Loading from persistence is handled by client side code loading data into a buffer in memory (using the relevant operating system file loading methods) and then passing this buffer into PathEngine load calls.

So we could theoretically switch to using std::vector assign() and get rid of this specific use case for changing vector initialisation semantics.

But there are some other reasons why I prefer not to do this.

Dependencies and low level interfaces

The use case I showed in my last post involved the following methods on a custom PathEngine stream object:

    void openNextElementAsArray_GetSize(tUnsigned32& arraySize);
    void finishReadElementAsArray(char* destinationBuffer);

This is low level, works with raw pointers, and is not really modern c++ coding style, but I actually prefer this in many ways to passing in a std::vector reference or templated iterator arguments. It’s nice to avoid dependencies on a vector class, for example, or the additional complexity of templating, and it’s nice to be able to use these methods with buffers that are not managed as vectors.

Some of PathEngine is written as quite low level code, and I think that one of the great things about the STL is how easy it is to interface STL components with lower level code. Being able to taking pointers into the buffer of an STL vector for low level buffer access is one example of this, but sometimes this kind of low level buffer access implies direct vector resizing, and resizing vectors without element initialisation can then often be desirable.

Working with partially initialised buffers

Another use case we come across sometimes in PathEngine is when we want a buffer to store some kind of data corresponding with a given set of index values, but don’t need to set values for all elements, and we have information available somewhere else which tells us which elements have actually got meaningful values set.

One example could be where we want to form a sequence of elements from a large set of potential elements, and so we set ‘next’ values for each element currently included in our sequence, but we don’t need to initialise next values for elements not included in the sequence at all.

This is perhaps a less common use case, however, and I’m not sure how much difference this actually makes to timings in practice.

Wrapping up

It’s good to know the right way to do things in modern c++. With vectors the right way usually means preferring reserve() over resize(), and this will avoid unnecessary initialisation in many cases (and the need to worry about details of vector initialisation semantics).

In the case of PathEngine however, I quite like being able to mix low and high level code. We still find it useful in some cases to resize() vectors without explicit element initialisation and changing the initialisation semantics in our custom vector class helps to ensure this is as efficient as possible!

** Comments: Please check the existing comment thread for this post before commenting. **

31 Dec 05:49

为何笨蛋有自信

by 游识猷

本文作者:游识猷

tumblr_lywnzbauCQ1qdctn9o1_500
我见过许多对自己的观点充满信心的人,他们非常乐意提供忠告,从养生健康说到职场指南,再到政治局势以及人生哲学风花雪月。至于我自己,我并不是一个特别有自信的人,但我是个还不错的倾听者。于是每次我都会想,听听吧,也许他会说些我本来不知道的东西,如此我就能从中受益——有些时候我确实获益匪浅,但是也有很多时候,我发现面前那个夸夸其谈的家伙不但把无数谬误当成真理,而且已经偏离了最初讨论的主题千万里,一万匹脱缰的野马都难以比拟那奔逸的思维与跳脱的勇气。

在听过许多大错特错的演说后,我一直在思索:那些充满自信的笨蛋,他们的自信到底哪来的?莫非他们真的不知道自己是笨蛋吗?

同时,我也在自省:会不会我有时候也是个笨蛋,而且我自己还全然一无所知?

感谢卡尼曼,他的《思考,快与慢》一书解答了我的大部分疑虑。三个问题的答案如下:一个人知道得少一点,反而更容易自信。许多笨蛋确实不知道自己是笨蛋。以及,包括我在内,人人都常常会成为笨蛋——好消息是,做个笨蛋有助于心理健康。

先从两套系统说起。

很多人都听过所谓“左脑负责理性,右脑负责感性”,这当然是错的,理性和感性都是太过复杂的认知活动,光靠半个大脑搞不定。

不过,每个人确实都有那么两套认知系统,这两套系统并不能精确地对应到人体某个特定区域,不过,它们的职能还真有几分类似“感性”和“理性”。

自主运行,不费脑力,不断感知评估周遭一切的,叫“系统1”。极其费力才能运行,负责解决难题、自我控制、批判审视的,叫“系统2”。 情难自禁,不由自主,脱口而出,解答“心情好吗”“那人可靠不”的,是系统1。深思熟虑,统计演算,推理批判,解答“明年有多少天假期”、“2013×2014=?”的,是系统2。

理论上来说,系统2是最高决策中心,它可以驳回系统1的意见,当理性战胜感性,我们就有了“不感情用事”的时刻。一开机就自动后台运行的系统1不引人注意,需要主动开启然后在前台运行的系统2则醒目异常又功能强大。也正因如此,我们在自我评价时往往会想起系统2的种种光辉事迹,于是每个人对自己的评价都是理性客观不偏不倚符合实际,并且每个人都认为自己很有自知之明。

问题在于,系统2实在太过耗能了。以至于在真正调用系统2的时候,我们瞳孔放大了、心跳加快了、血糖降低了……能连续几小时集中注意力,沉浸于“心流”状态中的是极少数学霸,大多人一动脑就难以克制地想购物刷微博吃甜食上厕所。而且作为一个功耗有上限的普通人类,即使再强迫自己,我们的系统2也只能运转到某一个限度——实验显示,我们一般在瞳孔扩张50%、心跳每分钟增加7拍的时候,就会长出一口气,放弃思考那个可恶的难题。

一岁婴儿就会挑最短路径,我们当然也喜欢省力省心。系统2虽有千好万好,但是一用就超负荷实在痛苦。幸好,我们还有擅长联想、容易发散、可脑补各种因果故事、能根据最近发生过的事和当下的种种线索做出判断和抉择的系统1,虽然它不懂逻辑,没学统计,充满成见……但应付日常生活完全够用,一秒钟内就能从脑补跳到结论,简直是多快好省的典范。

那么,如果出现了非日常的、确实难倒你的问题怎么办?

系统1自有妙计,它会自动绕开这个问题,找到一个更容易的相关问题来作答。倘若“目标问题”是你真正要回答的难题,你最终回答的是替代品——“启发式问题(heuristic question)”。“你最近对生活满意吗”替换成“我现在的心情如何”。“张艺谋该被罚款吗”替换成“我有多赞成/讨厌计划生育”。“除夕应该放假吗”替换成“我有多么渴望除夕放假”……不会被难倒,不必付出巨大努力,你已经有了令自己满意的答案了。

最妙的是,我们还会对这个答案充满信心。

为什么呢?因为自信这种事,取决于“认知放松(Cognitive Ease)”和“前后一致”。

当我们能轻松地想起一个耳熟能详的故事,这个故事的起承转合(在我们看来)非常连贯,各种情节与线索(在我们看来)毫无矛盾,因果逻辑(在我们看来)又自洽又清晰,我们就会对这个故事充满信心。

拉姆斯菲尔德曾说过一段被群嘲但其实有道理的名言:“我们都知道自己知道一些事,有些事我们知道我们已经知道。我们同时知道我们不知道一些事,就是说,有些事我们知道我们不知道。但我们并不知道有些事情我们其实不知道,有些事我们真的不知道我们不知道。”

哪怕缺乏关键线索(但我们不知道我们缺线索)、不懂得相关知识(但我们不知道我们不懂得)、逻辑一塌糊涂(但我们不知道我们没逻辑),只要“成功作答”,多巴胺就会令我们感觉如此良好,良好到我们不会发现自己其实根本不掌握回答“目标问题”所需要的知识储备,而那个信心满满的答案,根本是答非所问。

最臭的臭皮匠最不知道自己是臭皮匠,所谓“达克效应”是也 ,相反,真正优秀的好手反倒容易低估自己,因为他们会错误地假设自己轻而易举就能做到的事情,其他人也能做到 。

心理学家邓宁(David Dunning)说,越是无能的人,越是难以识别出别人的“真功夫”,从而越是可能高估自己的水平,所谓“不知道自己不知道”,直到真正接受相关训练,才可能“知道自己不知道”。义愤填膺声讨“孕妇剖腹产被偷肾”“女童阑尾炎被切子宫”的人大抵不会想到下列问题——从剖腹产切口能拿出肾吗?从阑尾炎切口能切掉子宫吗?我真的具有评估这个问题的医学知识吗?

至于本该在这种时候跳出来挑剔的系统2在干什么呢?正在不假思索地赞同系统1的直觉结论呢。偶尔系统2也会调动注意力和记忆力去搜寻“证据”,但那往往是“先审判,再找证据”, 它会局限于搜寻与已有结论一致的信息,并马马虎虎地调查核对。

比如说,当看到“游识猷是个好人吗?”这个问题的时候,跟我关系良好的人在短时间内就会做出“是”的回答,然后他们的系统2会开始回想有关我的各种美好回忆。至于讨厌我的人,同样在短时间内就会做出“不是”的回答,他们的系统2于是会开始搜寻各种有关我的斑斑劣迹。至于与我最为亲密的人,太多共同经历会涌上心头,有好有坏,有甘有苦,于是反而会觉得这个问题一言难尽。

其实都是系统1先行。

真正的系统2该怎么判断呢?

首先,需要给出“好人”的定义标准。(是的,系统1不在乎歧义,遇到歧义时,“直觉”就会让我们不知不觉地舍弃了同一词语的各种可能含义,只留下一种)。

其次,要如何收集数据来判断“游识猷是否符合这些好人标准”,是让她自己填写调查问卷,还是让她周遭的人填写调查问卷,还是人肉她在网上留下的各种蛛丝马迹以“见微知著”?

最后,当我们收集了一堆数据,这些数据可信吗(比如说,游识猷那家伙有没有收买调查者来作弊)?样本数足够吗?足以认为游识猷是个“统计上显著的好人或坏人”了吗?有没有可能我们仍未掌握对判断起决定性作用的证据,比如那家伙曾经干过一件天大的恶事,然后尽了一切努力去毁灭证据……

看到这里,想必你的系统1已经有了结论:写这篇文章的人不知是好是坏,但应该是个神烦的蛇精病……

因为我们是人类,而人类只对最后的结论有反应。至于结论的信源、获取这些结论的方式什么的,人类一点也不在乎。

虽然拥有系统2,大部分人爱用的还是系统1。系统1会通过许多比现实更简化但更统一的方式来表现这个世界,会用猜测弥补证据的缺失,会把零散的碎片串成一个故事。而一个给人自信的好故事,必须是连贯的,却未必是完整的。都说“兼听则明偏听则暗”,其实我们是“偏听则明兼听则乱”,只听一方的故事,我们反而更有自信下结论“我已掌握了事实真相”,而“兼听”得来的各种罗生门一旦超出了我们的推理能力,只会让我们精疲力尽地放弃追索。

当你看到一个充满自信的人,不要轻易地相信他。当你充满自信,不要满足于貌似正确的答案,对于自己的直觉,也还请常持怀疑态度。

如果你要自诩理性,切切谨记,理性与智商从来都不是一回事。智商衡量的是“你的系统2在做智商测试时能达到怎样的程度”。理性衡量的是“你的系统2有多经常启用”。 高智商不能消除理性错误,就连诺贝尔奖得主鲍林也会非理性地推荐大剂量服用维生素C,那以后的无数研究证明了鲍林所深信不疑的“防癌防感冒”等效应都是子虚乌有,鲍林实实在在地当了一回有自信的笨蛋。

当然,当笨蛋并不全是坏事,人非机器,长期对每个念头保持警惕,时刻摒除各种虚幻的优越感……实在是很累很辛苦的一件事,即使是深知人类各种偏见与谬误的研究者,也难以让自己免受这些影响,就好比即使了解视错觉的各种原理,你还是会照旧看到各种错觉。虽然福尔摩斯教导我们说,“没有数据就形成理论是极大的错误”,然而福尔摩斯本身就不是“幸福快乐”的化身。笨蛋更有自信,更乐观,更不容易抑郁,更愿意冒各种风险……简直是优点多多。卡尼曼也承认,在注意到自己也有各种偏见后,他对自己感到更失望,自信心也多少受到了打击。不过,若能从这种打击里恢复过来,通过发现自己的“笨蛋时刻”,我们就能离犯错更远一些。

关于

本文首发百度知道日报

26 Dec 04:48

生物学视角:如何面对死亡

by Ent

本文作者:Ent

如何面对死亡,是一个大而沉重的话题。宗教给人的慰藉很大程度上是因为它对此能给出回应(对错不论),而颇有一部分哲学也是围绕它展开的。这些是“正统”的角度。不过其他学科有时也能尝试给出非正统的回答——比如生物学。

如果让生物学尝试给出一个回答的话,那么也许会是这样的:

所有的动物都有“生之意愿”——趋向生存和繁殖的本能。倒也不是因为这个本能有多好,只不过缺乏这个本能的生物大抵没法存在而已。

一般来说动物生存本能和死亡回避是一体两面。但不尽然如此。毕竟传播基因才是终极目的,如果在某个特定场合下,死能比生更好地传递基因,结局就是死。打个不太恰当的比方,一道菜做出来之后无论如何精心保存也是不可能永生的,但是它的菜谱却可以代代相传。把菜做得很好吃当然会缩减这道菜的物理实体本身的寿命,但是却大大有助于菜谱的流传。

现实中最好的例子就是繁殖后便死去的个体,比如大马哈鱼。对于它的生活方式来说,一口气把全部能量储备变成后代,自己油尽灯枯,比起细水长流要更有利于基因传承。所以它这么做了。如果大马哈鱼有意识,那么也许它临死的时候是毫无恐惧、心满意足吧。

不然的话为什么文学作品里爱情和死亡这两个主题总是相遇?所谓“不朽的爱情战胜死亡”这个主题虽然滥套,某种意义上的确是事实。

到了人类又有不同了。人类发明了一种新的不朽方式:文化。

虽然伍迪艾伦说“我不希望靠文字来不朽,我希望靠不死来不朽”,但前者终究是一种方式。文化——或者说meme——是一种新的、几乎独立于基因的可以流传的东西。那么它就和基因一样也是(部分)战胜死亡的一种方式。

所以初步的结论是:个体——我——是一个物理实体,它不可能永远存在。只有信息能长存。记录完整的躯体信息不太可能,哪怕只记录心智相关的信息也离我们很远,我们最多只能记录下一个提纲并设法让它流传开来。躯体信息的提纲是基因,心智信息的提纲是文化/meme,一个人如果在死之前能做到这两件事,就已经是所有可能的永生方式中最好的一个了。

但是有一种反对意见:

虽然基因和谜因可以长存,但我能感受到的这个“我”却是必朽的,这难道不才是最重要的吗?是“我”在出生和死去啊,别的那些东西明明都不是“我”。

……真的不是“我”吗?

丹内特讲过一个科幻比喻。假设你想永生。现在人体冷冻技术成熟了,你可以依靠它把自己冰冻到遥远的未来,等到真的奇迹出现。但是,你怎么保证这段时间里没有人打搅你的躯体呢?

有两种策略。第一种是造堡垒,配备全套自动化防御系统、维护系统和能源系统。相对来说这个办法比较简单,缺点是,有些麻烦你的堡垒再坚固也无法抵御,比如小行星撞击……

第二种是造高达,难度更大,但效果也更好。

无论是堡垒还是高达,你都需要给它们编程以应付各种不同环境。因为很多环境其实是你自己无法预料到的,你还需要给它们提供你能搞到手的最好的人工智能系统——堡垒要求可以低一些,高达的AI就得非常复杂了。

有这个想法的肯定不止你一个人。还会有很多别人的堡垒和高达分布在地球上。你的堡垒或高达会时不时和它们互动,所以你还要考虑到外交能力——战争、联盟、背叛和信任都是需要你的代理者进行实时判断的,你除了提供一套好程序底子之外帮不上它的忙。

你甚至还可以考虑一些极端的手段,比如指示你的代理者,一旦发现物质复制技术成熟,就把你的躯体和代理者复制很多份,这样一份毁灭也不至于影响大局。或者指示代理者,一旦有新的自适应进化AI技术,就升级成它,然后依靠进化算法保证AI的水平与时俱进。还可以指示代理者随着技术的进步不断改良代理者自身的机械和武器设计……

好吧,你肯定已经看穿了。这特么根本不是科幻,这就是现实。只不过现实中,堡垒是植物,高达是动物,而那个“你”,其实是你的基因。

那么,到底谁才是真正的“我”呢?如果说现在这个肉身的我才是真我,假如有一天我真的造了一个高达然后在里面沉睡,那这个高达又是谁呢?

生物学上,一般把个体的界限划定在你的细胞和细胞的直接衍生物上。我的皮肤是我的,因为它由我的细胞组成;我的头发也是我的,虽然头发不是细胞但它的颜色和粗细确实很大程度上受到我的细胞控制。换句话说,它是我的表现型。一个个体,就是一套基因组的所有表现型的汇聚。

但是表现型的定义必须这么窄吗?

道金斯举石蝇为例。它的幼虫会用小石子盖房子。我们假设它会盖两种房子,一深一浅,并且发现这个深浅满足孟德尔定律。那么,房子的深浅就是某个基因的表现型。虽然从底层机制上讲,房子的颜色来自小溪里石子的色颜色,而不来自生物自己分泌的黑色素,但这有什么关系呢?

进一步说,虽然石头房子不是生物体的一部分,但是它的确包裹在外面、给予生物体以保护,只不过它是由石头而不是几丁质做的——但这又有什么关系呢?蜘蛛网甚至不是包裹在蜘蛛外面,不能随着蜘蛛的运动而翻转——但这又有什么关系呢?

这就是“延伸的表现型”——表型扩展到了生物个体之外的领域。的确,我的高达并不是我的细胞分泌出来的,也不是由有机物质构成的,但它还是“我”的产物,正如我的血肉之躯一样。它们都是表现型,都是基因(和谜因)影响世界的方式。

所以,个体是一个(很有用的)幻觉,个体和个体之外环境的区分也是一个幻觉。哪怕个体本身消失了,只要它延伸出去的表现型还在,我就还在对世界施加影响,某种意义上就没有真的死去……只是fade away而已。

罗素晚年的时候说,个人的生命应该像一条河,开始很小,到最后没有任何可见的变化就与大海融为一体,毫无痛苦地失去个体生命。这是真的。我们死去的时候确实抛弃了一个表现型汇聚成的躯壳代理人,但是底层的基因和谜因依然存在于新的载体里,而顶层的延伸表现型也依然存在于世界的汪洋大海之中。

最后,一则漫画。

C5AIJmrX8rxE1kaoeTGgxfIhYsbrUw9IEHXzU5Qd4gKIAgAAAA8AAEpQ_600x3555

08 Dec 05:24

Symmetry

by fgiesen
???

fairly cute

As a general rule, when trying to understand or prove something, symmetry is a big help. If something has obvious symmetries, or can be brought into a symmetric form, doing so is usually worth trying. One example of this is my old post on index determination for DXT5/BC3 alpha blocks. But a while ago I ran into something that makes for a really nice example: consider the expression

|a + b| + |a - b|

If these were regular parentheses, this would simply evaluate to 2a, but we’re taking absolute values here, so it’s a bit more complicated than that. However, the expression does look pretty symmetric, so let’s see if we can do something with that. Name and conquer, so let’s define:

f(a,b) := |a + b| + |a - b|

This expression looks fairly symmetric in a and b, so what happens if we switch the two?

f(b,a) = |b + a| + |b - a| = |a + b| + |-(a - b)| = \\ |a + b| + |a - b| = f(a,b)

So it’s indeed symmetric in the two arguments. Another candidate to check is sign flips – we’re taking absolute values here, so we might find something interesting there as well:

f(a,-b) = |a + (-b)| + |a - (-b)| = |a - b| + |a + b| = f(a,b)

And because we already know that f is symmetric in its arguments, this gives us f(-a,b) = f(a,b) for free – bonus! Putting all these together, we now know that

f(a,b) = f(b,a) = f(|a|,|b|) = | |a| + |b| | + | |a| - |b| |

which isn’t earth-shattering but not obvious either. You could prove this directly from the original expression, but I like doing it this way (defining a function f and poking at it) because it’s much easier to see what’s going on.

But we’re not quite done yet: One final thing you can do with absolute values is figure out what needs to happen for the expression to be non-negative and see if you can simplify it further. Now, both |a| and |b| are always non-negative, so in fact we have

f(a,b) = |a| + |b| + | |a| - |b| |.

Now suppose that |a| \ge |b|. In that case, we know the sign of the expression on the right and can simplify further:

f(a,b) = |a| + |b| + (|a| - |b|) = 2 |a|

and similarly, when |b| \ge |a|, we get f(a,b) = 2 |b|. Putting the two together, we arrive at the conclusion

|a + b| + |a - b| = f(a,b) = 2 \max(|a|,|b|)

which I think is fairly cute. (This expression comes up in SATD computations and can be used to save some work in the final step.)


21 Nov 08:33

你的孤独基因懂

by 秦鹏

(文/ David Dobbs)就在21世纪的头几年,基恩·罗宾逊观察蜜蜂体内基因表达的迅速变化之时,加州大学洛杉矶分校的一位研究者——斯蒂夫·科尔(Steve Cole)引起了他的注意。科尔在UCLA的博士后研究方向是免疫及癌症遗传学,在那之后,他成为了第一批把完整基因组的基因表达研究引入社会心理学的研究者之一。他意识到,基因对外来信息不间断的实时反应正是生活的许多变化作用于我们之处。

“我们以为自己的身体是稳定的生物结构,虽生于世间却从根本上与世隔离,以为我们是统一的个体,穿行于世界。但实际上,从保持我们身体运转的分子进程中,我们了解到我们远比自己意识到的易变,其实是世界在我们体内穿行。”科尔解释道。

科尔还说:“每一天随着我们的细胞死去,我们都要替换掉自己身上1%到2%的分子构成。我们在不断地建造和重建新细胞,而基因表达易受影响的性质对此功不可没。这就是细胞的意义:细胞,是将生活经历转化为生物现象的机器。”

孤独的猴子和孤独的人

紧张感或者孤独感能够破坏免疫系统——有时候甚至到了危及生命的程度。图片来源:shutterstock

20世纪90年代,科尔开始他的社会心理学研究之时,测定基因表达变化的基因芯片技术刚刚出现,价格高昂,主要应用于免疫学和癌症的研究。因此他从使用流行病学工具开始——流行病学本质上就是对人们生活方式的研究。

他早期的一些论文关注社会经历如何影响携带艾滋病病毒(HIV)的男性。1996年一项以80名呈HIV阳性但是9年前尚还健康的男同性恋为对象的研究中,科尔和同事们发现其中的未出柜者更容易被病毒击败。

接下来他又发现,对于那些孤独的HIV携带者来说,不管他们是否已经出柜,病情的恶化都会更加迅速。然后他证明了,未携带HIV且未出柜的男同性恋比起已出柜者,罹患癌症和多种传染性疾病的比例都更高。差不多在同一时候,卡内基·梅隆大学的生理学家完成了一项控制良好的研究,证明社会关系较富有的人得普通感冒的几率也较小。

“你被夺命的病毒包围着,”科尔说,“但你在社交方面感受到的压力和孤独似乎在关闭你的病毒防御。这又是怎么回事?”

下一步他转向了恒河猴,一种可以进行受控研究的实验室动物。2007年,他和加州大学戴维斯分校的灵长类动物学家约翰·卡皮塔尼奥(John Capitanio)一起研究社交压力会如何影响携带SIV的恒河猴。所谓SIV即猿猴免疫缺损病毒(※此处已更改),相当于猴子版本的HIV。卡皮塔尼奥发现携带SIV的猴子如果被不停地转移到充满陌生“人”的新团体中并因而饱受压力的话,就会发病及死亡得更快——这与1996年科尔对孤独同性恋者的研究结果不谋而合。

卡皮塔尼奥进行了一次粗略的免疫学分析,发现受到压力的猴子的抗病毒反应较弱。科尔决定研究得更加深入一点。他首先切开了淋巴结——“对抗感染的大本营”——发现在感受到社交压力的猴子体内,病毒在将压力信号传入淋巴结的交感神经干周围长势旺盛。

“这是一条线索。”科尔说:就在免疫反应本该最强的地方,病毒却生长得咄咄逼人。看来神经干里的压力信号要么在途中被消弭,要么在抵达之后被忽略。

科尔进一步研究之后,发现是后者:猴子的身体产生了适当的压力信号,但是免疫系统好像没有做出正确的回应。为什么呢?他无法凭借手中的工具找到答案。他仍然在盯着细胞,但他需要的是探查到细胞内部。

最终科尔有了机会。他在UCLA一直在努力掌握跨越整个基因组的基因表达分析技术,并在2001年被任命为教授。基因芯片机器——基恩·罗宾逊用于研究蜜蜂的那种——变得越来越便宜。科尔得到了一台的使用权,并将其投入到工作中来。

由此开始了我们所说的孤独患者研究。

首先,科尔与芝加哥大学社会心理学家约翰·卡乔波(John Cacioppo)合作,挖掘了卡乔波对153名50岁至70岁的健康芝加哥人所做的社会关系调查数据。两人确定出8名社会关系最安全的人和6名最孤独的人,从他们身上提取了血样。(社会关系最不安全的6人是真真正正的孤独者,他们报告称在最近的4年里感觉与他人相隔甚远。)然后科尔从血样的白细胞(免疫系统的一个关键成员)中分离出遗传物质,观察其DNA的状态。

他发现了广泛而强烈,同时又很怪异的模式化基因表达反应,这种反应在接下来的几年里变得相当常见。在人类基因组的大约22,000条基因中,209条基因的表达反应在孤独组和非孤独组之间呈现显著差异。这意味着基因组的大约1%——一个相当大的比例——会由于一个人是否感觉孤独而表现出不同的反应。

研究对象的基因表达模式打印出来后很像是罗宾逊的换巢蜜蜂基因表达变化红绿矩阵图:孤独患者和社会关系安全者的整块基因看上去明显不同。而这些基因中有很多都与炎症免疫反应有关。

这一发现开启了全新的认识。如果社交压力的确能够造成这种基因表达谱,那么很多事情也许便能够得到解释,比如在科尔先前的研究中,孤独的HIV携带者为什么会如此迅速地发病。

不过这项研究的对象仅仅有14人。科尔需要更多。

随后几年里,他在包含贫困儿童、患癌症的抑郁人士和照料将死于癌症的配偶的人士的群组中,都发现了类似的失衡基因表达或者免疫反应谱。在后来的一个研究项目中,他与合作者,英属哥伦比亚大学的心理学家格里高利·米勒(Gregory Miller)和尼古拉斯·罗雷德(Nicolas Rohleder)一起采访了温哥华地区103名15岁至19岁的健康女性,询问她们的社交生活,然后抽血绘制基因表达图谱,半年之后再次抽血并绘制图谱。

一些女性在第一次访谈中称她们在感情生活或者与家人、朋友的关系方面遇到了麻烦。六个月之后,这些社交有问题的对象出现了科尔在其他独立研究中发现的那种失衡基因表达谱。

2009年年初,科尔在一篇综述论文中解释了所有这些现象,并在当年将论文发表于《心理科学最新动向》(Current Directions in Psychological Science)。“我们通常认为压力是致病风险因素。”科尔说,“它确实是,或多或少。但是如果你利用我们拥有的最优良仪器实际衡量一下压力,就会发现它在社交孤立面前根本不值一提。社交孤立是最为确定、最为强劲的社交或心理致病因素。无可匹敌。”

这有助于解释为什么很多工作压力大但收入高的人似乎并未遭受致病效应,而其他人,尤其是那些孤独且贫困的人,却在不停增加罹患压力相关疾病的病例数——肥胖症、II型糖尿病、高血压、动脉硬化、心力衰竭、中风。

尽管这些效应早已众所周知,科尔仍表示当他发现社会联系对基因表达的影响如此巨大时,还是感觉很惊讶。“或者说不是因为我们发现了这种影响,”他纠正道,“而是因为我们看到了这种影响的一致性。这种基因以活性变化对社会经历做出反应,以及基因网络和基因表达引起的连锁反应,一致得出人意料——从猴子到人,从5岁儿童到成人,从温哥华的少年到芝加哥的60岁居民。”

为什么贫穷会摧残身体

贫穷本身看上去也开始像是种疾病了。图片来源:shutterstock

科尔的工作带来了各种各样的启示——有的重大且具备实用价值,有的令人兴奋又引人哲思。比如说,它有助于解释往往在穷人身上阴魂不散的健康问题。贫穷会摧残身体:过去几十年间的数百项研究已经确定了低收入和哮喘、流行性感冒、心脏病、癌症以及其他各种疾病的高发病率之间的关联。然而瘪瘪的钱包并不会让你生病,我们都知道一些人逃脱了贫穷带来的危险。那么究竟是贫困生活的什么方面让我们生病呢?

2008年,科尔与米勒以及当时也在英属哥伦比亚大学的社会心理学家伊迪丝·陈(Edith Chen)开展的研究就是要找寻这个问题的答案。研究者收集了31个孩子的基因表达谱。他们的年龄从9岁至18岁不等,全都患有哮喘,其中16名家境贫困,15名生活宽裕。

和科尔预料的一致,富裕组儿童表现出健康的免疫反应,控制肺部炎症的基因活性升高。贫困孩子的炎症基因表现得更加繁忙,而控制炎症的基因网络却萎靡不振,而且在他们的病例中出现了更多的哮喘发作和其他健康问题。贫穷似乎正在搅乱他们的免疫系统。

不过科尔、陈和米勒怀疑还有另外的因素在起作用——某种往往伴随着贫穷但并非贫穷本身的因素。因此他们不仅抽取了那些孩子的血样,收集了他们的社会及经济地位信息,还为他们播放了展现含糊或尴尬的社交场合的影片,然后询问他们感觉这些情境中包含了多大的威胁。

结果发现贫困的孩子比富裕的孩子感受到更多的威胁。这种心理学家所说的“认知框架”方面的差异并不值得惊讶。先前的很多研究都已经证明,家境贫寒或者身处贫穷社区毋庸费解地容易让人们对含糊的社交情境蕴含的威胁更加敏感。

但在这项研究中,陈、科尔和米勒想要看一下他们是否能够区分开认知框架的影响和收入差距的影响。结果证明他们可以,因为两个组中都出现了例外的孩子。几名贫困孩子在含糊情境中感受到极少的威胁,而几名富裕孩子感受到的却很多。影响大部分基因表达差异的,其实是孩子的认知框架而非收入水平。造成免疫反应失常的主要因素看来不是贫穷,而是孩子是否认为社会可怕。

但认知框架又从何而来?孩子认为世界可怕,是因为受到他人的影响,还是因为面对世界时感觉孤独?这项研究的设计不足以给出答案。但是科尔相信孤独感起了关键作用。这个想法得到了2004年一项研究强有力的支持,该研究的对象是57名遭受严重虐待,以至于社会工作者要将他们搬离家庭的学童。人们往往称之为“考夫曼研究”,因为其发起者是耶鲁大学精神病学家琼·考夫曼(Joan Kaufman)。

考夫曼研究看上去就像是对所谓抑郁风险基因——5-羟色胺转运体基因(SERT)所做的经典调查。这种基因有长短两种形式,当然任何单一基因对情绪或者行为的影响都是有限的。然而很多研究发现短型SERT似乎能使很多人(和恒河猴)对环境变得更加敏感。根据那些研究,携带短型SERT的人在面对压力或者创伤时更容易抑郁或者焦虑。

考夫曼首先查看孩子们的精神健康能否与其携带的SERT类型形成关联。答案是肯定的:携带短型SERT的孩子遭受的精神健康问题比携带长型的孩子多一倍。

接下来考夫曼把孩子们的抑郁状况和SERT类型与他们的“社会支持”(一个心理学术语,是指个人可以感受、察觉或接受到的来自他人的关心或协助水平)做了交叉比对。在这里,考夫曼把社会支持严格地定义为每月至少与家庭之外一位可信任的成人接触一次。

结果他得到了不同寻常的发现,对那些拥有社会支持的孩子而言,这样一个单一而适度、严格定义的社会支持就消除了80%短型SERT和虐待带来的组合风险。

可靠社会联系的缺失对孩子的伤害差不多与虐待一样严重。他们遭受的孤立的破坏力如此巨大,以至于人们需要提出疑问,在这样的情况下,什么因素的危害才是最严重的。大多数精神病学文献都把糟糕的经历——极度的压力、虐待、暴力——当作有害因素。然而如果社会联系几乎能够完全保护我们免于受到严重虐待所带来的戕害,那么孤立岂不是和殴打与忽视一样有害吗?

考夫曼研究还挑战了西方关于个人状态的传统观念。用研究中用到的词语来说,我们有时候把“社会支持”设想成某种附属品,某种也许能够强化我们的额外资源。然而这种看法将人类的默认状态假定为茕茕孑立,实际并非如此,我们的默认状态是丝丝相连。

千百万年来我们一直是社会性动物。图片来源:shutterstock

正如科尔的同事约翰·卡乔波在他的书《孤独》中所写道的,当英国政治哲学家托马斯·霍布斯(Thomas Hobbes)写下没有文明的人类生活“孤独、贫困、卑污、残忍而短寿”时,他其实犯了个错误:贫困、卑污、残忍而短寿或许是真的,但很少孤独。

科尔说:“你改变不了你的基因。但是如果我们的这些研究中哪怕有一半是正确的,那就意味着你能够改变你的基因的行为方式——而这跟改变基因也差不了多少。通过调整你的环境,你能够调整你的基因活性。这便是我们一辈子都在做的事情。我们在一刻不停地试图找到太多挑战与太少挑战之间的那个最佳位置。”

“这是非常重要的一点:我们是自己的社会经历的构建者。你的主观经验比你的客观条件更有力量。即便你与很多人挤在一间屋子里,只要你感觉孤独,你还是会出问题。而如果你感觉自己拥有足够的支持,哪怕目力所及之处并无一人;或者如果你在想象中拥有社会联系;或者你面对世界时感觉人们在关心你,感觉你自己有价值、感觉自己很好,那么你的身体就会以‘很好’的方式行事——即便你的感受是错误的。”

科尔引用了一句约翰·弥尔顿(John Milton,英国诗人、思想家,因其史诗《失乐园》和反对书报审查制的《论出版自由》而闻名于后世)的诗:“意识是超然而独立的,它本身就可以把地狱造就成天堂,也能把天堂折腾成地狱。”

相关的果壳网小组

 

编译自:PacificStandard,The Socail Life of Genes
文章题图:
Shutterstock 友情提供

你可能感兴趣

  1. 祖先的经历,也能遗传给你?
  2. 孤独也会危害健康
  3. 决定“我像谁”的是基因,不是遗传力
  4. 基因能决定一切吗?
  5. 压力大影响母猴免疫系统的基因表达
  6. 因地制宜的基因表达:跟着怂人混,你也会变怂
  7. 千人千面,为什么人的脸型彼此不同?
  8. 当爹的也能塑造胎儿吗?
  9. 老男人们,生娃要抓紧啦!
  10. 人生可以重来,你也不再是你
  11. “垃圾DNA”不垃圾
  12. 孤独的人更短命?
19 Nov 09:50

[饭文]星巴克与哈根达斯

by 辉格

星巴克与哈根达斯
辉格
2013年10月22日

最近,以星巴克为靶子,央视再次玩弄起了“揭批暴利”的拙劣游戏,它那套以原料成本计算暴利、以基于市场汇率的国际价格比较论证国别歧视的方法,自然不值一提,不过,从商业角度看,星巴克的价格差异这件事本身倒是有些值得玩味之处。

一种评论认为,星巴克在中国市场定价偏高,是因为国内同行不争气,未能对他施加足够竞争压力,而按照星巴克自己的说法,定价差别是基于运营成本和“市场动因”上的差异,这两种说法,恐怕都没有点中问题的关键。

在美国,星巴克只是另一个快餐式消费品牌,其消费者也只是普通大众,而在中国,星巴克的主力消费者是追随西方文化的都市年轻白领;这两群消费者虽在消费着同样的产品,但他们在各自社会结构中所处的位置不同,看待这一消费行为的方式也不同,去星巴克喝一杯咖啡这件事,对他们有着相当不同的社会学意义。

这一差别在商业上引出了两个后果:首先,两国消费者的地理分布不同,中国的星巴克消费者更多集中在白领聚集的大城市,特别是受西方文化影响更大的沿海大都市的中心商业区,这意味着更高的店铺租金;其次,中国消费者为这项消费行为赋予了更多文化意义——包括文化认同、自我身份定位和个性彰显——,而这些意义的实现更多的依赖在店消费,而不是仅仅买走一杯咖啡,这意味着更低的翻台率和更高的单位固定成本。

这件事给我们的启发是,随着一种消费文化的传播,追随模仿者的组成特质会改变这种文化的性质,把它变得与其最初形态不同,这就要求,当一家跨国企业在不同文化之间推行一种消费模式时,需要对这种改变作出反应,而事实也表明,他们已经学会了如何反应。

或许我们可以把这种情况称为“哈根达斯现象”,因为它比星巴克更清晰的演示了上述机制,哈根达斯在美国只是个普通大众品牌,不算高档,更与奢侈无关,但在中国,由于被新潮白领选中而作为“说到它时显得不那么俗气的冰激凌”——它的北欧名字可能也起了点作用——而身价数倍。

较弱的例子是肯德基,在中国的三四线小城市,周末带孩子去肯德基吃饭常常是对孩子的一次奖励,许多家长甚至在肯德基为孩子过生日,这种情况在肯德基的故乡恐怕是闻所未闻和不可思议的。

不过,并不是任何西方消费品牌在中国都会有类似待遇,它必须能够典型的代表西方消费文化,而且要时常在影视文学作品中出现,不能太偏僻太小众;当然,这是近十几年来城市年轻人有了越来越多的机会通过媒体、影视、海外华人的文章、与海归的接触了解西方世界之后的情况,而在二十多年前,一个精心捏造的假洋品牌也足以获得高端洋气上档次的地位。

而且,这种商品还必须与被视为更高阶的身份相匹配,才能获得哈根达斯溢价,比如自行车,当它作为代步工具时,在当前的中国会被视为与低收入相关联的元素,而只有它作为健身工具时,才可能是高端洋气的,所以,假如某个欧美品牌自行车进入中国,若定位于代步市场,定价会低于欧美,若定位于运动健身市场,适当的定价应高于欧美。

全球化时代,随着消费模式在不同文化间传播,哈根达斯现象大概不会少见,从喜欢以某种方式喝咖啡的某甲,到喜欢像某甲那样喝咖啡的某乙,到喜欢让别人觉得他在像某甲那样喝咖啡的某丙,再到喜欢被某乙某丙们视为同类的某丁,虽然都在喝着同样的咖啡,但驱动这些消费的动机、他们对服务的需要、以及愿意为此付出的代价,都是不同的。

以前美国华人还没这么多的时候,美国人去中餐馆吃饭,也算得上是开洋荤了,很明显,针对此类偏好的中餐馆,其服务模式和定价策略,都会与国内中餐馆大相径庭;前些年,身边有个华人女朋友陪着,在许多美国人眼里也是很有面子的事情,不过,同样是娶一位华人女孩这件事,对于中国青年和美国青年,其动机和意义更是相去万里了。

 

相关文章

14 Nov 08:37

电脑一歇菜,人能挺住吗?

by
???

人类不可靠,AI在未知环境下也难以保证可靠,这个就要看如何取舍了。把未知因素通告给人类目前还是比较好的方式。

要知道怎么做就必须得实际去做。计算机自动化所节省的那份苦力,恰恰是我们学习知识所必须付出的。图片来源:Kyle Bean/The Atlantic

(文/Nicholas Carr)2009年2月12日傍晚,美国大陆航空公司的一架客机在大风天气里开始了它从新泽西州纽瓦克市飞往纽约州布法罗市的航程。如今的商业航班,飞行员其实并没有很多事情要做,这趟航班也不例外。在预计一小时的飞行时间里,机长马尔文·雷斯诺(Marvin Renslow)在起飞的短暂过程中进行了简单的人工驾驶,将这架庞巴迪Q400型涡轮螺旋桨飞机升上了天空,之后便打开了自动驾驶仪,让软件来操纵飞行。接下来,客机在1.6万英尺的高空平稳地朝西北方前进,雷斯诺和他的副驾驶丽贝卡·肖(Rebecca Shaw)聊起了他们各自的家庭生活、工作情况以及空管人员的性格和脾气。飞机顺利到达了法布法罗机场,但就在起落架和襟翼放下以后,驾驶员的油门操纵杆突然剧烈摇晃起来,这是飞机失去升力、或将陷入气动失速状态的信号。于是,自动驾驶仪关闭,机长接过手来开始人工驾驶飞机。雷斯诺的反应很快,但做的却偏偏是错误的事情:他猛地拉起操纵杆,提起了机头(这会使空速降低),却没有同时向前推动油门操纵杆让飞机加速。雷斯诺的操控不但没有化解危机,反而使飞机骤然减速。紧接着飞机就失去了控制,然后像砖头一样从空中掉了下来。“我们完了!”是机长在这架Q400客机撞上布法罗郊区的一所房子前说的最后一句话。

专业人士沦为了电脑操作工?

这起导致机上全部49人以及地面上1人死亡的事故本来不应该发生。美国国家运输安全委员会调查后认为,事故原因是飞行员的操作失误。调查报告指出,机长面对失速警告的反应 “本应自动化,实际的飞行控制输入与所受训练并不相符”,显示出“惊慌和混乱”。运营这条航线的地区性航空公司科尔根航空(Colgan Air)的一名高管也承认,机上的驾驶人员遇到紧急情况发生时缺乏情景意识(situational awareness)。

布法罗市的坠机不是一个孤立的事件。几个月后,又有一起极为类似的灾难发生,只是这次造成了更多的伤亡。2012年5 月31日晚上,一架法国航空公司的空客A330客机从巴西的里约热内卢起飞,前往巴黎。起飞后大约3小时,这架大型喷气式客机便在大西洋上空遭遇了风暴的袭击。飞机的空气速度传感器由于表面结冰,开始给出错误的读数,导致自动驾驶仪自动脱开。惶惑之下,操作飞机的驾驶员皮埃尔-塞德里克·博宁( Pierre-Cédric Bonin)猛地拉回操纵杆。飞机开始上升,失速警告也同时响起,但博宁仍然一意将操作杆往回拉。随着飞机大幅攀升,失去了速度,空速传感器再次开始工作,反馈给机组人员准确的数字。然而,博宁继续让飞机减速。最终整架客机停滞下来,开始下落。如果博宁当时松开操纵杆,A330客机很可能会自我校正过来,但他没有这样做。3分钟后,飞机从3.5万英尺的高空掉下来,击中海平面。机上的乘客和机组人员共228人全部身亡。

人类历史上的第一个自动驾驶仪由两个陀螺仪组成,在1930年的一篇《大众科学》(Popular Science)文章里被戏称为“金属飞行员”。两个陀螺仪一个水平安装,另一个竖直安装,分别连接到飞机的控制器,由螺旋桨后面的一个风力驱动的发电机供电。水平陀螺仪保持机翼水平,而竖直的则负责转向。现代的自动驾驶仪与当年那个简陋的设备几乎找不到相似之处。现代的自动驾驶仪由飞机上的计算机控制,运行极其复杂的软件,从电子传感器收集信息,每时每刻都在不断调整飞机的姿态、速度和轴承。如今的飞行员在他们所谓的“玻璃驾驶舱”里工作,以前的那些模拟表盘和读数器大多已经消失,取而代之的是数字化的显示框。自动化已经变得如此之复杂,在通常的客运航班里,留给人类飞行员操控的拢共只有短短3分钟的时间。飞行员花很多时间在做的,是监控屏幕和键入数据。不太夸张地说,他们已经成了电脑操作员。

而这——根据许多航空和自动化专家得出的结论——成了一个问题。过度使用自动化系统导致飞行员的专业知识退化,反应变得迟钝。英国布里斯托尔大学的人体工程学专家简·诺伊斯(Jan Noyes)将其称为“机组人员的去技术化”。没有人会怀疑自动驾驶仪多年来在增强飞行安全这方面所做的贡献。自动驾驶仪减轻了飞行员的疲劳程度,提供故障的预先警报,当机组人员无力驾驶飞行时,可以保持飞机仍然有序驾驶。但整体飞行事故的稳步下降,掩盖了近来一系列“令人瞠目结舌的新型事故”,乔治梅森大学的心理学教授和自动化领域的权威拉贾·巴拉苏罗(Raja Parasuraman)这样评论。当自动驾驶仪出现故障时,太多的飞行员面对突如其来、已经很少承担的重任,都犯下错误。2011年,在美联社的一次采访中,美国联合航空公司的资深飞行员、曾经担任航空公司飞行员协会(Air Line Pilots Association)最高安全官员的罗里·凯(Rory Kay)直截了当地说:“我们忘记了如何飞行。”美国联邦航空管理局(Federal Aviation Administration)大是紧张,同年1月下发了“安全警报” ,敦促各航空公司让其所辖的飞行员进行更多的手动飞行。美国联邦航空管理局警告说,过度依赖于自动化可能将飞机和乘客都置于危险之中。

依赖自动化,失去的或许比得到的更多

航空公司的教训值得我们思考。这些事件揭示了自动化虽有一切好处,但却会损害依赖它的人的能力和表现。其影响的方面远远不止是安全。因为自动化会改变我们如何行动、如何学习和知道哪些东西,事情就牵涉到道德层面。我们作决定或做不出决定把哪些事情交给机器,会塑造我们的生活和我们自己在世界上的立足之地。事情一直如此,但近年来,随着节省人力的技术的轨迹从机械转向软件,自动化已渗透到生活的方方面面,其运作也变得更加隐蔽。为了追求便利、速度和效率,我们忙不迭地将工作负荷抛给计算机,来不及反思这样做的代价是什么。

医生用计算机来做诊断、做手术;华尔街的银行家用计算机来组合和买卖金融工具;建筑师用计算机来设计建筑;律师用计算机来记录证据。被计算机化的并不只有专业工作领域。有了智能手机和其他体积更小、价格更便宜的电脑,我们依靠软件来进行日常生活的许多杂事。我们打开App帮我们购物、做饭、社交,甚至养育孩子。我们一个路口一个路口地遵循GPS的指示 。我们征求推荐引擎的意见看什么电影、读什么书和听什么歌。我们有问题问谷歌,有事情交给Siri。在工作和生活中,我们越来越多地生活在玻璃驾驶舱里。

一百年前,英国数学家、哲学家怀海特(Alfred North Whitehead)写道:“文明的发展就是加大不用想便能完成的事情的数量。”很难想象还有什么表述比这句话更能显示对自动化的信心。怀海特的话里隐含着一种这样的信念——人类活动是从上到下有等级区分的:每当我们把一件任务或一种工具交给机器去负责,就把自己解放出来去追求等级更高的、需要更多灵活性、更多智能或更开阔视野的任务。每向上一步,我们都可能会失去些什么,但从长远看,我们的所得将远远大于所失。

历史提供了充足的证据来支持怀海特。自从人类发明了杠杆、方向盘和算盘以来,我们已经将数不清的体力活儿和脑力活儿交给了机器。但是,怀海特的见地不应该被当成普遍的真理。当他写下这句话时,自动化还往往局限于一些特定的、明确的、重复性的劳动领域——用蒸汽织机织布、用机械式计算器算数。现在的自动化则不同。计算机可以被编程执行非常复杂的工作,其间要评估许多变量来完成一连串紧密协调的任务 。许多软件程序还负责脑力工作,比如观察检测、分析判断,甚至作出决定等等直到最近都还被认为是人类所独有的职能。这可能会使得操作计算机的人落入扮演一个高科技零杂工的角色——他键入数据、监控输出、谨防事故。与其说让我们腾出手来开辟思想和行动的新前沿,各种各样的软件实际上让我们的目光变得短浅。我们用一条条的规则替换了更加精细和专业的才干。

大多数人都愿意相信,自动化能让我们把时间花在更高层次的追求上面,但不会改变我们行动或思考的方式。这是一种谬见——自动化学者称之为“替代神话”的说法。节省劳力的设备不只替代了工作或其他活动中的一些彼此孤立的组成部分,它还改变了整个任务,包括参与者的角色、态度和技能。乔治梅森大学的心理学教授、自动化领域的权威拉贾·巴拉苏罗和他的一位同事在其2010年的一篇论文中指出,“自动化并不是简单地替代人类活动,它往往会以设计者预期之外和始料不及的方式改变这一活动。”

大多数人都愿意相信,自动化能让我们把时间花在更高层次的追求上面,但不会改变我们行动或思考的方式。这是一种谬见——自动化学者称之为“替代神话”的说法。图片来源:​inopalesolutions.com

心理学家发现,当我们用电脑工作时,经常会陷入自满和偏见——两种会削弱我们表现和引发错误的认知缺陷当中。当一台计算机诱使我们进入一种虚假的安全感当中,自动化自满就出现了。相信计算机会准确无误地完成工作并且处理任何出现的问题,我们任注意力四散开去。我们与手头的工作脱离开来,对周围事情的注意力也在淡去。自动化偏见则是指我们过于相信显示器上面的信息的准确性。我们对软件的信任变得如此之强,以至于忽略或无视其他的信源,包括自己的眼睛和耳朵。当一台计算机提供了不正确或不完备的数据,我们视而不见。

在驾驶舱里、在战场上、在工厂的控制室中,高风险的情况下自满和偏见的例子已有据可查。然而,最近的研究表明,这种问题困扰着每一个用电脑工作的人。今天,许多放射科医生使用分析软件来高亮乳房X光片上可能有问题的地方。通常,这些亮点有助于疾病的发现。但是,它们也可以具有相反的效果。受软件建议的影响,放射科医生可能会不那么留意还没有被突出显示的区域,有时候就会忽视了早期肿瘤的影像。我们大多数人在使用电脑时也会经历自动化自满。在使用邮件或文字处理软件时,因为知道了有拼写检查在工作,我们就不那么细心地校对语法错误了。

自动化将人从执行者转变为观察员

计算机可以削弱人类认识和关注点的方式指向了一个更深层次的问题。自动化将我们从执行者转变为观察员。我们放下了操纵杆,转而观看屏幕。这种转变可能使我们的生活更轻松,但同时也可以抑制专业技能的发展。从20世纪70年代末开始,心理学家就一直在记录一种被称为“加工效应”(Generation Effect)的现象。这种现象最初是在词汇研究当中被观察到的,研究人员发现,当人们从脑中主动生成单词(背单词时“经过了大脑”),要比仅仅是看更容易记住它。

这一现象现在已被研究透彻,在许多不同的情况下都对学习有着影响。当你积极参与一项任务,便开启了复杂的心理过程,这些过程能让你记住更多的知识。你学得更多、记得也更多。当你在一段较长的时间里重复同一种任务,你的大脑会构造专门的神经回路用于进行这一活动。大脑会聚集丰富的信息存储,并且将知识组织得让你瞬间就能调用它。无论是网球场上的小威廉姆斯还是在棋盘上的卡尔森[注释],技艺精湛的高手可以发现情境中的规律、评估信号,并以看似不可思议的速度和精度应对不断变化的情况。看起来像是本能的反应,实际上是辛苦磨练出、来之不易的技巧,也正是现代软件旨在缓解的劳累中锻炼出来的技巧。

注释马格努斯·卡尔森,挪威国际象棋大师,现为19岁的卡尔森是国际象棋等级排名第一的棋手。 2004年4月26日,年仅13岁零148天的卡尔森获得国际象棋特级大师称号。

荷兰的认知心理学家克里斯托夫·范宁韦根(Christof van Nimwegen),在2005年开始了一个实验,旨在调查软件对我们掌握技术诀窍方面的影响。范宁韦根招募了两拨人,玩一个叫做“传教士与食人族”(Missionaries and Cannibals)的电脑游戏,游戏的规则基于一个经典的逻辑难题。在游戏中,玩家必须运送5个食人族和5个传教士(在范宁韦根的实验里分别用5个黄球和5个蓝球表示)过河,使用的船一次最多只能容纳 3名乘客。无论在船上还是岸上,食人族的数量都不能比传教士多(否则就要吃人了)。实验参与者被分成两组,一组使用教学软件来完成任务,软件提供一步一步的指导,高亮出哪些移动是可行的、哪些不是。另一组则使用一个基本的程序,不会提供任何辅助。

你可能也猜到了,使用教学软件的那一组在一开始取得了更快的进步。他们可以简单地按照提示进行操作,而不是每一步都停下来,回忆规则并想出如何将其适用于新的形势。但是,随着测试的进行,不使用辅助软件的一组逐渐占据上风。他们对游戏形成了更清晰的概念性理解,制定出更好的策略,也少犯错误。8个月后,范宁韦根让相同的人再次玩这个游戏。此前没有使用辅助软件的一组几乎以超出对手一倍的速度完成了比赛。在加工效果的正面作用下,他们“对知识形成了更强的铭记”(imprinting of knowledge)。

范宁韦根在实验室里观察到的现象——自动化会妨碍我们将信息转化成知识的能力——在现实世界中也可以见到。在许多企业中,管理者和其他专家都依赖决策支持系统来分析信息和提出行动方案。例如,会计师在企业的审计中会使用这种系统。应用程序会加快工作的进度,但一些迹象表明,软件越是能干,会计师就越是不行。最近由澳大利亚的研究人员进行的一项研究,调查了决策支持系统对三家跨国会计师事务所的影响。其中两家会计师事务所采用非常先进的软件,能够根据会计师在软件客户端输入的一些基本问题的答案,在客户的审计文件中推荐纳入相关业务风险。第三家公司使用简单的软件,需要会计师来评估一系列可能的风险,手动选取相关的列表。研究人员给每家公司的会计师做了测试,测量他们的专业知识。第三家公司的会计师比其他两家的明显很强,对不同形式的风险都显示出了更强的理解能力。

谁还需要人类,这真的会成为一个问题

而最令人震惊和不安的,是计算机自动化还仍处于早期阶段。专家过去以为,程序员将复杂任务自动化的能力有限,特别是在那些涉及感官知觉、模式识别和概念性知识的领域。他们以驾驶汽车为例,说驾驶汽车不仅需要在很短的时间里理解高速运动的视觉信号,还需要流畅地应对突发状况。两位著名经济学家在2004年写道,“面对迎面而来的车辆执行向左转这个动作涉及的因素之多,很难想象一套规则可以复制司机的行为。”短短6年后,2010年10月,谷歌宣布它已经建立了一个由7辆“自驾驶汽车”组成的车队,在加利福尼亚州和内华达州的道路上已经行驶了超过14万英里(超过20万公里)。

无人驾驶汽车为机器人将如何在物理世界中导航和执行任务提供了一个预览,并且从人类手中接过了环境意识、协调运动和流体决策的工作。大脑任务的自动化正在取得同样迅速的进展。就在几年前,一台电脑参加像《危机边缘》(Jeopardy)这样的智力问答节目听上去似乎很可笑,但在2011年的一个著名比赛中,IBM的超级计算机Waston将这档节目一直以来的冠军肯·詹宁斯(Ken Jennings)打得落花流水。Waston并不像人类那样思考,它并不了解它在做什么或说什么。它的优势在于现代计算机处理器的非凡速度。

2011年出版的《与机器赛跑》(Race Against the Machine)探讨了计算机化对经济产生的影响。在书中,麻省理工大学(MIT)的研究人员埃里克·布林约尔松(Erik Brynjolfsson)和安德鲁·迈克菲(Andrew McAfee)指出,谷歌的无人驾驶汽车和IBM的超级电脑Waston是自动化新浪潮的代表,对呈“指数级增长”的计算机力量的利用,将改变几乎每一个工作和职业的工作性质。今天,他们写道,“计算机发展得如此迅速,其应用领域从科幻小说迈入日常生活……短短几年就够了。”

谁需要人类呢?这个问题,不管是疑问还是反问,经常会在讨论自动化的话题里出现。如果计算机的能力扩展得这么快,相比之下,如果人类看起来缓慢、笨拙、容易出错,为什么不建立一个自成一体的系统,完美地执行任务而没有任何人类的监督或干预呢?为什么不把人为因素给排除出去呢?在评论自动化和飞行员操作失误之间的联系时,技术理论家凯文·凯利(Kevin Kelly)指出,显而易见的解决方案是开发一个完全自主的自动驾驶仪:“从长远来看,人类飞行员不应该驾驶飞机。”硅谷的风险投资家维诺德·科斯拉(Vinod Khosla)最近表示,当保健医疗软件(他戏称为“医生算法”)从协助主治医生做出诊断发展到完全取代医生之时,医疗保健将会得到大大改善。解决不完善自动化的方法就是全面自动化。

这种想法是很诱人,但没有机器是万无一失的。即使是最先进的技术也迟早会有失效、起到反效果的一天,最先进的计算机系统也会遇到它的设计者从来没有预料到的情况。随着自动化技术变得越来越复杂,算法、数据库、传感器和机械部件之间的联系越来越紧密,潜在的故障源的数量将成倍增长,而且愈加难以检测。所有的部件都在完好运行,但系统设计中出了一个小错误就能酿成大祸。而且,就算可以设计出一个完美的系统,它仍将在一个不完美的世界里运行。

在1983年学术期刊《自动化》(Automatica)上发表的一篇经典论文中,英国伦敦大学学院的工程心理学家利兹安·班布里奇(Lisanne Bainbridge)描述了计算机自动化的一个难题。许多系统设计者都假设,人类操作员是“不可靠、低效的”,至少和计算机相比是如此。于是,设计师便尽量让人承担尽可能小的责任。最终,人变成了监控者,只是被动地看着屏幕。而这样一份工作是我们人类——出了名爱走神的物种——特别不适合的工作。早在二战期间对雷达操作员的调查研究结果就表明,人类很难在超过半个小时的时间里一直保持注意力盯着屏幕看东西。班布里奇指出,“这意味着人类不可能完成监控突发异常情况的基本职能。”而且,由于一个人的能力“不用就会变差”,哪怕是经验丰富的操作员,如果一味盯着屏幕,最终也将和初出茅庐的新手没什么两样。难以集中注意力和缺乏对原理的认知这两点加起来,增大了遇到事故操作员会无力应对的可能。于是乎,人类将是系统中最薄弱的环节的预言就自我应验了。

削弱自动化的影响

心理学家已经发现了一些简单的方法来削弱自动化的不良影响。我们可以设定在不规则的时间间隔让软件转向控制人工控制,随时保持待命可以使人类操作员集中注意力,提高他们的情景感知和学习能力。我们也可以限制自动化的范围,确保使用电脑工作的人执行具有挑战性的任务,而不是仅仅处于旁观者的地位。给人更多的事情做,有助于使加工效应发挥作用。我们还可以在软件里融入教育性的例程,需要用户重复困难体力和脑力任务来促使记忆形成和技能培养。

有些软件设计师记住了这样的建议。在学校,最好的教学程序会通过促使人集中注意力、要求人下功夫,或者通过重复巩固学到的技能来帮助学生掌握一个课题。这些软件的设计体现了大脑储存记忆方面的最新发现,将概念性知识和实际操作融入其中。但是,大多数软件或应用程序并不鼓励学习和参与,不仅如此,有的还起到相反的效果。这是因为培养和维持专业技能几乎必然牺牲速度和效率,学习需要效率低下,而力求最大限度提高生产力和利润的商业很少会做出这样的让步。个人也一样,人人都在寻求效率和便利。我们选择减轻工作负担的程序,而不是那些使我们干活更卖力和耗时更久的程序。

无论在驾驶舱中的飞行员还是诊室里的医生,要知道怎么做就必须得实际去做。关于人类最了不起的、也是最容易忽视的一件事情是:我们与现实每碰撞一次,就会加深对世界的了解,进而更充分地融入这个世界。虽然与艰苦任务做斗争会使我们害怕付出劳动,但正是这份劳动定义了我们何以为人。计算机自动化舍本逐末,它让我们更容易得到我们想要的东西,但却增加了我们与劳作的距离,而劳作是要理解这个世界所必须的。当我们把自己化为屏幕生物,就必须面对一个存在的问题:我们是想让能做的事情来定义自己,还是让想要的东西来定义?如果我们不设法解决这个问题,我们的小工具会很乐意代我们作答。 
 
尼古拉斯·卡尔(Nicholas Carr)是《肤浅》(The Shallows: What the Internet Is Doing to Our Brains)一书的作者。科技评前不久刊登了他的文章“互联网如何毒化了我们的大脑?”以及“电子书能取代纸质书吗?”。

 

编译自:《大西洋月刊》,All Can Be Lost: The Risk of Putting Our Knowledge in the Hands of Machines
​文章图片:theatlantic.com

 

相关的果壳网小组

你可能感兴趣

  1. 电子书会取代纸质书吗?
  2. 经济学人:劳斯莱斯为A380所造引擎爆炸
  3. “914班机”真的穿越了35年?
  4. 坐飞机,你被这些谣言骗过吗?
  5. 反劫机,见招拆招的斗争
  6. 史上最成功的劫机犯
  7. 找黑匣子太OUT,飞行数据将同步发送卫星
  8. 号称NASA史上最搞怪的斜翼机
  9. 飞机上为啥禁止使用手机?
  10. 油价“打飞机”?果壳网编辑部也能!
  11. NASA披露2025年新一代客机构想蓝图
  12. 愤怒的小鸟撞飞机
13 Nov 08:35

编程能力与编程年龄

by 陈皓

程序员这个职业究竟可以干多少年,在中国这片神奇的土地上,很多人都说只能干到30岁,然后就需要转型,就像《程序员技术练级攻略》这篇文章很多人回复到这种玩法会玩死人的一样。我在很多面试中,问到应聘者未来的规划都能听到好些应聘都说程序员是个青春饭。因为,大多数程序员都认为,编程这个事只能干到30岁,最多35岁吧。每每我听到这样的言论,都让我感到相当的无语,大家都希望能像《21天速成C++》那样速成,好多时候超级有想和他们争论的冲动,但后来想想算了,因为你无法帮助那些只想呆在井底思维封闭而且想走捷径速成的人

今天,我们又来谈这个老话题,因为我看到一篇论文,但是也一定会有很多人都会找出各种理由来论证这篇论文的是错的,无所谓了,我把这篇文章送给那些和我一样准备为技术和编程执着和坚持的人。

论文

首先,我们先来看一篇论文《Is Programming Knowledge Related to Age?》(PDF链接),这篇论文是两个北卡罗莱纳州立大学计算机科学系的两个人Patrick Morrison 和 Emerson Murphy-Hill 对StackOverflow.com上的用户做了相关的数据挖掘得出来的一些数据。(我们知道StackOverflow.com上的数据是公开的,任何人都可以用来分析和统计,所以这篇论文的真实性是有的)

数据采样和清洗条件如下:(数据全量是1694981用户,平均年龄30.3岁)

  • 15-70岁之间的用户(这年龄段的用户被称做“Working age”),当然,有很多用户没有输入年龄,这些用户都被过滤了。
  • 用户在2012年内都回答过问题。因为StackOverflow在2012年对问题和答案的质量要求得比以前高了一倍,所以更能反映程序员的真实水平。
  • Reputation声望在2-100K之间。(注:StackOverflow的用户Reputation是得到社会认可的,在面试和招聘中是硬通货币。比大学的学分更有价值)

上述的条件一共过滤出84,248名程序员,平均年龄:29.02岁,平均Reputaion在1073.9分。

年龄分布图

下面我们来看一下他们的年龄分布图:我们可以看到程序员年纪的正态分布(高点在25岁左右,但是中点在29岁左右)

能力和年龄分布图

然后,计算每个人每个月的Reputation,这样可以找到这个用户的真正的活跃时间,这样便于计算这个程序员的真实能力。(总声望 / 活跃时间),可以得到他平均每个月得来的Reputation。

我们来看看程序员的能力和年龄段的分布图:(你可能会大吃一惊)

上图中我们可以看到,程序员的能力在从25岁左右开始上升,一直到50岁后才会开始下降。所以说,程序员吃的不是青春饭。只有码农,靠蛮力,用体力而不是用脑力的程序员才是吃青春饭的人。

年纪大的人是否跟不上新技术

论文的作者分析了Tag,用了最近5年内比较流行的技术Tag,然后用了一套比较严谨的算法来查看那些所谓的“老程序员”是否在新技术上跟上不了,所谓跟不上,也就是这些老的程序员在回答这些新技术上并不活跃。所谓老,就是37岁以上的程序员(就是我现在的年纪)。

得到了下表:可以看到,老程序员和年轻的程序员对于一些新技术的学习来说也是差不多的,甚至有些项还超过了年轻的程序员。

结论

论文的结论是:

1)程序员技术能力上升是可以到50岁或60岁的。

2)老程序员在获取新技术上的能力并不比年轻的程序员差。

我的一些感受

最后,我说一说我的一些感受:

  • 这些年来的对于外企和国内感受—— 国外牛B的IT公司的工程能力并不见得比国内的要强多少,但是国外那些NB的IT公司的架构和设计能力远远超过国内的公司,最可怕的是,那些有超强架构和设计能力的“老程序员们”还战斗在一线,这些战斗在一线的老鸟的能力绝对超过100个普能的新手。
  • 对年轻程序员的感受——国内新一代的程序员们太浮燥了。老实说,对于大多数人来说,如果你没有编程到30岁,你还不能成为一个“合格”的程序员所以,并不是编程编到30岁就玩完了,而是编程编到30岁才刚刚入门。这些不合格的程序,整天BS这个不好,那个不好的,而且喜欢速成,好大喜功。
  • 我是一个奔四的人了,编程就像登山一样,越往上爬人越少,所以,在我这个年纪还有想法,对编程还有热情的人不多了,基本上都是转Manager了。其实,什么职位,Title都是虚的,公司没了什么都没了,只有技术才是硬通货。而且,越是这个年纪还在玩编程玩技术的人,其实其经验和能力都是比较强的,都是中坚力量,如果还有其它这个年纪和我一样的人,求交往

(全文完)

(转载本站文章请注明作者和出处 酷 壳 – CoolShell.cn ,请勿用于任何商业用途)

——=== 访问 酷壳404页面 寻找遗失儿童。 ===——

相关文章

10 Nov 08:59

Some Unity codebase stats

I was doing fresh codebase checkout & building on a new machine, so got some stats along the way. No big insights, move on!

Codebase size

We use Mercurial for source control right now. With ”largefiles” extension for some big binary files (precompiled 3rd party libraries mostly).

Getting only the “trunk” branch (without any other branches that aren’t in trunk yet), which is 97529 commits:

  • Size of whole Mercurial history (.hg folder): 2.5GB, 123k files.
  • Size of large binary files: 2.3GB (almost 200 files).
  • Regular files checked out: 811MB, 36k files.

Now, the build process has a “prepare” step where said large files are extracted for use (they are mostly zip or 7z archives). After extraction, everything you have cloned, updated and prepared so far takes 11.7GB of disk space.

Languages and line counts

Runtime (“the engine”) and platform specific bits , about 5000 files:

  • C++: 360 kLOC code, 29 kLOC comments, 1297 files.
  • C/C++ header: 146 kLOC code, 18 kLOC comments, 1480 files.
  • C#: 20 kLOC code, 6 kLOC comments, 154 files.
  • Others are peanuts: some assembly, Java, Objective C etc.
  • Total about half a million lines of code.

Editor (“the tools”), about 6000 files:

  • C++: 257 kLOC code, 23 kLOC comments, 588 files.
  • C#: 210 kLOC code, 16 kLOC comments, 1168 files.
  • C/C++ Header: 51 kLOC code, 6k comments, 497 files.
  • Others are peanuts: Perl, JavaScript etc.
  • Total, also about half a million lines of code!

Tests, about 7000 files. This is excluding C++ unit tests which are directly in the code. Includes our own internal test frameworks as well as tests themselves.

  • C#: 170 kLOC code, 11 kLOC comments, 2248 files.
  • A whole bunch of other stuff: C++, XML, JavaScript, Perl, Python, Java, shell scripts.
  • Everything sums up to about quarter million lines of code.

Now, all the above does not include 3rd party libraries we use (Mono, PhysX, FMOD, Substance etc.). Also does not include some of our own code that is more or less “external” (see github).

Build times

Building Windows Editor: 2700 files to compile; 4 minutes for Debug build, 5:13 for Release build. This effectively builds “the engine” and “the tools” (main editor and auxilary tools used by it).

Build Windows Standalone Player: 1400 files to compile; 1:19 for Debug build, 1:48 for Release build. This effectively builds only “the engine” part.

All this doing a complete build. As timed on MacBookPro (2013, 15” 2.3GHz Haswell, 16GB RAM, 512GB SSD model) with Visual Studio 2010, Windows 8.1, on battery, and watching Jon Blow’s talk on youtube. We use JamPlus build system (“everything about it sucks, but it gets the job done”) with precompiled headers.

Sidenote on developer hardware: this top-spec-2013 MacBookPro is about 3x faster at building code as my previous top-spec-2010 MacBookPro (it had really heavy use and SSD isn’t as fast as it used to be). And yes, I also have a development desktop PC; most if not all developers at Unity get a desktop & laptop.

However difference between a 3 minute build and 10 minute build is huge, and costs a lot more than these extra 7 minutes. Longer iterations means more distractions, less will to do big changes (“oh no will have to compile again”), less will to code in general etc.

Do get the best machines for your developers!

Well this is all!

10 Nov 08:55

How to become a Graphics Programmer in the games industry

by Oliver Franzke

As we were recently hiring a new Graphics Programmer at work I had to identify what kind of technical knowledge and skills we would expect from a potential candidate. Although this definition will be somewhat specific to what we look for in a candidate, it might still be of interest to other coders trying to score a job in the industry as a Rendering Engineer.

This post might help you to identify areas to learn about in order to get you closer to your goal of becoming a Graphics Engineer, whether you just finished your degree or perhaps have been working in the games industry in a different role. Alternately, if you are a seasoned Rendering Programmer, then you know all of this stuff and I would love to hear your comments on the topic.

Know the Hardware

Learning about the strengths and weaknesses of the hardware that will execute your code should be important for any programmer, but it’s an essential skill for a Graphics Engineer. Making your game look beautiful is important; getting all of the fancy effects to run at target frame rate is often the trickier part.

Of course, it would be unrealistic to expect you to know every little detail about the underlying hardware (especially if you are just starting out) but having a good high-level understanding of what is involved to make a 3D model appear on screen is a mandatory skill, in my opinion. A candidate should definitely know about the common GPU pipeline stages (e.g. vertex- and pixel-shader, rasterizer, etc.), what their functionality is and whether or not they are programmable, configurable or fixed.

Very often, there are many ways to implement a rendering effect, so it’s important to know which solution will work best on a given target device. Nothing is worse than having to tell the artists that they will have to modify all of the existing assets, because the GPU doesn’t support a necessary feature very well.

For example, the game that I am currently working on is targeting desktop computers as well as mobile devices, which is important because mobile GPUs have very different performance characteristics compared to their desktop counterparts (check out my 5 minute micro talk on this topic if you are interested). Our team took this difference into account when making decisions about the scene complexity and what kind of effects we would be able to draw.

A great way to learn more about GPUs is to read chapter 18 of Real-Time Rendering (Third Edition), because it contains an excellent overview of the Xbox 360, Playstation 3 and Mali (mobile) rendering architectures.

Good Math Skills

Extensive knowledge of trigonometry, linear algebra and even calculus is very important for a Graphics Programmer, since a lot of the day to day work involves dealing with math problems of varying complexities.

I certainly expect a candidate to know about the dot and cross products and why they are very useful in computer graphics. In addition to that, it is essential to have an intuitive understanding for the contents of a matrix, because debugging a rendering problem can make it necessary to manually ‘decompose’ a matrix in order to identify incorrect values. For example, not that long ago I had to fix a problem in our animation system and was able to identify the source of the problem purely by looking at the joint matrices.

In my opinion, a candidate should be able to analytically calculate the intersection between a ray and a plane. Also, given an incident vector and a normal, I would expect every Rendering Engineer to be able to easily derive the reflected vector.

There are plenty of resources available on the web. You can find some good resources here. The book “3D Math Primer for Graphics and Game Development” does a great job explaining a lot of the fundamentals like vectors, matrices and quaternions to name just a few topics. I would also strongly recommend attempting to solve some of these problems on a piece of paper instead of looking at a preexisting solution. It’s actually kind of fun, so you should definitely give it a try.

Passion for Computer Graphics

An ideal candidate will keep up to date with the latest developments in computer graphics especially since the field is constantly and rapidly advancing (just compare the visual fidelity of games made 10 years ago with what is possible today).

There are plenty of fascinating research papers (e.g. current SIGGRAPH publications), developer talks (e.g. GDC presentations) and technical blogs available on the internet, so it should be pretty easy to find something that interests you. Make sure to check out the blogs of fellow Rendering Engineers for some really inspiring articles!

Of course implementing an algorithm is the best way to learn about it, plus it gives you something to talk about in an interview. Writing a cool graphics demo also helps you to practice your skills and most of all it is a lot of fun.

If you want to maximize your chances of getting a job make sure to spend some time making your demo look pretty. You may have implemented the most efficient tessellation algorithm, but if people can’t see what’s going on they might not be as impressed as they should be. Creating visually pleasing images is a big part of the profession, so it’s generally a good idea to show that you have this skill as well.

Performance Analysis and Optimization

One of the responsibilities of a Graphics Programmer is to profile the game in order to identify and remove rendering related bottlenecks. If you are just starting out I wouldn’t necessarily expect you to have a lot of practical experience in this area, but you should definitely know the difference between being CPU and GPU bound.

An ideal candidate will have used at least one graphics analysis tool like PIX (part of the DirectX SDK), gDEBugger or Intel’s GPA. These applications are available for free allowing you to take a closer look at what’s going on inside of a GPU, isolate bugs (e.g. incorrect render-state when drawing geometry) and identify performance problems (e.g. texture stalls, slow shaders, etc.)

Conclusion

The job of a Graphics Programmer is pretty awesome since you’ll be directly involved with the visual appearance of a product. The look of a game is very often the first thing a player will know about it (e.g. trailers, screenshots) which has been very gratifying for me personally.

Truth be told, you won’t be able to write fancy shaders every day. You should be prepared to work on other tasks such as: data compression (e.g. textures, meshes, animations), mathematical and geometry problems (e.g. culling, intersection computations) as well as plenty of profiling and optimizations. Especially, the latter task can be very challenging since the GPU and the associated driver cannot be modified.

To sum up, becoming Rendering Engineer requires a lot of expert knowledge, and it is certainly not the easiest way to get a foot in the proverbial games industry door, but if you are passionate about computer graphics it might be the right place for you!

The Beginner’s Checklist