随笔-341  评论-2670  文章-0  trackbacks-0

这个系列的起因是这样的,王垠写了一篇喷go的博客http://www.yinwang.org/blog-cn/2013/04/24/go-language/,里面说go已经烂到无可救药了,已经懒得说了,所以让大家去看http://www.mindomo.com/view.htm?m=8cc4f95228f942f8886106d876d1b041,里面有详细的解释。然后这篇东西被发上了微博,很多博友立刻展示了人性丑陋的一面:
1、那些go的拥护者们,因为go被喷了,就觉得自己的人格受到了侮辱一样,根本来不及看到最后一段的链接,就开始张牙舞爪。
2、王垠这个人的确是跟人合不来,所以很多人就这样断定他的东西“毫无参考价值”。

不过说实话,文章里面是喷得有点不礼貌,这也在一定程度上阻止了那些不学无术的人们继续阅读后面的精华部分。如果所有的文章都这样那该多好啊,那么烂人永远都是烂人,不纠正自己的心态永远获得不了任何有用的知识,永远过那种月入一蛆的日子,用垃圾的语言痛苦的写一辈子没价值的程序。

废话就说到这里了,下面我来说说我自己对于语言的观点。为什么要设计一门新语言?原因无非就两个,要么旧的语言实在是让人受不了,要么是针对领域设计的专用语言。后一种我就不讲了,因为如果没有具体的领域知识的话,这种东西永远都做不好(譬如SQL永远不可能出自一个数据库很烂的人手里),基本上这不是什么语言设计的问题。所以这个系列只会针对前一种情况——也就是设计一门通用的语言。通用的语言其实也有自己的“领域”,只是太多了,所以被淡化了。纵观历史,你让一个只做过少量的领域的人去设计一门语言,如果他没有受过程序设计语言理论的系统教育,那只能做出屎。譬如说go就是其中一个——虽然他爹很牛逼,但反正不包含“设计语言”这个事情。

因此,在21世纪你还要做一门语言,无非就是对所有的通用语言都不满意,所以你想自己做一个。不满意体现在什么方面?譬如说C#的原因可能就是他爹不够帅啦,譬如说C++的原因可能就是自己智商太低hold不住啦,譬如说Haskell的原因可能就是用的人太少招不到人啦,譬如说C的原因可能就是实在是无法完成人和抽象所以没有linus的水平的人都会把C语言写成屎但是你又招不到linus啦,总之有各种各样的原因。不过排除使用者的智商因素来讲,其实有几个语言我还是很欣赏的——C++、C#、Haskell、Rust和Ruby。如果要我给全世界的语言排名,前五名反正是这五个,虽然他们之间可能很难决出胜负。不过就算如此,其实这些语言也有一些让我不爽的地方,让我一直很想做一个新的语言(来给自己用(?)),证据就是——“看我的博客”。

那么。一个好的语言的好,体现在什么方面呢?一直以来,人们都觉得,只有库好用,语言才会好用。其实这完全是颠倒了因果关系,如果没有好用的语法,怎么能写出好用的库呢?要找例子也很简单,只要比较一下Java和C#就够了。C#的库之所以好用,跟他语言的表达能力强是分不开的,譬如说linq(,to xml,to sql,to parser,etc),譬如说WCF(仅考虑易用性部分),譬如说WPF。Java能写得出来这些库吗?硬要写还是可以写的,但是你会发现你无论如何都没办法把他们做到用起来很顺手的样子,其实这都是因为Java的语法垃圾造成的。这个时候可以抬头看一看我上面列出来的五种语言,他们的特点都是——因为语法的原因,库用起来特别爽。

当然,这并不要求所有的人都应该把语言学习到可以去写库。程序员的分布也是跟金字塔的结构一样的,库让少数人去写就好了,大多数人尽管用,也不用学那么多,除非你们想成为写库的那些。不过最近有一个很不好的风气,就是有些人觉得一个语言难到自己无法【轻松】成为写库的人,就开始说他这里不好那里不好了,具体都是谁我就不点名了,大家都知道,呵呵呵。

