crystal-chinaChina
  • 文档
  • Github
  • 注册
  • 登录
目录
  • 前言
  • 简介
  • 安装 ➤
    • 包管理
  • 写给 Rubyists ➤
    • 类型
    • 方法
    • 代码块
    • 杂项
    • 性能因素(WIP)
    • 迁移 Ruby 代码到 Crystal
  • 基础知识
  • 查找性能瓶颈 (WIP)
  • 交叉编译

杂项

最后编辑于: 2025年03月09日

§ 运行代码方式

Crystal 是一门编译型、静态类型的语言,因此你需要先编译,再运行。

假设你有一个 foo.cr:

   1  
   2  # Crystal
   3  puts "Hello world"
   4  

你需要首先 build 它

   1  
   2  $ crystal build foo.cr
   3  

它在当前文件夹下,创建了一个可执行文件叫做:foo,然后你可以运行它。

   1  
   2  $ ./foo
   3  Hello world
   4  

当作为最终发布版分发时,记得添加 --release, 这开启了最高级别的 llvm 优化,它带来了好得多的性能,但需要更长的 build 时间。

当执行基准测试时,请总是记得开启它。

   1  
   2  $ crystal build --release foo.cr
   3  

crystal build --help 来获取更多的帮助。

§ 迭代器(Iterator)

Crystal 额外引入了 Iterator 类型(等价于 Ruby 中的 Enumerator::Lazy)

通常来说,当调用一个 Enumerable 方法,例如:#each, #map 等,但是没有代码块时, 会返回一个 lazy 的 Iterator 对象。

举个例子:我们希望取出一千万以内的前三个偶数,并将其 ✖️ 3。

   1  
   2  (1..10_000_000).select(&.even?).map { |x| x * 3 }.first(3) # => [6, 12, 18]
   3  

上面的写法是工作的,但是建立了数个不必要的中间数组,我们将其分解如下:

   1  
   2  (1..10_000_000).select(&.even?) # => 返回一个大小为 500 万,元素全是偶数的数组
   3  
   4  map { |x| x * 3 } # => 将上面的的所有元素✖️3
   5  
   6  first(3) # => 最后取出前三个
   7  

这带来额外的计算以及不必要的巨大内存消耗,因为我们只对前三个元素感兴趣, 但是却返回了 500 万个,并计算,但最后只取了前三个,其他被抛弃。

更高效的做法,首先通过 each 返回一个 lazy 的 Iterator,因为 Iterator 重新定义了很多 Enumerable 的方法,例如:上面的 #map, #select, #first, 调用这些方法,返回了一个 包裹调用者 Iterator 对象的新的 Iterator 对象,在调用链的最后,我们仍然得到的是一个新的 lazy 的 Iterator 对象,没有任何实际计算,

   1  
   2  (1..10_000_000).each.select(&.even?).map { |x| x * 3 }.first(3) # => #<Iterator::FirstIterator ... >
   3  

分解如下:

   1  
   2  (1..10_000_000).each # => <Range::ItemIterator ...>
   3  select(&.even?) # =>  Iterator::SelectIterator(<Range::ItemIterator ...> ... >>
   4  ...
   5  first(3) # => #<Iterator::FirstIterator ... >
   6  

当我们希望取出值时,我们可以再次调用 #each(&) 或 to_a

   1  
   2  first_three_iter = (1..10_000_000).each.select(&.even?).map { |x| x * 3 }.first(3)
   3  
   4  first_three_iter.each do |x|
   5    p x # => 直到这里才实际执行计算。
   6  end # => 打印 6, 12, 18
   7  
   8  # 因为 Iterator 是单向的,上面已经通过 each(&) 将所有的三个结果都取出了
   9  
  10  first_three_iter.to_a # => [],因此 to_a 返回空数组。
  11  

正如你猜想的那样,你也可以通过 Iterator#next 方法,一个一个的将元素取出。

   1  
   2  iter = (1..5).each
   3  
   4  iter.next # => 1
   5  iter.next # => 2
   6  typeof(iter.next) # => Int32 | Iterator::Stop
   7  

