const int是什么类型_Hello, 类型系统

97bd25579cb6d6ce9476719eb59492be.png

人类思维是不考虑类型的。要写类型的语言,都会妨害写代码的思维流畅度。设计得差的类型系统,还会妨碍你写出通用的代码,为了适应它,你不得不学习奇特的技巧和设计模式。比较强力的类型系统,写类型 spec 可能是普通代码量的好几倍,经常好几小时跑不完,完全检查几乎不可能。

另一方面,人类思维也经常出 bug,怕改错代码也会需要一些额外手段去保证程序的正确性。理想的类型标注是程序正确性的证明 —— 但现实生活中 QA 员工才是程序正确性的“证明”……

这次 Hello World,就造一个半吊子的类型系统。

类型是什么

众所周知,类型就是集合。构造主义要求高些:类型是可构造的集合。构造法有以下几种:

有些集合可以被"计算",也就是可以一一到上至某个自然数的子集。编程中碰到的所有类型其实都是可枚举的。

几个例子:

  • ⊥: {}
  • Bool: {true, false}
  • Int: {0, 1, -1, 2, -2, 3, -3, ...}
  • Prime: {1, 2, 3, 5, 7, 11, ...}
  • Double: {(double)0x0, (double)0x1, (double)0x2, (double)0x3, ...}
  • String: {"a", "b", "aa", "ab", "ba", "bb", ...}

通过“精炼类型” (Refinement type) 的方法,也可构造:

  • Odd: f(Int) , f(x) = 2x + 1
  • UnsignedInt: {x | x <- Int, x >= 0}

或者说,可枚举的集合,通过对其元素列表领悟 (List comprehension) 操作,产生的结果也是可枚举集合。

函数也可以有类型,函数类型(Function type)是类型系统开始变复杂的地方。在类型推断很弱或者没有的静态类型语言里写 lambda 很累。

参数化类型(Parametric type)允许定义类型函数,通过这个函数复合出新的类型。

类型函数如果支持非类型的参数,那就是依赖类型(Dependent type)。

如果类型没法在编译时检查好,得在运行时检查,那就是契约(Contract)。

类型还有两种复合:和与积(Union, Cartesian product)。

子类型(Subtyping)在很多语言中很常见,但其使用场景可以用类型复合代替。

我们还有行为类型(Behavioral type),通过对象所支持的操作来定义。不过行为类型的计算不太好区分编译时和运行时,这里暂且不搞。

综上需求,先给我们的轮子设计点语法。

表达类型的语法

如果要进行编译期类型检查,往往需要一套特殊语法,把类型表达式和普通表达式隔离开来,方便在编译期完成类型表达式的求值。

我们用顶行的冒号,来表达下个表达式的类型;用 => 表达函数类型。例如:

: Int
x = 0

: Double Double Double Double => Double
def minkowski_distance t x y z
  t ** 2 - x ** 2 - y ** 2 - z ** 2
end

用括号表达参数化类型

: Matrix(t, a, b) Matrix(t, b, c) => Matrix(t, a, c)
def matrix_mul ma mb
  (ma.rows.zip mb.cols).map -> [x, y] # Tomb 语言和 Hongmeng 一样,是开源的
    x * y
  end
end

: => Array(Int, 3)
def one
  [1, 1, 1]
end

类型的积和结构体的 field 是一回事,留给结构体定义就可以了:

struct Foo[
  xip: Int
  baz: String
]

类型的和 (Union) 用 | 表达:

: Int | String
a = 3

类型也可以嵌套,用括号包起来

: Array (Any => Any) => Array
def map a f

对于精炼类型,我们先添加一个声明断言的语法 pred,就像这样:

pred GT8
  self.size > 8
end

pred EQ8
  self.size == 8
end

然后再在类型里代入断言,这样类型的语法可以保持比较简单,利用上面声明的两个断言就像这样:

: String&GT8 => String&EQ8
def take8 s
  s.take 8
end

综上,这是我们设想的类型表达式语法:

Type : (Term* induce)* Term # 注意 => 是右结合的
Term : lparen Type (or Type)* rparen | const Param? Refinement?
Param : tlparen Vars? rparen
Refinement: ampersand const
Vars : var (comma var)*

对应的词法:

[a-z_][a-z_0-9]* var
[A-Z][a-z_0-9]* const
(?<=[a-z_0-9})])( tlparen # tight left paren
( lparen
) rparen
& ampersand
, comma
=> induce
| or

下面实现个类型运算的原型。先准备点基础设施,然后加上类型运算。

嗯用 Ruby 写比用相继式演算写要简单些 (题图误导)……

作用域

众所周知周知,作用域分词法作用域和动态作用域。词法作用域只用分析当前文件就能弄清楚;而动态作用域需要运行时才能弄清楚,JavaScript 那么难用,部分原因就是放太多东西在动态作用域里了。还有的作用域规则通过分析多个文件

变量、常量和结构体一样响应 name 方法

Var = Struct.new :name, :type
Const = Struct.new :name, :type, :methods # 为 struct 时带 methods

作用域用哈希表达即可,例如

{
  "x": "Int"
  "y": "String"
}

函数类型用数组表达, 例如

#: Int Int => Int
["Int", "Int", "Int"]

考虑到预置的环境,初始化类型系统

class Type
  VAR_PRELUDE = {}
  CONST_PRELUDE = {}

  def initialize
    @var_scopes = [VAR_PRELUDE, {}]
    @const_scopes = [CONST_PRELUDE, {}]
  end
end

作用域的操作 (lambda / method 会给变量作用域)

def push_var_scope args
  new_scope = {}
  args.each do |name, ty|
    new_scope[name] = Var[name, ty]
  end
  @var_scopes.push new_scope
end

def pop_var_scope
  raise "mismatch var push/pop" if @var_scopes.size == 1
  @var_scopes.pop
end

def push_const_scope
  @const_scopes.push 
    {}
end

def pop_const_scope
  raise "mismatch const push/pop" if @const_scopes.size == 1
  @const_scopes.pop
end

定义常量 / 变量

def define_var name, type
  @var_scopes.last[name] = Var[name, type]
end

def define_const name, type, methods=nil
  @const_scopes.last[name] = Const[name, type, methods]
end

查找变量/常量/结构体

def lookup_var name
  @var_scopes.reverse_each do |scope|
    var = scope[name]
    return var if var
  end
  nil
end

def lookup_const name
  @const_scopes.reverse_each do |scope|
    const = scope[name]
    return const if const
  end
  nil
end

查找方法类型

def lookup_method const, method_name
  if const.methods
    method = const.methods[method_name]
    raise "method #{method_name} not found" if !method
    method
  else
    raise "const is not a struct"
  end
end

匹配类型麻烦些,因为嵌套的函数类型也要判断相等。

如果认为任意类型可以和 "Any" 类型互换,那就支持了渐进类型

def type_match? param_ty, arg_ty
  if arg_ty == 'Any' or param_ty == 'Any'
    true
  elsif !param_ty.is_a?(Array) and !arg_ty.is_a?(Array)
    arg_ty == param_ty
  elsif param_ty.is_a?(Array) and arg_ty.is_a?(Array)
    param_ty.size == arg_ty.size and
      param_ty.zip(arg_ty).all?{|(a, b)| type_match? a, b }
  else
    false
  end
end

类型运算

由于是半吊子类型系统,不用实现推导(Type inference),就实现个类型演绎(Type induction)好了。另外为了代码更简单,和 Scala 一样做乞丐版类型演绎好了——也就是只对 lambda / method 内进行类型推导,不进行全局类型推导。

没标注类型的变量,如果位于赋值表达式左边或者函数参数,可以自动标注其类型。

进行类型运算,我们先得对源程序 AST 做遍历,转换成类似三位址码 (Three address code)的赋值表达式(实际上我们用不限定地址的更灵活的方式)。对字面量,我们替换成一个特殊的无参函数赋值表达式,用函数的返回值类型去标注,这样代码要处理的 case 就少很多了。

CallAssignment = Struct.new :dst, :receiver, :method_name, :args, :method
ValueAssignment = Struct.new :dst, :src

我们的类型演绎就和数独填空一样,循环填空直至到达不动点

def annotate_types
  updated = false
  until not updated
    updated = false
    assignments.each do |a|
      annotate a
    end
  end
end

def annotate obj
  updated = false
  case a
  when CallAssignment
    if a.receiver.type != 'Any'
      a.method ||= lookup_method a.receiver, a.method_name
      if a.method
        if a.dst.type != a.method.type.last
          a.dst.type = a.method.type.last
          updated = true
        end
        a.args.zip a.method.type[0...-1] do |arg, param|
          if !arg.is_a? Literal and arg.type != 'Any' and param.type != 'Any'
            arg.type = param.type
            updated = true
          end
        end
      end
    end
  when ValueAssignment
    if obj.dst.type == 'Any' and obj.src.type != 'Any'
      obj.dst.type == obj.src.type
      updated = true
    elsif obj.dst.type != 'Any' and obj.src.type == 'Any' and !obj.is_a? Literal
      obj.src.type == obj.dst.type
      updated = true
    end
  end
  updated
end

对于精炼类型,我们可以生成断言的表达式,连同其他类型信息一起扔给 Z3 计算。当然,由于我们是半吊子的类型系统,就先不管了。

类型校验

在类型计算中检查矛盾,抛错就可以了。

搞定。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值