好的语言,除了库写起来又容易又好用以外,还有两个重要的特点:容易学,容易分析。关于容易学这一点,其实不是说,你随便看一看就能学会,而是说,只要你掌握了门道,很多未知的特性你都可以猜中。这就有一个语法的一致性问题在里面了。语法的一致性问题,是一个很容易让人忽略的问题,因为所有因为语法的一致性不好而引发的错误,原因都特别的隐晦,很难一眼看出来。这里我为了让大家可以建立起这个概念,我来举几个例子。

第一个例子是我们喜闻乐见的C语言的指针变量定义啦:

int a, *b, **c;

相信很多人都被这种东西坑过,所以很多教科书都告诉我们,当定义一个变量的时候,类型最后的那些星号都要写在变量前面,避免让人误解。所以很多人都会想,为什么要设计成这样呢,这明显就是挖个坑让人往下跳嘛。但是在实际上,这是一个语法的一致性好的例子,至于为什么他是个坑,问题在别的地方。

我们都知道,当一个变量b是一个指向int的指针的时候,*b的结果就是一个int。定义一个变量int a;也等于在说“定义a是一个int”。那我们来看上面那个变量声明:int *b;。这究竟是在说什么呢?其实真正的意思是“定义*b是一个int”。这种“定义和使用相一致”的方法其实正是我们要推崇的。C语言的函数定义参数用逗号分隔,调用的时候也用逗号分隔,这是好的。Pascal语言的函数定义参数用分号分隔,调用的时候用逗号分隔,这个一致性就少了一点。

看到这里你可能会说,你怎么知道C语言他爹就是这么想的呢?我自己觉得如果他不是这么想的估计也不会差到哪里去,因为还有下面一个例子:

int F(int a, int b);
int (*f)(int a, int b);

