用 Crystal 来写东西,看起来像是能用 Ruby 那样的技巧,但实际上你是在写静态语言,有各种类型限制、转换什么的,其实感觉和写 Go 差不多
然后 Crystal 里面 Hash 和 NamedTuple 比较费神,在 Ruby 里面我们惯用 { foo: 1, bar: 2 }
这样的方式,在到了 Crystal 里面,这种是不可变的,于是有了 Hash(可变)NamedTuple(不可变)的区别
{ :foo => 1 }
, { "foo" => 1 }
这样是 Hash{ foo: 1 }
这样声明是 NamedTupleCrystal 里面,那些例如 downcase!
gsub!
这样 Ruby 常用的改变自身,减少内存 Copy 来提升效率的方式,没有了,如同 NamedTuple,String 也 immutable 的,你只需要:
foo = foo.downcase.gsub(/[a]/, "f")
当然,也有好的地方
a = nil
a = 1
a = "foobar"
# a is Int | String | Nil
a 就同时是 Int
, String
, Nil
3 种类型。
这个是用来达到动态语言体验的方式。
Crystal 如同 Ruby 一样,所有类型都是基于 Object,于是乎:
可以任意类型变量
a.to_s
a.as(String)
a.blank?
a.inspect
… 甚至可以做各种扩展
它也可以像动态语言一样,写 Bash 执行脚本,某些场景你可能需要用它:
#! /usr/bin/env crystal
puts "hello world"
然后你就可以动态执行:
$ chmod +x ./hello
$ ./hello
Crystal 目前的源代码,大量标准库的实现都是用 Crystal 实现的了,你能很容易看懂,了解原理,找到 Bug,甚至改进它,而 Ruby 里面大量都是 C 的实现。
内建 Format 工具,帮助你格式化代码,像 go fmt
一样,再也不用担心代码风格的事情。此外,在 Crystal 里面,你不在会纠结 '
或 "
的风格问题,Crystal 里面,单引号用于 Char,String 类型必须用双引号。
Crystal 内置 spec 用于编写测试,体验如同 Ruby 一样一样的,很好!
大家都容易关注到这点,这也是用它的原因。
官方页面以及各种介绍的地方都在提性能,我的一些感受:
eval
的部分,改用 macro
代替动态特性(编译期执行)等等动作,Ruby 要这么做,应该也能有很大的提升。例子:
require "http/client"
ch = Channel(Int32).new
10.times do
spawn do
res = HTTP::Client.get("https://ruby-china.org")
ch.send(res.status_code)
end
end
10.times do
status_code = ch.receive
puts "status: #{status_code}"
end
我用它写过一个小工具,可能关注过的人都看过,用它写绿色文件的好处。
但实际上没有那么美好,用 Crystal 写东西,编译出来的,虽然是 Native 的可执行文件,但实际上由于设计的原因,lib 库是动态链接,于是你写好的东西,不能像 Go 一样,拷贝一个文件给别人就能跑,还需要安装 libevent, libbdw-gc ... 我看到 GitHub Issue 上有提到过可以编译成静态链接的方式,但似乎这个在 macOS 上不行。
对于不熟悉 C,GCC,LLVM 的人来说,编译是个比较复杂的事情
练习写了两个小东西:
看上去确实没那么美好啊。感觉语法要做的跟 Ruby 一致这点会拖累了它,制约它的发展。但如果不这么做,它还有什么意思呢。想要性能和效率的平衡,还不如用 Elixir 了
其实 Crystal 的作者目标不是完全要做成 Ruby 那样,而是继承 Ruby 优秀的部分,解决不足的部分,例如,上次我问过一个关于 SecureRandom
文件 require 的名字问题。
Ruby 里面是:
require "securerandom"
Crystal 里面是:
require "secure_random"
作者说,这么做是改正 Ruby 的错误,这证明 Crystal 不是在盲目的做成 Ruby 那样,而是在取其精华。
同样,Crystal 也有自己的特点,例如 overload method,而不是 Ruby 那种 options 的动态方式:
def foo(name : String)
end
def foo(name : Integer)
end
Ruby:
def foo(name)
if name.is_a?(String)
elsif name.is_a?(Integer)
end
end
这样做的目的,当然有很大一部分原因是为何性能,减少运行期的不必要开销。
至少我目前看到 Crystal 继承 Ruby 的部分已经很不错了,例如:
主要还是吸收 ruby 的语法,语言是否优雅还是看个人,不过我觉得 crystal 已经做得足够棒了。
性能方面 benchmarks crystal 一般是跟 java,rust 对打,比 c 慢一点。就是并发不尽人意,等下一个大版本应该会好了。
overload method 这个确实不错。
兼容语法这个,虽然长得像,但经你帖子里一说,其实很多语义都不太一样,我反而会觉得更不清不楚了,还不如换个语法来的痛快。
另外,我不觉得“放弃以前的思维方式”是个坏事,学一个新东西,肯定会有各种新的思维方式的冲击,这个也是学习的一个副产品。其实 Crystal 也有很多新的东西吧,不是说长的像,就不需要用新的思维方式来考虑问题了。 (当然,我对 Crystal 完全不懂,有问题的地方请指正)
Crystal 吸收的不仅仅是 Ruby 的语法,光说语法太弱了,谁都能模仿,但那些骨子里的风格不是谁都能做的,就如同那些模仿 Rails 的,都只是长得像而已,实际上那些的作者都不懂 Rails 的理念。
在这点上,我认为 Crystal 做到了,看起来几个作者貌似还不是 Ruby 社区很有名的人物(以前都没听说过),但从我了解这段时间来看,Crystal 在继承 Ruby 的思想、理念方面真的很不错,在你实际用的时候能感受到 Ruby 给我带来那些东西。
这个不太好描述
这三个我都花了写时间写过一点,当然接触不是太深入,一些感受而已
Go, Elixir 是挺不错的,但问题是,为何我要放弃我认同、并热爱的 Ruby 那套方式?就为了性能?
我也觉得语法接近是一个问题,不一致就索性重写,拥抱另一个生态圈。接近又不完全兼容使得重写仍然需要很大的工作量,而现在 crystal 的生态也不够成熟,不看好 Ruby 项目向 Crystal 迁移。
偶尔关注下 Crystal,不过还是更看好 Elixir 多一些。个人感受是单纯的函数式编程并没有跟传统的 OO 有非常大的差别,相反很多理念是可以通用的。造成 Elixir 最大思维差异的是 process 的设计,以及衍生出来的 OTP 的那些东西,这都是 Erlang 特有的思路,函数式编程语言中貌似也就此一家。
喜欢 Elixir 的一个主要原因是,它是一个考虑到应用层级的解决方案,而不仅仅是提供一个语言。Mix 提供完整的项目构建和管理,ExUnit 提供完备的测试框架,Application 提供的组件化单元,Supervisor 提供的监控和重启机制(后两者都是 OTP 的范畴了),release 提供的打包和热更新…… 虽说 Elixir 很新,但得益于 Erlang 三十年的发展,各种基础组件都还比较稳固。不像其他语言要从地基开始重新做起。我觉得大多数基础设施还是需要时间积累的。
Scala 10 年的时候靠着 actor 火了一两年,顺便把 erlang 的热度也带起来了。但是过了 12 年之后,go 和 docker 相继出来就没影了(相对而言)。 scala 的 play 和 erlang 的 chicagoboss 都是相当有特色的 fullstack framework,也是该死的死,该沉寂的沉寂。
老实说,elixir 我觉得能活跃个一两年,之后还是得回归小圈子,自己默默耍着。至于 crystal,看看每月众筹的进度,能不死就烧香拜佛了
原生的 Erlang 综合起来比 Elixir 要好,尤其是二进制处理的语法。 不管是 Erlang 合适 Elixir 都有一个致命缺点,语言的抽象能力不够。或者说没有 OO 给予类的语言使用方便。 Scala 个人感觉不错。主要是你需要有 FP,OO 的编程背景。
我最近安装了 Linux, 下面用了 prax.cr 这个替代 pow 的工具,它就是用 crystal 重写的。看看能不能解决我关注的 Ruby 缺少类型检查的 问题。
时刻保持一颗用于学习的心,然后你需要:
分清事情重要或不重要,紧急或不紧急;
适当的时候要学会“拒绝”需求,或推迟;
不要顺着需求方的需求、思路来解决问题,要跳出来看;
尝试用简单的方式解决问题;
整理自己的一套最佳实践,反复应用、验证、并改进;
参考、借鉴、学习他人的实践方式;
以及用 Ruby / Rails,然后你就自由了
Crystal 入口文件编译就可以了,例如
b.cr, a.cr
# a.cr
require "./b"
puts "hello world"
运行或编译:
$ crystal build a.cr
OO 又是一个老话题了,编程范式跟着业务抽象来,什么合适用什么。比如 GUI 编程,没有 OO 怎么做?不要拿到锤子就把所有东西当钉子敲。通用编程语言应该支持所有编程范式。
可以这么理解,我主要说的是类型 OO,而不是概念 OO, erlang 中的类型明显没有 OO 语言的类型功能强大,封装,多态,和继承。recorder,和 map 的语法也不是很简单。
有个比较奇怪的地方是 Crystal 的类型推断,这几乎是这几年最奇怪的类型推断了。举个例子来说,如果这是一段 TypeScript 代码。
function test(param: String | number) {
if (param instanceof String) {
return 'Oops'
}
return param * 2 // 此处 param 必是一个 number,所以不会有错误抛出
}
test(1) // => 2
test('wat') // => 'Oops'
而 Crystal 中
def test(param : String | Int32)
if typeof(param) == String
return "Oops"
end
return param * 2.0 # no overload matches 'String#*' with type Float64
# Overloads are:
# - String#*(times : Int)
end
puts test(2)
puts test("wat")
无论是把另一个类型放在 return 后还是 else 中都无非规避掉这个问题。也就是说一个方法内某个参数类型的推断并不是基于行的,而是基于方法定义时指定的。这对于一些复杂的情况来说就变得很麻烦,需要大量地依赖重载的方式才能定义完。
类型判断使用 is_a? 而不是 typeof。 https://play.crystal-lang.org/#/r/244o
惊了,原来是这样。。。我一开始直接试着写了
param.is_a?String
给我报了 undefined method 'is_a?' for Int32
,我还以为这玩意没有 is_a?
方法。。。我才用的 typeof
。没想到在 is_a?
后面加个空格就好了。。。这是什么魔法。。。
关于 typeof 的说明官方文档也有提到:https://crystal-lang.org/docs/syntax_and_semantics/typeof.html
crystal 发起人写的关于 typeof 的一些魔法使用 https://crystal-lang.org/2015/08/24/its-a-typeof-magic.html
因为 is_a? 是一个方法,在 ruby 和 crystal 正规使用中都可以采用如下两种方式调用:
is_a?(arg)
is_a? arg # <-- 省略括号
比较奇怪的是,Ruby 中 ?
必须出现在方法名的末尾,所以当你写
3.is_a?Integer #=> true
可以被正确返回,在 Crystal 里当你这么写的时候,我认为 Crystal 也已经把它认为是方法了,因为它报的错误是:
undefined method 'is_a?' for Int32
但我觉得 Crystal 应该是在处理 is_a?
方法上有什么魔法,让这里不工作了。
实际上这样是不好的习惯,可能是 ruby 单独处理,官方文档的代码范例都是符合规则的 https://ruby-doc.org/core-2.4.1/Object.html#method-i-is_a-3F
比如:
ruby
2.4.0 :010 > "String".is_a?String && false
TypeError: class or module required
from (irb):10:in `is_a?'
from (irb):10
from /Users/wiiseer/.rvm/rubies/ruby-2.4.0/bin/irb:11:in `<main>'
2.4.0 :011 > "String".is_a? String && false
TypeError: class or module required
from (irb):11:in `is_a?'
from (irb):11
from /Users/wiiseer/.rvm/rubies/ruby-2.4.0/bin/irb:11:in `<main>'
2.4.0 :012 > "String".is_a?(String) && false
=> false
crystal
icr(0.22.0) > "String".is_a? String && false
=> false
icr(0.22.0) > "String".is_a?(String) && false
=> false
从如上对比可以看出来 crystal 的语法解析才是正确的。
这两天也学习了一些 Crystal 的官方文档,说说自己的感受。 首先 Crystal 的作者对这个语言的定位很好,提供的 future 尤其是并发机制是很不错的。类似 Golang. 但是,我们应该注意到一个语言想要发展的好光靠好的卖相是远远不够的,Crystal 现在还处于非常初级的阶段。就编译工具链都没有齐备。调试器,分析器等等。语言最核心的并发机制和 GC 也是非常初级。最总要的是没有一个大公司推动。和 Kotlie 的比较一些。你会发现 Crystal 的前景暗淡。很难在短期内爆红。个人倒觉得 Crystal 如果支持解释执行就厉害了。开发时解释执行,生产环境编译。
上週才看到這篇,本來想說 brew install 裝起來後,build 個 puts "hello word" 確定會動就好,竟然連玩兩個小時沒停擺,坦白說我超喜歡這樣的設計,這幾年拿來當開發主力的 GO 雖然效率不錯,可是寫起來挺囉唆的,Crystal 這 ruby 語法卻又兼有 type, 我認為它在表達性 (expressiveness) 與精確性 (Explicitness) 取得一個很好平衡,更難能可貴的是不用犧牲效率。