创建一个新的 Iterator (Ruby 里面叫做 Enumerator) 也是可以的, 需要下面的几步:

  1. 创建一个类,并且混入 Iterator(T) 模块
  2. 定义一个 #next 方法,返回下一个元素
  3. 在达到结尾时,调用 #stop 方法返回一个 Iterator::Stop::INSTANCE

例如,下面是一个 Zeros 类,返回指定数量的 0

   1  
   2  class Zeros
   3    include Iterator(Int32)
   4  
   5    def initialize(@size : Int32)
   6      @produced = 0
   7    end
   8  
   9    def next
  10      if @produced < @size
  11        @produced += 1
  12        0
  13      else
  14        stop
  15      end
  16    end
  17  end
  18  
  19  zeros = Zeros.new(5)
  20  
  21  zeros.each {|e| print e } # => 00000
  22  

§ require 用法不同

§ Crystal 移除了 require_relative 方法

代之,可以直接使用 require 来引用相对路径的文件。

   1  
   2  require "./foo"
   3  

并且支持和 File.match? 一样的增强版的 shell filename globbing

  • * 支持任意数量的除了目录分隔符之外的任意字符。

例如:require "./foo/*",匹配 foo 目录下的所有 cr 文件, 但不含子目录。

  • /** 递归的匹配所有子目录中的 cr 文件

例如:require "./foo/**",匹配 foo 目录以及所有子目录下的所有 cr 文件。

§ $CRYSTAL_PATH

类似于 Ruby 在 $RUBYLIB 从前往后中查找 lib 文件夹来确定引用的 gem, Crystal 等价的环境变量 叫做 $CRYSTAL_PATH, 可以通过 crystal env CRYSTAL_PATH 来取得这个变量的默认值

   1  
   2   ╰──➤ $ crystal env CRYSTAL_PATH
   3  lib:/home/zw963/Crystal/bin/../share/crystal/src
   4  

可以看到,$CRYSTAL_PATH 默认仅仅包含 当前目录下的 ./lib 以及本地安装的编译器的 标准库相对路径. ~/Crystal/share/crystal/src,我们将指定的文件夹加入到到 $CRYSTAL_PATH中,使用冒号(:) 分隔即可。

例如:

   1  
   2   ╰──➤ $ export CRYSTAL_PATH=new_folder:$(crystal env CRYSTAL_PATH)
   3  lib:/home/zw963/Crystal/bin/../share/crystal/src
   4   ╰──➤ $ crystal env CRYSTAL_PATH
   5  new_folder:lib:/home/zw963/Crystal/bin/../share/crystal/src
   6  

§ require 查找策略

我们假设这个加入 $CRYSTAL_PATH 的文件夹叫做 CPATH, 当我们 require "foo" 时,会按照如下顺序查找:

  • CPATH/foo.cr (1) 简单的 foo.cr
  • CPATH/foo/foo.cr (2) foo 替换为 foo/foo.cr
  • CPATH/foo/src/foo.cr (3) 和 (2) 类似,只不过 第一级 文件夹后面加了一个 src 文件夹。

上面的 foo 可以扩展成 a/b/c 这种形式,例如,require "foo/bar/baz", 会查找:

  • CPATH/foo/bar/baz.cr
  • CPATH/foo/bar/baz/baz.cr
  • CPATH/foo/src/bar/baz.cr

可见仍旧满足上面的策略,只不过将 foo 替换为 foo/bar/baz 而已。

💡 小提示

注意:require 绝对路径是不支持的。例如:require "/some/aboslote/path" Crystal 没有类似于 Ruby 中 load 方法的等价物,但是可以通过下面的 read_file 宏(macro) 来达到同样的目的

   1  
   2  # 这个 macro 相当于把文件 path.cr 里面的内容物理粘贴到宏调用位置
   3  {{ read_file("/some/alsolute/path.cr").id }} 
   4  
previous_page代码块

杂项

next_page性能因素(WIP)
欢迎在评论区留下你的见解、问题或建议

正在预览...
正在读取评论...
Crystal China
admin@crystal-china.org
githubtwittercrystal-lang