这也是一个“定义和使用相一致”的例子。就第一行代码来说,我们要如何看待“int F(int a, int b);”这个写法呢?其实跟上面一样,他说的是“定义F(a, b)的结果为int”。至于a和b是什么,他也告诉你:定义a为int,b也为int。所以等价的,下面这一行也是“定义(*f)(a, b)的结果为int”。函数类型其实也是可以不写参数名的,不过我们还是鼓励把参数名写进去,这样Visual Studio的intellisense会让你在敲“(”的时候把参数名给你列出来,你看到了提示,有时候就不需要回去翻源代码了。

关于C语言的“定义和使用相一致”还有最后一个例子,这个例子也是很美妙的:

int a;
typedef int a;

int (*f)(int a, int b);
typedef int (*f)(int a, int b);

typedef是这样的一个关键字:他把一个符号从变量给修改成了类型。所以每当你需要给一个类型名一个名字的时候,就先想一想,怎么定义一个这个类型的变量,写出来之后往前面加个typedef,事情就完成了。

不过说实话,就一致性来讲,C语言也就到此为止了。至于说为什么,因为上面这几条看起来很美好的“定义和使用相一致”的规则是不能组合的,譬如说看下面这一行代码:

typedef int(__stdcall*f[10])(int(*a)(int, int));
这究竟是个什么东西呢,谁看得清楚呀!而且这也没办法用上面的方法来解释了。究其原因,就是C语言采用的这种“定义和使用相一致”的手法刚好是一种解方程的手法。譬如说int *b;定义了“*b是int”,那b是什么呢,我们看到了之后,都得想一想。人类的直觉是有话直说开门见山,所以如果我们知道int*是int的指针,那么int* b也就很清楚了——“b是int的指针”。 

因为C语言的这种做法违反了人类的直觉,所以这条本来很好的原则,采用了错误的方法来实现,结果就导致了“坑”的出现。因为大家都习惯“int* a;”,然后C语言告诉大家其实正确的做法是“int *a;”,那么当你接连的出现两三个变量的时候,问题就来了,你就掉坑里去了。

这个时候我们再回头看一看上面那一段长长的函数指针数组变量的声明,会发现其实在这种时候,C语言还是希望你把它看成“int* b;”的这种形式的:f是一个数组,数组返回了一个函数指针,函数返回int,函数的参数是int(*a)(int, int)所以他还是一个函数指针。

我们为什么会觉得C语言在这一个知识点上特别的难学,就是因为他同时混用了两种原则来设计语法。那你说好的设计是什么呢?让我们来看看一些其它的语言的作法:

C++:
function<int __stdcall(function<int(int, int)>)> f[10];

C#:
Func<Func<int, int, int>, int>[] f;

Haskell:
f :: [(int->int->int)->int]

Pascal:
var f : array[0..9] of function(a : function(x : integer; y : integer):integer):integer;

 

这些语言的做法,虽然并没有遵守“定义和使用相一致”的原则,但是他们比C语言好的地方在于,他们只采用一种原则——这就比好的和坏的混在一起要强多了(这一点go也是,做得比C语言更糟糕)。

当然,上面这个说法对Haskell来说其实并不公平。Haskell是一种带有完全类型推导的语言,他不认为类型声明是声明的一部分,他把类型声明当成是“提示”的一部分。所以实际上当你真的需要一个这种复杂结构的函数的时候,实际上你并不会真的去把它的类型写出来,而是通过写一个正确的函数体,然后让Haskell编译器帮你推导出正确的类型。我来举个例子:

superApply fs x = (foldr id (.) fs) x

 

关于foldr有一个很好的理解方法,譬如说foldr 0 (+) [1,2,3,4]说的就是1 + (2 + (3 + (4 + 0)))。而(.)其实是一个把两个函数合并成一个的函数:f (.) g = \x->f(g( x ))。所以上述代码的意思就是,如果我有下面的三个函数:

add1 x = x + 1
mul2 x = x * 2
sqr x = x * x

 

那么当我写下下面的代码的时候:

superApply [sqr, mul2, add1] 1
的时候,他做的其实是sqr(mul2(add1(1)) = ((1+1)*2) * ((1+1)*2) = 16。当然,Haskell还可以写得更直白:
superApply [(\x->x*x), (*2), (+1)] 1

 

Haskell代码的简洁程度真是丧心病狂啊,因为如果我们要用C++来写出对应的东西的话(C语言的参数无法是一个带长度的数组类型所以其实是写不出等价的东西的),会变成下面这个样子:

template<typename T>
T SuperApply(const vector<function<T(T)>>& fs, const T& x)
{
    T result = x;
    for(int i=fs.size()-1; i>=0; i--)
    {
        result = fs[i](result);
    }
    return result;
}

 

C++不仅要把每一个步骤写得很清楚,而且还要把类型描述出来,整个代码就变得特别的混乱。除此之外,C++还没办法跟Haskell一样吧三个函数直接搞成一个vector然后送进这个SuperApply里面直接调用。当然有人会说,这还不是因为Haskell里面有foldr嘛。那让我们来看看同样有foldr(reverse + aggregate = foldr)的C#会怎么写:

T SuperApply<T>(Func<T, T>[] fs, T x)
{
    return (fs
        .Reverse()
        .Aggregate(x=>x, (a, b)=>y=>b(a(y)))
        )(x);
}

 

C#基本上已经达到跟Haskell一样的描述过程了,而且也可以写出下面的代码了,就是无论声明和使用的语法的噪音稍微有点大……

SuperApply(new Func<T, T>[]{
    x=>x*x,
    x=>x*2,
    x=>x+1
    }, 1);

 

为什么要在讨论语法的一致性的时候说这些问题呢,在这里我想向大家展示Haskell的另一种“定义和使用相一致”的做法。Haskell整个语言都要用pattern matching去理解,所以上面的这段代码

superApply fs x = (foldr id (.) fs) x
说的是,凡是你出现类似superApply a b的这种“pattern”,你都可以把它当成(foldr id (.) a) b来看。譬如说
superApply [(\x->x*x), (*2), (+1)] 1
其实就是
(foldr id (.) [(\x->x*x), (*2), (+1)]) 1
只要superApply指的是这个函数,那无论在什么上下文里面,你都可以放心的做这种替换而程序的意思绝对不会有变化——这就是haskell的带有一致性的原则。那让我们来看看Haskell是如何执行他这个一致性的。在这里我们需要知道一个东西,就是如果我们有一个操作符+,那我们要把+当成函数来看,我们就要写(+)。如果我们有一个函数f,如果我们要把它当成操作符来看,那就要写成`f`(这是按键!左边的那个符号)。因此Haskell其实允许我们做下面的声明:
(Point x y) + (Point z w) = Point (x+z) (y+w)
(+) (Point x y) (Point z w) = Point (x+z) (y+w)

(Point x y) `Add` (Point z w) = Point (x+z) (y+w)
Add (Point x y) (Point z w) = Point (x+z) (y+w)

 

斐波那契数列的简单形式甚至还可以这么写:

f 1 = 1
f 2 = 1
f (n+2) = f(n+1) + f(n)

 

甚至连递归都可以写成:

GetListLength [] = 0
GetListLength (x:xs) = 1 + GetListLength xs

 

Haskell到处都贯彻了“函数和操作符的替换关系”和“pattern matching”两个原则来做“定义和实现相一致”的基础,从而实现了一个比C语言那个做了一半的混乱的原则要好得多的原则。

有些人可能会说,Haskell写递归这么容易,那会不会因为鼓励人们写递归,而整个程序充满了递归,很容易stack overflow或者降低运行效率呢?在这里你可以往上翻,在这篇文章的前面有一句话“好的语言,除了库写起来又容易又好用以外,还有两个重要的特点:容易学,容易分析。”,这在Haskell里面体现得淋漓尽致。

我们知道循环就是尾递归,所以如果我们把代码写成尾递归,那Haskell的编译器就会识别出来,从而在生成x86代码的时候把它处理成循环。一个尾递归递归函数的退出点,要么是一个不包含自身函数调用的表达式,要么就是用自身函数来和其它参数来调用。听起来比较拗口,不过说白了其实就是:

GetListLength_ [] c = c
GetListLength_ (x:xs) c = GetListLength_ xs (c+1)
GetListLength xs = GetListLength_ xs 0

 

当你写出这样的代码的时候,Haskell把你的代码编译了之后,就会真的输出一个循环,从而上面的担心都一扫而空。

实际上,有很多性能测试都表明,在大多数平台上,Haskell的速度也不会被C/C++慢超过一倍的同时,要远比go的性能高出许多。在Windows上,函数式语言最快的是F#。Linux上则是Scala。Haskell一直都是第二名,但是只比第一名慢一点点。

为了不让文章太长,好分成若干次发布,每次间隔都较短,所以今天的坑我只想多讲一个——C++的指针的坑。剩下的坑留到下一篇文章里面。下面要讲的这个坑,如果不是在粉丝群里面被问了,我还不知道有人会这么做:

class Base
{
  ...
};

class Derived : public Base
{
  ...
};

Base* bs = new Derived[10];
delete[] bs;

 

我想说,这完全是C++兼容C语言,然后让C语言给坑了。其实这个问题在C语言里面是不会出现的,因为C语言的指针其实说白了只有一种:char*。很多C语言的函数都接受char*,void*还是后来才有的。C语言操作指针用的malloc和free,其实也是把他当char*在看。所以当你malloc了一个东西,然后cast成你需要的类型,最后free掉,这一步cast存在不存在对于free能否正确执行来说是没有区别的。

但是事情到了C++就不一样了。C++有继承,有了继承就有指针的隐式类型转换。于是看上面的代码,我们new[]了一个指针是Derived*类型的,然后隐式转换到了Base*。最后我们拿他delete[],因为delete[]需要调用析构函数,但是Base*类型的指针式不能正确计算出Derived数组的10个析构函数需要的this指针的位置的,所以在这个时候,代码就完蛋了(如果没完蛋,那只是巧合)。

为了兼容C语言,“new[]的指针需要delete[]”和“子类指针可以转父类指针”的两条规则成功的冲突到了一起。实际上,如果需要解决这种问题,那类型应该怎么改呢?其实我们可以跟C#一样引入Derived[]的这种指针类型。这还是new[]出来的东西,C++里面也可以要求delete[],但是区别是他再也不能转成Base[]了。只可惜,T[]这种类型被C语言占用了,在函数参数类型里面当T*用。C语言浪费语法罪该万死呀……

待续

posted on 2013-04-27 01:24 陈梓瀚(vczh) 阅读(33148) 评论(37)  编辑 收藏 引用 所属分类: 启示

评论:
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-04-27 02:02 | 溪流
学习了  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-04-27 02:20 | sev
板凳了..  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-04-27 03:43 | skyshaw
“GetListLength_ [] c = x” 应该是c吧  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-04-27 04:09 | 陈梓瀚(vczh)
@skyshaw
改了  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-04-27 06:22 | 蔡东赟
mark,先下班回家看  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-04-27 06:25 | zhenqu
不明觉厉。  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-04-27 07:48 | Richard Wei
mark, 拜膜...  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a)[未登录] 2013-04-27 16:14 | 123
小陈水平又提高了 呵呵  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-04-27 18:39 | tinro
int &b; &b是int?当然,你也可以说C++不是C。int *b,和*b里面的*,不好简单作为一个含义来解释。

至于Haskell,从数学角度看很美好,从工程角度看很糟糕。  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-04-27 20:32 | clang
看不懂他想喷什么,怎么办?  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-04-27 20:37 | 不懂
虽然很认真的看了一遍,还是没看懂“如何设计一门语言”的意思,只能说楼主智商太高了  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a)[未登录] 2013-04-29 04:37 | raof01
typedef int(__stdcall*f[10])(int(*a)(int, int));
这个不是:“f是一个数组,数组返回了一个函数指针,……”。数组不会返回什么东西的啊。我觉得用typedef来解释更清楚。上面的typedef等价于:
typedef int (*A)(int, int);
typedef int (__stdcall*F)(A);
F[10];  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-05-03 23:21 | Quon
想改变现状的人做了个新的语言出来,于是想出名的人有了可以喷的东西,评论家靠发明家吃饭.  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a)[未登录] 2013-05-15 22:13 | peakflys
对于博主最后一个例子所说的“但是Base*类型的指针式不能正确计算出Derived数组的10个析构函数需要的this指针的位置的”,请问为什么不行呢?难点在哪?  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a)[未登录] 2013-05-15 22:17 | peakflys
刚才构造了一个例子,如下:
#include <cstdio>

