Crystal Crystal 语言, 书写 Ruby 式语法, 编译执行代码

jiyinyiyong · 2013年12月29日 · 最后由 wilikeit 回复于 2018年05月13日 · 11973 次阅读

玩的时候用了 .cr 作为后缀,结果被 GitHub 识别成其他语言了, 跑过去一看,真有这么个编程语言抢我后缀.. Crystal 语言,Ruby 的语法,代码是编译执行的:

http://crystal-lang.org/

# Compute prime numbers up to 100 with the Sieve of Eratosthenes
max = 100

sieve = Array.new(max, true)
sieve[0] = false
sieve[1] = false

(2...max).each do |i|
  if sieve[i]
    (2 * i).step(max - 1, i) do |j|
      sieve[j] = false
    end
  end
end

sieve.each_with_index do |prime, number|
  puts number if prime
end

搜索论坛被提到过一次 http://ruby-china.org/topics/10389

Crystal 的博客上提到下面几点:

  • Garbage Collector
  • Bootstraped the compiler
  • Type inference (part 1)
  • Shortcut syntax for writing one-argument blocks
  • Null Pointer Exception

具体代码还是 pre-alpha 的... 等正式发布玩一下... 不过不知道发布要几几年啊..

用的 ruby-llvm 之前也想用来着 但是本地一直没装上 T_T

我也初步玩了下 感觉确实不赖啊 不过很奇怪为何单引号没有实现??

语法好亲切。

疑惑起来了.. 在知乎发了个帖问 http://www.zhihu.com/question/22399630 自己在学做很简单的脚本语言,, 类型推断究竟能多大程度让动态语言有静态语言的效率呢? 是所有的语言,能够类型推断的话,就都能够被编译成二进制代码吗?

#4 楼 @jiyinyiyong

不能啊,类型推断算法本身有复杂度限制啊 ...

Inferring Recursive Data Types 里面总结了几种不同的类型系统,对应的类型推断算法的复杂度,有几种是 P/NP HARD 的 ...

是所有的语言,能够类型推断的话,就都能够被编译成二进制代码吗?

不需要类型推断,也能编译成二进制代码的。只是运行起来可能还没解释执行快

看了下官网,这个语言好像最近几个月才有 GC...

#5 楼 @bhuztez 我理解的类型推断,就是基于递归的,对前束范式,把全称量词都替换掉,除此之外还有其他途径吗?

#7 楼 @rasefon 我看不懂那么多术语 ....

#8 楼 @bhuztez 就是倒 Ax x->y,然后把 x,y 用各种实际的类型去替换,比如 int,bool 这类,然后就可以做比较了。

有时候这种类型变量(占位符)可能不是只有一种类型,会是一个可能的类型的集合,这种时候就比较难办了。

#5 楼 @bhuztez #4 楼 @jiyinyiyong

JIT 不就是编译成二进制码执行吗

#10 楼 @rasefon

我理解的类型推断相当于在编译期运行个类似 Prolog 的东西

比如

fun f(a, b) { return a+b; }

改写成

f(A, B, Ret) :-
  +(A, B, Ret)

就好了

#12 楼 @bhuztez 这个例子里面的类型,假如这个语言只支持 int,那么类型就应该是 int x int -> int。

#4 楼 @jiyinyiyong 编译到二进制很简单的,把 vm 指令都改成函数调用就可以了,其实和解释执行区别不大... 如果把 vm 指令的实现内联进来,就能省掉 call 的开销,但编译出来的结果就会很大 (这里很矛盾的,如果用提取公共子表达式之类的优化手段,就退化回前面那种代码了). 所以一般都选择性的内联,和选择性的优化 (编译时间比执行时间还长的话,即时编译也没有意义了)

编译期类型作用就是去掉运行时的类型检查和方法查找开销,如果调用目标代码固定了就能内联进来 -- 但动态语言就是不固定的,很多脚本的大部分代码就是只运行一遍,延迟到运行时检查速度更快。还有个作用是利用数据类型的知识,减少从 tagged pointer 拆箱/装箱的开销,减少从对象取成员/写成员的开销 -- 但是这种优化之前往往要做逃逸分析,对象逃不出去才能这么做,而只要语言支持 continuation 或者比较强的运行时反射,基本都逃逸了... 另外,声明成 immutable 的变量就可以不用做逃逸分析直接拆箱。

#14 楼 @luikore 都设计成传值调用不就不用关心逃逸变量了嘛哈哈。

#14 楼 @luikore 术语超多 >_< 还只会很基础的解释器... 直观感觉是一路 type assertion...

if block.Tag == "block" {
  if item, ok := block.Value.(context); ok {
    runtime := Env{}
    for i, para := range item.args {
      // println("i is:", i)
      // debugPrint(xs)
      if token, ok := para.(cirru.Token); ok {
        runtime[token.Text] = cirruGet(env, xs[i+1:i+2])
      }
    }
    for _, line := range item.code {
      if exp, ok := line.(cirru.List); ok {
        ret = Evaluate(&runtime, exp)
      }
    }
    return
  }
}

https://github.com/Cirru/cirru-gopher/blob/master/block.go#L25

距离编译好远..

#16 楼 @jiyinyiyong 果断 Erlang 搞起啊 ...

#17 楼 @bhuztez 直接 elixir 啊

#18 楼 @krazy Elixir 丑爆了 ...

#17 楼 @bhuztez 太跳跃啊,为啥要 Erlang?

#19 楼 @bhuztez 仔细一想 =/= =:=操作符,各种 module attributes 和表达式后面的逗号句号以及字符串处理也没什么不可以接受的...

#16 楼 @jiyinyiyong 编译器和解释器代码构造相似,真正干事情的地方改成打印或者拼字符串就可以了。你可以先弄个输出 js/C 代码的编译器,会玩了就可以尝试输出 llvm 字节码了 (如果用 llvm 的话,建议实现语言选 C++, 可以少走很多弯路). 想直接输出 x86 也不难,可以看看 libjit 和 xbyak, 不过要先学习 calling convention 和熟悉 stack layout.

.NET DLR 上做语言更简单,把树给它就好了。

关键词都给你了,自己看 wiki 咯。

#22 楼 @luikore thanks, long way to go.

ruby 出世的时候,为什么没做成静态语言,那就没 jruby、crystal 什么事了

wilikeit 回复

感觉做成静态的会丢失很多的用户... Gradual Type 还是不大一样

jiyinyiyong 回复

不过静态语言的话,做成 ruby 那种,性能估计也不能保证

需要 登录 后方可回复, 如果你还没有账号请 注册新账号