class Base
{
public:
Base(const int a=10) : a(a)
{
printf("Base constructor,this addr: %p\n",this);
}
virtual ~Base()
{
printf("Base destructor, a=%d, this addr: %p\n",a,this);
}
int a;
};
class Derive : public Base
{
public:
Derive(const int a=20,const int b=20) : Base(a),b(b)
{
printf("Derive constructor,this addr: %p\n",this);
}
virtual ~Derive()
{
printf("Derive destructor, b=%d, this addr: %p\n",b,this);
}
int b;
};
int main(int argc, char* argv[])
{
Base *pb = new Derive[3];
delete [] pb;
printf("over\n");
return 0;
}
特意在基类和派生类里各加了一个成员变量,来增大两者可能的指针偏移。
结果证明 delete 多态的指针后,内存正常释放,而每个派生类对象的this指针也均无差错!
编译器:g++ (GCC) 4.4.6 20120305 (Red Hat 4.4.6-4)  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-05-16 00:04 | peakflys
最后一个例子不是什么坑,只不过是ISO未定义的情况,其实要实现基类指针正确找到派生类this偏移的方式很多种,记下大小,加上固定偏移就是一种方法,当然还有其他很多种方法,应该没什么特别的复杂,这里顶多告诫大家要慎用,因为是标准未定义的行为,crash or not crash,compiler decides it。建议博主修改之。  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-05-16 17:17 | weslee
那么烂人永远都是烂人,不纠正自己的心态永远获得不了任何有用的知识,永远过那种月入一蛆的日子,用垃圾的语言痛苦的写一辈子没价值的程序。  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-05-25 22:56 | Marvin
我对一门好的语言的定义是
你对语言的定位是什么,它恰当的解决了你定位的问题。

个人觉得,如果按我的这种定义,只有2个语言很好。一个是C,一个是Ruby.

如果你不懂,我给你举个例子。C++号称要代替C成为主流开发语言。它是要在C的基础上加入面向对象。可结果是1.在关键的时候,还点用C而不是用C++,比如写操作系统。2. C++号称是要在C的基础上加入面向对象,这个目标完成的怎么样我不做评论,但它只加入了面向对象吗?那个++到底要加什么,恐怕连C++的设计者也不清楚。
再一个例子,Java看到C++有问题,号称叫C++--,它减掉了C++不好的东西了吗?

按我的理想,真正好语言还没有出现。不然就不会出来这个多号称要改变这个,改变那个的语言了。

另外,我最常用的语言是C++, JavaScript. 也用过C#, Java, Obj-C, Ruby, PHP, Go等  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-05-25 22:57 | Marvin
Sorry,上面的主页地址写错了  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-05-28 19:19 | 陈梓瀚(vczh)
@Marvin
问题解决的再好,语言设计的烂也没用,看yacc,看antlr,只会让你学起来痛苦。一门好的语言,首当其冲就是“到处都没有特例”,其次才考虑怎么良好的解决某些问题——不过像C++这种语言其实可以解决大量的问题的同时也解决的好,缺点是你要经过大量的训练,很少人可以做到。学起来太难,好处就被抵消了。  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-06-03 21:09 | Marvin
@陈梓瀚(vczh)
C++有难度,我承认,但好像你没有看明白我上面写的内容。关键问题是C++增加的这些难度是否是值得的,能有效解决本质问题的方法是最好的。  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-06-04 19:44 | 陈梓瀚(vczh)
@Marvin
这些难度100%是因为兼容C语言+RAII而必须产生的。兼容C是值得的,RAII也是值得的,所以都是值得的。你无论去掉哪个,要么就不兼容C,要么RAII就不完整。  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-07-13 05:51 | 幻の上帝
@陈梓瀚(vczh)
需要RAII是静态语言+静态类型系统的原罪。
WG21本可以不照抄ISO/IEC 9899的一堆translation phases,允许C++提供动态语言的接口。结果还是抄了。为什么?怕无所适从?照顾习惯?市场原因?
的确C的渣是C++设计最重大的包袱。但要说100%就过了。举几个C++原创的渣设计:
1.new-expression的语法。
2.复杂value category的必要性。
3.混合不同的类型系统导致无法交互。这样说可能太抽象,一个显眼的问题是模板为什么要独立于类型系统之外?操作符sizeof...为什么不能由库实现?
当然关于类型系统C有更多负资产。不过至少就这几个例子不可能是C的过错。
  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a)[未登录] 2013-07-13 09:53 | 陈梓瀚(vczh)
@幻の上帝
RAII的原罪显然是内存太贵,这个根本没有别的原因,你怎么不说GC是原罪呢。真实的

我就不说你那个乱七八糟的评论了,至少
1:template怎么就在类型系统以外了
2:sizeof要用库实现也不是不可能,至少在我现在在设计的语言里面就是可以的,不过这个东西是C的事情,C可没有什么“用于偏特化的递归模板类型”这一功能的,你让他怎么做?  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a)[未登录] 2013-07-13 09:55 | 陈梓瀚(vczh)
@幻の上帝
我怕你看不明白再说一句,为什么python打开文件后也要close,还不是因为没有RAII,你又不能等gc来日,只好自己动手了吗。  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-10-20 00:38 | 幻の上帝
@陈梓瀚(vczh)
GC当然也是原罪。是不是必要呢,这是另外一回事了:不过我确信在静态语言里强迫用户使用GC更愚蠢,RAII在这里当然是更好的解决方案。所以关于RAII的必要性你没必要对我科普。
根源在于向机器明确资源管理的逻辑是程序员的义务——现在显然没有AI能从根本上精确猜测用户的意图以自动分清楚哪些资源是适合确定性析构的,而哪些更适合延缓回收作为优化。
1.无论如何,C++的template不是type。无论从概念定义还是实用上来讲template都和C++的type有显著区别。尽管有些东西有共性,但在语言规则上做不到。就算不管这个,还得考虑一些上下文:如C++11对T&&的类型推导规则就因为T是或者不是类型参数而有所不同。
2.省略号没被吃吧。是sizeof...不是sizeof。你试试看,打算重载几个?别忘了模板非类型参数和模板模板参数。


  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-10-20 00:41 | 幻の上帝
还有,这里我当然没有在说C,C在这上面的无能显而易见。
我是在列举C++原创的没有照搬C的比较欠抽的设计。一些问题不能在C++里解决说明C++在这里的失败——这也当然不因为你能发明新语言而当作解决了因此可以无视掉。

  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-10-26 16:54 | Mr.Okay
楼主您好!我是个语言设计方面的爱好者和学习者。您的文章真是写到我心里去了——我不是专业的,所以不可能像您这样表达的这么清楚。对于C语言在语法方面的不一致,我一直想知道,下面这个例子是不是一个体现:
int a, *b;
int* F(int a, int b);
第一行里,它希望你把*b看为整数,而第二行又希望你把int*当作一种类型。  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-10-27 22:04 | 陈梓瀚(vczh)
@Mr.Okay
所以说这就是坑啊  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2013-11-01 10:18 | 幻の上帝
不吱声么。
觉得sizeof...无厘头的话就换一个比较能造成实际困难的例子。
self-application lambda abstraction (λu.u u)在C艹里要写成什么类型的?如何用一个name表示这个在类型论意义上完全可以就是type但C艹就是不认(也构造不出)的东西?
简而言之,除了在递归类型上的不必要限制(函数不能声明自身类型为参数)外,C艹声称:
全称类型(这里的模板)不是C++类型。C++类型不被量化。
愚蠢的设计。
  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2014-05-11 12:29 | xnature
看到一半看不懂了

“就是因为他同时混用了两种原则来设计语法”

哪两种?前文似乎只说了一种啊  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2014-05-11 12:38 | xnature
前文开始C++定义中的function是什么东西啊?  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2014-05-11 12:54 | xnature
最后一个例子在C++中正确的做法应该是怎样的?  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2014-05-12 07:39 | 陈梓瀚(vczh)
@xnature
std::function是C++11的一个类  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2014-05-12 07:39 | 陈梓瀚(vczh)
@xnature
最后一个例子的正确做法当然是Derived* ds = new Derived[10];  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2014-07-03 07:56 | dramforever
没人注意haskell? 楼主一定没测试GetListLength
函数第一个字母小写哦  回复  更多评论
  
# re: 如何设计一门语言(一)&mdash;&mdash;什么是坑(a) 2014-10-26 19:00 | 幻の上帝
借个地方。
关于sizeof...那个嘛。。Google Groups上不去(本来应该回复在跟Nicol Bolas的讨论的)……
http://en.wikipedia.org/wiki/Pure_type_system
看来早几年我又在发明无聊轮子了。
(BTW,f*ck WG21/N1919。)
  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理