Skip to content

Fantom 语言手册(docLang) ​

原文: https://finproducts.atlassian.net/wiki/spaces/JIDD3/pages/34793422938/FIN+5.3.1+docLang 中文整理基于 FIN 5.3.1


manual ​

Fantom 语言手册(docLang) ​

Fantom 语言深入手册


chapters ​

Fundamentals
Structure空间: 类型.slot 程序结构
Literals文字类型的语法
Expressions表达式的语法
Statements语句的语法
Exceptions例外处理
Type System
TypeSystem带有动态位的静态类型系统
CompilationUnits源代码的组织
Podscreating pods
Classes创建类类型
Mixins创建混合类型
Enums创建 enum 类型
Slotsslot model
Methods深度潜入方法槽
Fields深度潜入战地槽
Inheritance时间档继承规则
Facets用元数据注释类型和插槽
Functions
Functions函数编程
Closures深入封锁
Interop
Interop语言界面特征摘要
Natives在 Java 和 C# 代码中执行方法和字段
JavaFFIJava 外部函数接口
Java传输到 Java
JavaScript传输到 JavaScript
Advanced
Deployment如何部署pod和应用程序
Env定制 Fantom 环境
Serialization读/写对象
Concurrency线程之间数据共享模式
Actors消息传递货币
NamingURI 命名系统
Logginglogging APIs
Localization本地化 APIs
DSLs域特定语言和编译器插件
DateTime日期和时间
Appendix
Appendix补充详细说明
Conventions我们使用的多样编码
Grammar正式的 BNF 语法

演员 ​

概览 ​

Fantom 包含一个 actor framework 货币 Actors 是一种轻量所开展的工作在背景线条上同步运行的物体。 演员通过发送同步信息获得工作. 然后由演员在所开展的工作所控制的背景线条上处理这些信件 ActorPool.

演员 ​

这个 Actor 类用于定义新角色。 所有行动者都是在 ActorPool 定义了演员是如何执行的。

行为者可以两种方式之一界定其接受行为:

  1. 向演员的建筑师传递函数
  2. 次类 Actor 并覆盖 receive

以下是两个简单的例子,

// pass receive to constructor as a closure function
a := Actor(pool) |Int msg->Int| { msg + 1 }

// subclass and override receive
const class IncrActor : Actor
{
  new make(ActorPool p) : super(p) {}
  override Obj? receive(Obj? msg) { msg->increment }
}

一个演员被保证从本质上接收它的信息——它从不同时被安排在多个线条上. 然而,不能保证一个行为者在一段时间内在同一条线上收到其所有信息。 例如,如果消息A,B,和C被发送给一个演员,运行时间可能会使用三个不同的线程来处理这些消息. 但演员保证会连续处理信件.

地方演员 ​

演员是 const classes 这意味着他们必须是不可改变的。 这可以让你在演员之间通过演员参考,但不能维持演员领域的任何可变状态. 相反,你可以将演员的"变化世界状态"存储在 Actor.locals。演员本地化是一个字符串/对象地图,它像线程本地化——每个演员都使用一个独特的地图。 为了防止命名碰撞,您应该将您的地图密钥前缀为您的 pock 名称 :

// store an actor local
Actor.locals["acme.global"] = "hum bug"

// get an actor local
Actor.locals["acme.global"]

例如,每收到一个信息,就建立一个保持计数器的演员:

pool := ActorPool()
a := Actor(pool) |msg|
{
  count := 1 + (Int)Actor.locals.get("count", 0)
  Actor.locals["count"] = count
  return count
}

100.times { a.send("ignored") }
echo("Count is now " + a.send("ignored").get)

请注意,在这个例子中,演员忽略了发送给它的信息,所以我们通过什么并不真正重要.

消息传递 ​

再由演员通过互相发送消息进行交流. 再接一个 immutable 以保证线的安全。

信件会用这些方法发送给一个演员 :

  • send: 立即将消息排入
  • sendLater:在一段时间后对消息进行排序
  • sendWhenComplete分段 : 一旦其他信件完成处理, 就会对信件进行排序

未来 ​

所有三种发送方式返回 a Future 用于获取该信息的结果。 您可以使用 Future.status - 未来通过三个过渡之一进入完整状态:

  • 演员处理所遇到的信息并返回结果
  • 演员在处理消息时提出例外
  • 取消未来(见 cancel)

一旦一个未来进入完整状态,结果可通过 get 方法。 如果未来还没有完成,那就叫 get 将阻断呼叫者,直到未来完成。 暂停可以用来固定一段时间内阻断. 通话 get 成果包括:

  • 如果处理成功,那么 get 将返回结果
  • 如果演员提出处理消息的例外,那么同样的例外就被提到给 get
  • 如果未来被取消,然后打电话 get 将升孜孜 CancelledErr
  • 如果使用超时,那么 TimeoutErr 如果演员在超时前不处理信件, 就会被丢弃

阻碍通过 Future.get 永远不要收到信息本身,因为这可能导致僵局。 最佳做法是设计服务演员,严格使用同步消息,同时保持不服务自己请求的客户端演员的同步消息.

信件处理错误 ​

当一个演员在其接收回调中提出一个处理消息的例外时,错误会在"未来"中提供. 当客户打电话 Future.get,然后演员的最初例外被提出. 但是如果客户不打电话 Future.get,那么它很容易被开发者忽略错误。 不幸的是,系统无法知道该错误是否将通过未来的客户端代码处理。

为防止错误被默默忽略,建议采用以下设计模式: 1.

  1. 每条电文应明确说明谁负责处理错误
  2. 如果演员对错误负责,则应登录错误
  3. 如果客户对错误负责,它必须拨打 Future.get 处理错误irs

作为一般原则,同步处理的信息由客户端处理. 但应能记录Async消息,

计时器 ​

这个 sendLater 方法可以用来设置定时器. 计时员在演员到期时会发回其排队的消息. 示例

pool := ActorPool()
a := Actor(pool) |Obj msg| { echo("$Time.now: $msg") }
a.send("start")
a.sendLater(1sec, "1sec")
a.sendLater(3sec, "3sec")
a.sendLater(2sec, "2sec")
Actor.sleep(5sec)

这个 sendLater 方法返回 a Future 用于取消计时器或民意测验/屏蔽,直到处理信息。

链条 ​

这个 sendWhenComplete 方法用于在另一条电文完成处理后发送电文。 使用 sendWhenComplete 允许同步的信息链。 考虑这个代码 :

future := actor1.send(msg1)
actor2.sendWhenComplete(future, msg2)

在这个例子中, msg2 正在排队 actor2 仅在 actor1 笋 完成处理 msg1通常,未来本身向它们提供的信息是:

a.sendWhenComplete(future, future)        // future is message itself
a.sendWhenComplete(future, MyMsg(future)) // MyMsg references future

记住 sendWhenComplete 无论未来是如何完成的,都叫它: 成功,有错误,或者取消。

整理信件 ​

通常在向一个演员发送消息时,我们可以将两个消息合并到一个消息中,以保存一些工作. 例如,在窗口系统中,通常厮守着一个平等地位,由一个窗口的所有脏部分组成,而不是一群小矩形. 一个演员可以使用 makeCoalescing 对其他建筑师来说

举个例子:

const class Repaint
{
  new make(Window w, Rect d) { ... }
  Window window
  Rect dirty
}

toKey := |Repaint msg->Obj| { msg.window }
coalesce := |Repaint a, Repaint b->Obj| { Repaint(a.window, a.dirty.union(b.dirty)) }
a := Actor.makeCoalescing(g, toKey, coalesce) |Repaint msg| {...}

在这个例子中,信息是: Repaint。 你觉得 toKey 函数用于获取决定两个信件是否可以连接的密钥。 在这个例子中,我们把每个窗口的再油漆组合起来。 如果线程用绯闻检测到两个相同密钥的未决信件(此情况下的窗口),则它称为 coalesce 函数以合并信件。 举例来说,我们返回一个新的 Repaint 与两个肮脏地区结合的事件。

发送的信件 sendLater 和 sendWhenDone 永不凝结。

的基础控制 ​

Fantom 目前的执行使用 * 未绑定 * 消息队列 。 这意味着如果一个演员收到的信息比它能处理的速度快,那么它的队列将会继续增长. 最终这可能导致内存的例外. 您可以使用以下一些技术来实施流量控制,以防止无限制的队列永远生长:

  • 期货与 isDone 或用途 get 有超时取消一段时间后未处理的信件
  • 使用 coalescing queues 合并待处理信件
  • 使用 sendLater 在演员排队时安排看狗计时器
  • 使用 sendWhenDone 创建消息反馈循环

例如,考虑一个“阅读者”角色,从一个大文本文件中读取文本的行,并将这些行发送给其他“处理”角色进行平行处理。 如果读者尽可能快地推动文字的行,那么它就有可能最终在记忆中排出大量的行. 更好的战略是让处理行动者在准备处理一条线时与读者排队。 这将创建一个自然的反馈循环,并允许读者根据处理器的运行速度来节制其IO.

演员池 ​

所有演员都是在 ActorPool. ActorPools 管理使用共享线程池执行演员.

随着消息被发送给演员,他们被分配一个线程来完成自己的工作. ActorPool将创造多达100条线程,之后演员必须等待一条线程释放. 一旦一线解脱,就用来处理下一个演员. 如果没有演员有等待工作,那么线程会停留几秒钟,然后被放回操作系统. 在这个模型中, ActorPool 使用0到100线的峰值,这取决于该池的演员目前有多少人有工作。 您可以通过设置调整峰值限制 Actor.maxThreads:

ActorPool { maxThreads = 10 }

动极醇一建成就立即被视为运行. 然而,它实际上不会产生它的第一线,直到它的一位演员收到信息. 如果一个游泳池的所有演员 完成处理他们的信息, 然后在一段长时期后,所有游泳池的线程 释放。

ActorPool 可用手动关闭 stop 方法。 一次 stop 被调用,游泳池进入停止状态,游泳池内的演员可能不再收到任何信息. 然而,所有待处理的信息都允许继续处理。 一旦所有待决信件处理完毕,池子就会进入done状态. 使用 join 在 ActorPool 完全关闭之前,

这个 kill 方法可用于进行无序关闭。 与 stop, kill 并不能给演员一个完成处理待处理信件队列的机会 - 所有待处理信件都被取消. 目前正在执行信件的行为者被打断(可能或不会立即终止该线索)。 一旦所有演员都放弃了线条, ActorPool 进入done状态.


附 录 ​

类型推论 ​

本节正式定义了编译器如何在列表,地图上进行类型推论的规则,以及三位运算符:

// list of expressions
[v1, v2, ...]  =>
  V = common(v1, v2, ...)

// map of expressions
[k1:v1, k2:v2, ...] =>
  K = common(k1, k2, ...)
  V = common(v1, v2, ...)

// ternary operator
cond ? t1 : t2  =>
  common(t1, t2)

集合的类型推论是基于我们称为 * common * 的函数,用于在类型列表中找到最常见的基类. 以下算法用于计算常用类型:

  1. 如果类型充分履行 Obj?
  2. 如果类型清单只有一个项目,则返回该类型
  3. 如果某一种类型无效,则结果无效
  4. 如果所有类型都不是参数化的通用类型,那么就找到所有类型都共享的最常见类别;我们只考虑类别,混合类型则被忽略
  5. 如果其中任何一种类型是参数化的通用,则:
    1. 如果所有类型都是参数化的列表,那么计算通用的V类型
    2. 如果所有土拨鼠都是参数化的地图,那么:
      1. 如果所有签名完全相同,则使用该类型
      2. 使用 sys::Map
    3. 如果所有类型都被参数化,则:
      1. 如果所有签名完全相同,则使用该类型
      2. 使用 sys::Func
    4. 如果上述各项均不属实,则使用 Obj

类 ​

概览 ​

班级是最基本的 type:

  • 分类范围在一个舱内,并在全球范围内确定合格名称 podName::ClassName
  • 类中包含零或更多唯一命名的槽
  • 班级总是从一个超级班级继承(sys::Obj 这是这一规则的唯一例外)
  • 类可继承零或更多混音器

使用 class 关键字 :

class MyNewClass {}

类别蜗牛 ​

类别可附加下列修改语:

  • public
  • internal
  • abstract
  • final
  • const

例如,宣布内部抽象类:

internal abstract class Foo {}

保护 ​

这个 public 和 internal 关键词定义类的能见度. 页:1 public 系统里每个人都可以使用类。 一个 internal 类只可见于声明pod内的类型。 如果没有指定保护关键字,则类默认为公开.

摘要类 ​

文摘类(英語:cradual class)是设计为永远不能直接被即时化的类. 称为抽象类的构造器是一个编译器错误 。 然而抽象类确实有用于子类的构造器.

摘要类可能包含也可能不包含 abstract methods。虽然所有包含抽象方法的类别,但必须宣布为抽象。

与抽象阶级相反的称为混凝土阶级.

最后类 ​

终课不能被子分类 。 任何从最终类扩展的尝试都会导致编译器错误.

组合类 ​

Const课程是不可改变的 - 一旦创建,一个实例就保证不会发生状态变化. 必须遵守下列规则,并按等级分类:

许多原始的原始阶层 包括: Bool, Int, Float,以及 Str. Const 类和不可改变性在 thread safety.

欧比 ​

所有阶级的根是 Obj 课。 Obj 是唯一没有超级阶级的阶级

实例 ​

Fantom 使用一个 pure object oriented 类型系统。 这意味着所有变量都是一个完全属于一个类的物体。

反思 ​

这个 Type 类用于 người在运行时代表一个类。 您可以使用 Type.of 方法 :

Type t := Type.of(obj)

由于所有对象都是类的实例, 因此 Type 对象将始终代表一个类。 鉴于a Type 代表混凝土类,可以反省地创建新实例:

t.make                   // use default make constructor
t.make(args)             // use make constructor with arguments
t.method("makeX").call   // lookup and use a named constructor

结 论 ​

概览 ​

关闭是创建 function 在方法的体内。 关闭能够参考其所附范围的当地变量。 这种能够创建内线函数,从而访问本地范围,因此很容易使用关闭作为方法参数. 例如,关闭被广泛用作一种 iteration 书面材料。

语法 ​

关闭的基本语法 :

|A a, B b...->R| { stmts }

关闭的开始是它的签名,它重新使用与 function types。关闭的正文是一系列零或更多的声明。 那个 return 语句用于返回结果并退出关闭(Fantom不支持任何其他跳出关闭方式,除非 return or throw) (中文(简体) ). 举个简单的例子:

f := |->| { echo("hi there") }
f()
f()

上面的代码创建了一个向控制台打印消息的关闭. 如果我们运行上面的代码 "你好"是印刷两次。 我们把关闭 变量 f。关闭本身是一种表达方式,它创造了一个实例: Func - 就像说 8 是一种创建 Int。函数的签名是 |->| 这意味着殆尽函数不需要参数而返回 Void。一旦关闭指定给 f我们可以蟑螂 call f 就像任何其他功能一样。

还有一个例子:

f := |Int a, Int b->Int| { return a + b }
nine := f(4, 5)

上面的代码宣布关闭 接受两个 Ints 并归还他们的金额。 通知关闭使用 return 语句以返回结果(然后我们看看如何省略结果)。

绑定本地 ​

关闭的真正力量在于它能够与附着范围的当地变量结合。 举个例子:

counter := 0
f := |->Int| { return ++counter }
echo(f())
echo(f())
echo(f())
echo(counter)

此示例创建函数返回 Int 然后三次调用函数。 注意关闭正文如何使用本地变量 counter。关闭时既可以读也可以写任何包含范围的变量——就像一个 if 声明或声明 while 循环。 因此上面代码的输出是打印"1","2","3"和"3".

范围 ​

当关闭与其外部范围内的当地变量有联系时,该变量就一直存在。 记住,关闭只是 Func 可在原范围以外传递的对象。 举个例子:

static Func createFunc()
{
  counter := 0
  return |->Int| { return ++counter }
}

static Void main()
{
  f := createFunc
  echo(f())
  echo(f())
  echo(f())
}

这个 createFunc 方法返回绑定在本地变量上的关闭函数 counter对它们来说,本地变量将存在,只要关闭存在。对它们来说, main 方法将函数指定为变量 f 那就叫三声 输出会打印出"1","2"和"3". 这实际上允许关闭在援引之间储存自己的状态。

装订这个 ​

如果在一种实例方法内宣布了关闭,则关闭可以约束 this 变量与其它本地变量一样:

Str first := "Bart"
Str last  := "Simpson"

Void test()
{
  f := |->Str| { return first + " " + this.last }
  echo(f())
}

上面的代码说明对两个本地插槽具有约束力. 封锁与 first 带有隐含 this。关闭使用明确 this 约束 last。注意 this 关键词引用了所附方法的示例, *而不是 * 关闭对象。 这也意味着通用 Obj 方法类 toStr 和 type 引用所附方法实例,而不是关闭实例。

多重关闭 ​

当孜孜不倦赘言煞费苦心时,

counter := 0
f := |->Int| { return ++counter }
g := |->Int| { return ++counter }
echo(f())
echo(g())
echo(f())
echo(g())

上面的代码印着"1","2","3","4",因为两者都 f 和 g 共享相同的约束 counter.

注:在目前的实施中,所有关闭都具有相同的本地人。 这意味着,任何涉及这些本地人的关闭都会妨碍收集所有关闭变量的垃圾。

关闭参数 ​

关闭只是普通表达式,可以作为论据传递到期望一个 Func 参数。 许多关键API被设计为与函数配合. 例如,考虑 List.findAll 方法,该方法返回匹配标准的每个项目的子列表。 既然我们想让比赛结束 findAll 让您通过任意函数来确定匹配。

让我们考虑一下在列表中找到所有偶数的例子:

list := [0, 1, 2, 3, 4]
f := |Int v->Bool| { return v%2==0 }
evens := list.findAll(f)

上面的代码创建一个函数,然后传递到 findAll 方法。 由于关闭只是一个表达,我们也可以将代码改写为:

evens := list.findAll(|Int v->Bool| { return v%2==0 })

由于关闭方式被大量使用,范托姆支持从鲁比借来的特殊语法. 如果关闭是方法调用的最后论据,那么关闭就可以被拉出作为调用后的后缀:

evens := list.findAll() |Int v->Bool| { return v%2==0 }

既然我们没有通过任何辩论 除了关闭, 我们可以进一步简化这个代码 通过删除 parens:

evens := list.findAll |Int v->Bool| { return v%2==0 }

重复 ​

封闭设计是重复的主要机制。 接受函数参数的关键方法 :

  • List.each: 添加列表
  • List.eachr: 倒转列表
  • Map.each: 绘制地图

当将一个既包含值又包含整数索引的列表传递到关闭时:

list := ["one", "two", "three"]
list.each |Str val, Int index| { echo("$index = $val") }

但记住我们不必 use all 向该职能提供的论点。 例如,如果我们不在乎整数指数:

list := ["one", "two", "three"]
list.each |Str val| { echo(val) }

地图迭代方法相同:

map := [1:"one", 3:"three", 5:"five"]
map.each |Str val, Int key| { echo("$key=$val") }
map.each |Str val| { echo(val) }

关闭类型推论 ​

作为最后一个参数传递到方法支持类型推论的关闭 :

// fully specified closure signatures
list := ["one", "two", "three"]
list.each |Str v, Int i| { echo("$i = $v") }
list.each |Str v| { echo(v) }

// inferred closure signatures
list.each |v, i| { echo("$i = $v") }
list.each |v| { echo(v) }

如果把类型从关闭参数中移开,那么根据关闭的上下文推断出来。 在上述例子中,一个结束语转至: Str[].list.each 推定有一类 |Str,Int|.

也可以结合返回类型使用推论,也可以完全省略返回类型:

odds = [1, 2, 3, 4, 5].findAll |v->Bool| { v.isOdd }
odds = [1, 2, 3, 4, 5].findAll |v| { v.isOdd }

关闭只有在传递给期望函数的方法所承担的类型时才能推断出来. 如果无法推断关闭参数,则默认值为 Obj?:

// closure with inferred type of |Obj? v|
f := |v| { echo(v) }

带动 锁定 ​

它-块是一种特殊的封闭形式,有以下区别:

-钝器略去功能签名,只用卷曲套装申报

  • 使用 type inference 根据其背景
  • 定义一个隐含的单一参数 it -所作的隐含界定 it
  • 不允许在它块中返回关键字
  • 它块被赋予编译时间权限,以设置 Const 字段 it 参数,尽管运行时检查如果试图在外置 Const Err its 构造器(参见 const fields)

需要单个参数函数时,可使用块块:

["a", "b", "c"].each |Str s| { echo(s.upper) }  // long hand
["a", "b", "c"].each { echo(it.upper) }         // short hand

在上述例子中,它的障碍是隐含着 Str 调用参数 it.

这个 it 参数和隐含的一样有效 this 参数。 如果某一标识符没有在本地范围内被宣布,那么我们就试图与 it:

["a", "b", "c"].each { echo(it.upper) }  // explicit it call
["a", "b", "c"].each { echo(upper) }     // implicit it call

碞钩 this,如果一个本地变量在 it,然后使用本地变量。 如果有人试图隐含地进入两个地点上都存在的槽。 this 和 it,然后是编译时间错误:

["a", "b", "c"].each { echo(toStr) }        // Ambiguous slot error
["a", "b", "c"].each { echo(it.toStr) }     // explicit call on it
["a", "b", "c"].each { echo(this.toStr) }   // explicit call on this

此函数 ​

一般来说, sys::This 的各项活动 return type 实例方法。 有一个例外 - 您可以声明输入的方法参数为 |This| 以显示其块函数:

new make(|This| f) { f(this) }

带锁 ​

Fantom 允许您在任意表达式中附加一个它块 。 当它块被使用而函数不被预期时,编译器会生成调用到 Obj.with:

list := Str[,].with { fill("x", 3) }   // explicit call to with
list := Str[,] { fill("x", 3) }        // implicit call to with

默认执行 Obj.with 只应用函数 :

virtual This with(|This| f)
{
  f(this)
  return this
}

使用它- 块和 Obj.with 允许您打开带有任意表达式的新词典范围。 它对于声明式编程相当有用.

区块通常与 comma add operator 以暗示方式将项目添加到收藏中。

// this long-hand syntax
pane.with
{
  add(child1)
  add(child2)
  add(child3)
}

// can be collapsed to this with-block and comma operator
pane
{
  child1,
  child2,
  child3
}

汇编单位 我就是 ​

组织 ​

fantom源代码的pod被组织成文件系统中的一个或多个目录. 按惯例,一个pod的源树结构如下:

podxyz/
  +- fan/        // ".fan" source files
  +- java/       // ".java" Java native peers
  +- js/         // ".js" JavaScript native peers
  +- dotnet/     // ".cs" C# native peers
  +- res/        // resources to bundle with pod
  +- locale/     // localization files
  +- test/       // ".fan" unit tests
  +- build.fan   // build script for pod
  +- pod.fandoc  // pod level documentation

其中每个目录可能都包含其中任意的子目录,尽管不需要.

Fantom源代码使用".fan"扩展名存储在纯文本文件中. 每个源文件可包含一个或多个 class or mixin 定义。 与Java不同的是,没有限制每个类型必须在自己的源文件中. 虽然通过惯例,我们把每个类 在一个源文件 一个匹配的名称。 但是,如果你有很多小课程, 那么最好明智地结合它们。

字符编码 ​

只有一条简单的规则可以遵循——所有Fantom源文件都必须在UTF-8编码中. 更好的是,如果想要确保您的源代码容易在任何编辑器中使用,那么就使用标准7位的ASCII字符(这是UTF-8的干净子集). 说完了,够了

源文件解剖 ​

编译者喜欢使用花式术语汇编单位来描述源文件的文本. 一个汇编单位的结构相当简单:

  1. 零或以上 using 语句
  2. 一个或多个类型的定义

这个 using 从外部吊节导入到汇编单位的命名空间。 类型定义是实际的类和混音.

使用 ​

每个编译单元可以指定零或更多 using 将类型导入到单位命名空间的语句。 将类型导入到命名空间中, 允许您使用简单的类型名称 。 type signatures 而不是合格的名字。

最简单的形式 using 语句是将 * * 全部导入到您的命名空间中 :

using inet

上面的语句允许您使用定义于 inet 通过它简单的名称。 例如,简单的类型名称 TcpSocket 现在决心 inet::TcpSocket.

我们还可以将单一类型导入命名空间. 例如仅仅导入 inet::TcpSocket 而不从中导入所有其他类型 inet 我们使用这个语法 :

using inet::TcpSocket

在大型项目中,两个不同的pod必然会重新使用同一个类型名称. 如果我们在编译单元中需要使用两种类型,那么我们可以使用合格名称——尽管它可以有点动词. 另一种选择是使用 using as 语句。 例如,给定一个命名碰撞 red::Foo 和 blue::Foo洽谈 blue::Foo as BlueFoo 使用此语法 :

using blue::Foo as BlueFoo

这个 using as 语句是用来命名碰撞的 - 不要用它来做骨头头的东西 比如导入 sys::Str as S 所给予的帮助。

这个 sys pocke 自动导入到每个编译单元 - 如果您尝试导入, 它实际上是一个编译器错误 sys 当然

评论 ​

Fantom支持三种评论风格:

  • /* */ 块注释用来评论可以跨越多行的一组文字。 你会筑巢 /* */ 评论作为一种方便的方式来评论代码的很多部分
  • // 行尾注释忽略到行尾的所有内容
  • ** fandoc 注释

这个 ** 不管怎样,注释用于文档。 不管怎样 // 评论时,它会将所有内容评论到行尾. Fandoc的评论类似于 /** */ 贾瓦多克或 /// C#文档评论. 您在前面再加一条线 ** 为类型或位置定义指定文档的注释 :

** This class is really cool
class Cool
{
  ** When you gotta just do it!
  Void justDoIt() {}
}

文档用纯文本写成,使用一套特殊的结构规则。 fandoc。Fandoc 允许您在纯文本源文件中写出好看的文档,但可以翻译为其他格式,如格式良好的HTML。


货币 ​

概览 ​

Fantom使用与Java和C#非常不同的路径来解决货币问题. Java和C#使用共享内存模型——所有线程都能够访问对方的内存空间. 需要同步锁定,以确保线程以一致的方式共享数据. 这种货币模型相当强大,但在低层次的抽象作用下运作. 因此,即使技术熟练的程序员也很难写出既无种族条件又无僵局的代码。 这种模型也使得建立可堆肥的系统非常困难,因为所有系统组件必须协调它们一致地使用锁.

Fantom的货币模型基于以下原则:

  • ** 无共享变异状态**:线程在任何情况下都不会共享变异状态;
  • 不可变异性:不可变异性的概念嵌入语言本身. 无法移动的数据可以在线条之间有效和安全地共享(例如通过静态字段);
  • Message Passing:演员API是围绕着这个想法构建的. passing messages 同步工作者之间;

不可用性 ​

受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 受助人 Fantom 支持这些不可变的物体类型 :

  • 任何物体实例 const class
  • 结果是 Obj.toImmutable 列表、 地图、 Buf 或 Func

顾名思义,一个集合类是不可改变的——编译器验证所有预期将字段本身都是不可改变的,只设置在对象的构造器中.

Func 对象由编译器根据函数是否从环境中捕获可变状态而确定为可变状态或不可变状态。 见 Functions 更多细节。

内存已备份 Buf 物体可以通过调用 toImmutable 方法。 原字节被传输到一个新的只读 Buf 实例 。

这个 toImmutable 由 List 和 Map 是一种返回只读的深层副本的机制,它能确保所有值和密钥本身都是不可改变的。 编译器将允许在构建过程中指派Const List/Map 字段,但它含蓄地呼吁 toImmutable。例如,要声明字符串的集合列表:

// what you write
class SouthPark
{
  const static Str[] names := ["Stan", "Cartman", "Kenny"]
}

// what the compiler generates
class SouthPark
{
  const static Str[] names := ["Stan", "Cartman", "Kenny"]?.toImmutable
}

您可以检查一个物体是否通过 Obj.isImmutable 方法。

共享对象 ​

殆尽角色】允许对象在线程之间共享.殆将对象】传递到另一个线程的特定 API 包括:

  • Actor.send
  • Actor.sendLater
  • Actor.sendWhenComplete
  • Future.get

诘问所有这些API都使用相同的模式,在线程之间安全通过一个对象. 如果一个物体是不可改变的,那么我们可以安全地通过引用将其传递给另一个线程. 否则,我们假设物体是 serializable 然后将对象的序列拷贝传递到另一个线程。 在您的应用设计中,这两种方法都有其利弊:

  • 不可改变:
    • or experient for simple structure if fast to create to 创建简单结构的理想
    • 结构深厚的物体在单一线条内改变费用昂贵
    • 总是非常高效地在线条之间传递
  • 连载:
    • 深层结构对象可以在单一线程中有效修改
    • 线条之间穿行费用不大

公约 ​

概览 ​

这些是我们在Fantom代码库中使用的编码惯例. 绝不要求你遵守我们的公约,只要你在乎,就记录下来。 但是,如果为核心分配提供编码,这些公约就得到执行。

源文件 ​

  • 使用7位安全ASCII作为UTF-8的干净子集
  • 使用 Unix "\n" 行尾
  • 喜欢放课 FooBar 在一个名为"FooBar.fan"的源文件中
  • 如果你有一堆小班, 联合到一个单一的来源
  • 将测试类分为单独的“ test/” 目录

命名 ​

  • 类型名称是"FooBar"等上层骆驼案例
  • 槽名是"fooBar"(包括所有田间和方法,甚至康斯特田间)等小骆驼案例
  • 别用"FOO"之类的尖叫盖子
  • 符号名称为小骆驼
  • Pod名字是低级骆驼案例,也是全球独一无二的。 您应该先给您的 pock 名字加一些东西以避免命名冲突 。 例如SourceForge或Google代码项目名称是安全命名的公约. 或有组织或域名的前缀pod. 如果你拥有一个".com"域名,不要将"com"包含在你的pod名称中.
  • 不要使用在识别资料中完全资本化的词: -唠叨 -用"SomeId"而不是"SomeID"
    • 使用“ readXml” 代替“ readXML ”

通用名称 ​

  • 倾向于添加到附件
  • 优先地址添加器
  • 倾向于论证
  • 偏爱目前
  • 首选点网/点网
  • 倾向于错误
  • 你需要从 html/ Html 到 HTML
  • 首选 http/Http 到 HTTP
  • 优先使用身份标识/标识符
  • iO更喜欢iO/IO 正选位置
  • 倾向于 msg 给消息
  • 偏爱参数参数
  • 倾向于记录
  • 请求优先 波士顿
  • 倾向于回应
  • 倾向于资源
  • 偏爱罗罗
  • 更喜欢Rw/RW到Rw
  • 偏爱长度或数 偏重于源头
  • 用户名优先给用户Name
  • 偏爱val值
  • 最好警告他们
  • 偏好 xml/ Xml 到 XML

缩进 ​

  • 不使用标签字符, 只使用空格

  • 使用两个缩进

  • 使用Allman型的牙套:

    if (cond)
    {
      doTrue
    }
    else
    {
      doFalse
    }

每一行最好有一个没有分号的单词

  • 如果声明很短,而且有助于可读性,则将其折叠到单行中
  • 在关键词和打开的 paren 之间留下一个空格 if, for, while, switch,以及 catch 语句

爸: ​

  • 总是省略 () 没有参数的方法调用
  • 偏爱 Foo(...) 带有参数的 当我们使用时,样式构造符
  • 偏爱 Foo {...} 样式构造符,使用它块时
  • 本地变量的优先推论类型
  • 偏ضرار ال الك الك الك الك الك الك الك الك الك الك
  • 偏爱 Obj[] to List 和 Obj:Obj to Map
  • 喜欢省略 return 单一声明方法的关键词和关闭语

评论 ​

  • 使用 /* */ 仅评论代码的章节

  • 喜欢使用领先 33. ** 凯末尔:

    class Foo
    {
      **
      ** This is a very well written comment
      **
      Void doSomethingCool() {}
    }
  • 使用74行断开逻辑段 / 字符 :

    //////////////////////////////////////////////////////////////////////////
    // Section
    //////////////////////////////////////////////////////////////////////////
  • 使用74号线 * 在单个源文件中区分类的字符 :

    **************************************************************************
    ** NewClass
    **************************************************************************
  • 我们在每个源文件的顶端使用以下评论(显然名字会为你不同):

    //
    // Copyright (c) 2008, Brian Frank and Andy Frank
    // Licensed under the Academic Free License version 3.0
    //
    // History:
    //   28 Aug 08  Brian Frank  Creation
    //

高频 ​

概览 ​

DSL或域特定语言允许您将其它语言嵌入到您的 Fantom 源代码中. DSL的语法是:

AnchorType <|...|>

之间的一切 <| 和 |> 符号被认为是DSL本身的源代码. 主锚类型定义了编译DSL的方式. DslPlugins在主播类型上注册,由Fantom编译器调用,将其翻译为Fantom表达式.

内建的 DSL ​

Fantom目前拥有这些DSL的舰只:

字符串 DSL ​

您可以使用 Str DSL 来写入除“ QQ” 序列以外的字符串 。 它们的工作类似于用Perl,Python或Ruby等语言的XML CDATA部分或这里的文档:

echo(Str <|no \ or $ escapes need, and
           multi-line works too|>)

见 Str Literals 更多细节。

雷格克斯 DSL ​

您可以使用 Regex DSL 构建一个 Regex 带有字符串模式的示例 :

Regex <|foo|foo/(\d*)|>

你不需要担心逃避像反弹这样的特殊角色.

缩写 ​

您可以通过子分类媚写自己的蚂蚁DSL DslPlugin见 RegexDslPlugin 类作为一个简单的开始示例:

class RegexDslPlugin : DslPlugin
{
  new make(Compiler c) : super(c) {}

  override Expr compile(DslExpr dsl)
  {
    regexType := ns.resolveType("sys::Regex")
    fromStr := regexType.method("fromStr")
    args := [Expr.makeForLiteral(dsl.loc, ns, dsl.src)]
    return CallExpr.makeWithMethod(dsl.loc, null, fromStr, args)
  }
}

// register in indexed props in build script
index = ["compiler.dsl.sys::Regex": "compiler::RegexDslPlugin"]

注:写插件需要访问编译器API. 这个API有一个非常大的表面积,所以我们不能保证这些API在未来不会改变.


广泛散发时间 ​

概览 ​

以下sys类型用于表示和随时间而工作:

  • Duration雏形
  • DateTime: 相对于特定时区的一个绝对时间段
  • TimeZone哎 : 代表根据Zyreinfo数据库显示的 UTC 抵消和白天节省时间的规则
  • Date: 独立于任何时间的日历日期
  • Time: 独立于任何日历日期的一天时间
  • Month: 年度数月
  • Weekday: 每周几天的注解

所有这些课程都是不可改变的 const classes.

选项 ​

Fantom时间的基本单位是纳秒tick. 期限和日期时间均为 ticks 方法和一个需要若干勾选的构造器。

就持续时间而言,虱子是相对的。 那个 Duration.now 方法可以用来跟踪过去的时间,独立于墙点时间。 如果你在测量相对时间段,你应该总是喜欢 Duration.now 因为它始终独立于系统时钟的变化而前进。 计算机经常自动调整时钟数秒甚至分钟,这样可以扭曲墙上时间的测量。 在 Java 运行时间 Duration.now 地图到 System.nanoTime.

Ticks in DateTime使用UTC凌晨1-Jan-2000的时代. 2000年之前的日期使用负勾选。 希望你能把千年作为时代。

广泛散发时间 ​

相对于1-Jan-2000纪元,绝对时间作为数纳米秒滴表示. 滴答对计算机来说是一个很好的时间表现,但是作为人类,我们把时间想成年,月,日,小时,分钟等. 从滴滴到人类时间的翻译总是相对特定时区. 例如,纽约时间337,350,600,000,000滴滴代表8:30m 9-Sep-2010,但伦敦时间是下午1:30. 这在绝对时间上是完全一样的瞬间,但是根据我们的时间区的观点,人类的时间代表是不同的.

这个 DateTime class 类相对于给定的时区,将绝对时间封装在勾选符中。 虽然绝对时间和时区人类时间是两个不同的概念,但将它们捆绑在一个单一的类别中是方便的. 在实践中,了解某个时间戳的时区往往相当重要。 当时间有模棱两可的时区关联时,会产生无数问题。 例如ISO 8601时间表示提供了UTC的抵消,但这永远不足以实际计算出时区. 在Fantom,我们需要一个清晰的时区 与每个日期时间相关联, 它是犬科系列化代表的一部分。

DateTime类为访问与相关时区相对的人类时间元素提供了很好的简单API:

d := DateTime.now
echo("$d.day $d.month $d.year | $d.hour $d.min | $d.tz")
echo("$d.date | $d.time")

// outputs on a computer in EDT
9 sep 2010 | 8 58 | New_York
2010-09-09 | 08:58:54.668
  1. 注意到: tz 用于获取关联 TimeZone叩头 。
echo(DateTime.now)
echo(DateTime.nowUtc)
echo(DateTime.now.toTimeZone(TimeZone("Taipei")))

 // outputs
 2010-09-09T09:00:41.09-04:00 New_York
 2010-09-09T13:00:41.106Z UTC
 2010-09-09T21:00:41.09+08:00 Taipei

本地化和格式化 ​

所有三个类 日期、 时间和日期时间支持 a toLocale 和 fromLocale 用于使用字符串模式解析/格式的方法。 那个 pattern language 与Java的SimpleDateFormat 所用的类似。 但Fantom支持一些额外的特性,并坚持以下公约:

-瑶字栏(年、月、日、日、日)的资本化字母

  • 小写字母用于时间字段(小时、分、秒)
  • 可选秒和分数秒资本化(S和F)

几个简单的例子:

DateTime.now.toLocale("kk:mmaa")              =>  09:10am
DateTime.now.toLocale("DDD 'of' MMMM, YYYY")  =>  9th of September, 2010

时区 ​

在Fantom中,我们使用术语 *timezone * 来概括两个概念:从协调世界时和省日时规则中抵消。 例如,美国东部标准 时间是从协调世界时到5小时。 但3月第二个星期日2时至11月第一个星期日2时之间是日间节能时间(DST),是协调世界时的−4hrs.

由于时区是D OlympiaeTime代表式中如此关键的一个方面,Fantom需要综合的时区模型和数据库. 时区对于计算机来说是相当麻烦的,因为它们是政治抽象,而不是科学抽象。 这意味着一个特定区域可能会随着时间的推移而改变其时区规则(协调世界时或抵消DST规则). 例如,2007年,美国更改了DST开始和结束的日期。 这意味着2000年的计算日期使用与2010年不同的规则(我们称之为这些历史时区).

幸运的是,有一个数据库可以记录各地区和时间的这些规则。 我们用 zoneinfo Unix 和 Java 的一些版本所使用的数据库。 在Fantom中,我们利用“adm/buildtz.fan”脚本,将Zoneinfo数据库的一个子集编译成二进制代表。 数据库保存在"etc/sys"中. 由于数据库的大小,我们使用随机访问IO来按需加载时区. 在JavaScript中,JsTimeZone类用于定义哪些时区定义发送到浏览器.

zoneinfo数据库使用命名时区的惯例作为"持续/城市". 例如,美国东部时间被划为"American/NewQQYork". 由于各大洲之间没有重复的城市名称,因此该城市名称也起到犬科标志的作用. 由于时区在串行化期间总是包含在内,我们u只使用城市名称作为犬形识别符. 在API中,这被称为 name 和 fullName.

时区异名 ​

随着时间的推移,地名可能会改变。 这些变化往往是出于政治原因:"卡尔克塔"成为"科勒卡塔","赛贡"成为"Ho\ chi\ minh". 过时的时区名称被配置文件"etc/sys/timezone-aliases.props"绘制为时区别名. 此文件来自 tzinfo " backward" 文件 。

都将是什么时候 TimeZone.fromStr 如果无法将名称映射到实时区域,则该名称与别名对照。 如果它被映射成别名(或用简单的名称,或用全名),则返回犬形时区. 例如:

TimeZone("Asia/Saigon")  =>  Asia/Ho_Chi_Minh
TimeZone("Saigon")       =>  Asia/Ho_Chi_Minh

相对时间区 ​

在大多数情况下,我们希望绝对比较时间。 例如,如果查看一个日志文件,我们一般期望看到来自多个时区的事件按绝对时间排序. 但有时我们希望用人类的时间来比较时间。 特殊时区"Rel"用于此目的. 任何转换为/从"Rel"保留时区表示并更改绝对勾选.

这里有一个简单的程序来演示:

pattern := "DD-MM-YYYY hh:mm zzz"
a := DateTime.fromLocale("01-09-2010 03:00 Los_Angeles", pattern)
b := DateTime.fromLocale("01-09-2010 03:00 Chicago", pattern)
echo("$a ?= $b => ${a==b}")
a = a.toRel
b = b.toRel
echo("$a ?= $b => ${a==b}")

// outputs
2010-09-01T03:00:00-07:00 Los_Angeles ?= 2010-09-01T03:00:00-05:00 Chicago => false
2010-09-01T03:00:00Z Rel ?= 2010-09-01T03:00:00Z Rel => true

权衡 ​

与任何软件开发一样,这些API的工程需要权衡. 我们希望用一个简单易用的API来权衡解决大多数案件。 当然,它是一个角度问题 基于你个人使用的案例: 但值得考虑权衡。

所有Fantom时间API都是基于纳米秒滴. 一个64位整数可以在+/-292年之间存储. 因此,通过使用纳米秒作为滴答的单位, 我们做了一个最佳的权衡, 有利于那些需要纳米秒精度的程序 和那些持续100年的程序。 也值得注意的是,JavaScript将所有数字都当作64位浮点数,因此在与大持续期或DateTimes合作时,纳米秒精度丧失.

Fantom的约会基于 Gregorian calendar 这不是唯一使用的日历系统。 但实际上,使用Gregorian系统打出99.9%的使用量而不增加并发症.

实际的UTC时间尺度使用跳跃秒来保持日历与太阳时间同步. 但一般来说,计算机系统并不考虑跳跃秒数,Fantom也不考虑. 从实用的角度来看,这使得在Fantom dictions和Java millis等其他表述之间很容易转换.

按建筑日期和时间分类进入核心 sys 舱,我们不提供所有 钩和功能 这是每个人可能需要的。 但这是取舍 我们的第一个优先事项是提出所有API可以使用的核心表述,而不增加依赖。 座上座座上座座上座座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上座上 但是,当然,任何人都可以在新的舱内创建额外的API.


部署 ​

概览 ​

Fantom 编译器生成的 pock 文件包含一个字节代码代表,我们称之为 *fcode *. Pod 文件只是普通的zip文件,您可以在您最喜欢的zip工具中打开. Pod 文件还包含恒定集合的文件和任何可能与您的 pock 捆绑(通过 Pod.files 页:1

Pod文件在Java和.NET之间可移植. 运行时间负责在 pock 文件中读取执行. 运行时间也提供了全面实施 sys 舱位 :

  • Java运行时间读取 pockes 文件,并发布fcode作为Java字节码. 此翻译在运行时进行 。 sys API在普通Java代码中执行.
  • 同样,.NET运行时间读取 pockes 文件,并在运行时间以 IL 发布fcode 。 sys API在普通C#代码中执行. 注意.NET此时不支持.

以下插图描绘了这一架构:

deployment

土著人 ​

如果一个pod100%地被写在Fantom代码中,那么它在运行时间之间是完全可移植的. 不过,有些舱型喜欢 inet or fwt 需要将基础平台与本土方法联系在一起。

当你用本地方法构建一个pod时,它会产生一个正常的pod文件,这是所有反射元数据所必需的. 但也会产生更多的本土目标:

  • Java:分类文件被编译并添加到 pock zip(参见 JavaFFI)
  • JavaScript: JavaScript 源文件被添加到 pock(参见 JavaScript)
  • DotNet: DLL 被添加到lib/dotnet中,用于pod

也见 docTools 如何用本地代码构建pod。

依赖关系 ​

所有pod都有一套明确的依赖其他pod的功能. 所有舱必须依赖 sys 舱。 依附关系在您的 build script 并可在运行时通过 Pod.depends.

依附物通过 Depend 课。 它们以字符串格式宣布,其中包括播客名称和一组版本限制. 版本限制可以是简单的版本编号,版本编号和任何更大的,也可以是版本范围. 参见: fandoc 用于格式细节。

tependences is used by two ways. 依赖性有两种使用方式. title constitutions definitions decide 中决定了哪些pod可以通过 using 语句。 导入依赖列表中未声明的pod是一个编译时间错误 。

依赖性也在运行时检查. 如果一个pod的依赖性没有得到满足,那么pod就无法加载.

部署 ​

安装Fantom应用程序包括三个部分:

  1. 平台运行时间: Java VM 或.NET VM(通常是预先安装)
  2. Fantom 运行时间:核心文件的分布
  3. Pods: 您应用所需的pod库

从网络下载的Fantom发行版其实是一个开发者Distro. 大部分文件不需要运行时间。 一般说来,运行时间所需的唯一目录是 bin, lib,以及 etc在 lib 目录中您可以删除应用程序不需要的所有 pock、 罐和 dll 文件 。


数字 ​

概览 ​

Enums是一类特殊类型,它定义了可能的值的离散范围:

  • 纳姆是正常的 classes 具有所有相关特性
  • Enums是隐含的凸轮
  • Enums是隐含的终极
  • Enums有固定的例范围
  • Enums 可以包含声明零或更多唯一命名的插槽
  • Enums总是继承 Enum
  • Enums可能继承零或更多混音器
  • Enums必须有私人建筑师

数字使用 enum 位置关键字,必须在其他槽声明前声明其数值范围:

enum class Color { red, blue, green }

范围 ​

eum的*范围是它的离散的一组实例。 范围中的每一个实例都有以下特征:

  • 以一个独特的基于零的正则表示
  • 由一个独有的名称识别
  • 通过其名称的 让静态康斯特字段进入
  • 是一个宣示海姆阶级的例子
  • 不可变(含环)

编译器自动生成两个帮助槽:

  • vals 是按正数字索引的区域内所有值的列表
  • fromStr 用于解决名称

以这个例子来说明在封面之下发生的情况:

// what you write
enum class Color { red, blue, green }

// what the compiler generates
class Color : Enum
{
  static const Color red   = make(0, "red")
  static const Color blue  = make(1, "blue")
  static const Color green = make(2, "green")

  static const Color[] vals = [red, blue, green].toImmutable

  static new fromStr(Str s) { ... }

  private new make(Int ord, Str name) : super(ord, name) {}
}

Enum 构造器 ​

Enums可以声明自己的构造器来初始化额外的字段. 记住,一个enum是康斯特,这意味着它的所有领域也必须是康斯特. 这反过来意味着所有字段必须在构造器中初始化. Enum 构造器必须是私有的. 以下语法用于声明和调用一个enum构造器:

enum class Suits
{
  clubs("black"),
  diamonds("red"),
  hearts("red"),
  spades("black")

  private new make(Str color) { this.color = color; }

  const Str color;
}

注意申报类实际上不管理自己的正文和名称,这总是由编译器完成的.

Enum 修改器 ​

Enums 可用 public or internal 修饰者就像 classes。使用 abstract, final,或 const 带有电源的修改器 final 和 const 被隐含。

反思 ​

E绮ms由零基整数正弦和字符串名称键入. 以下总结了经常用来与enum工作(使用 Month 例如:

  • Enum.ordinal编号: Int 直线键
  • Enum.name编号: Str 名称密钥
  • Month.vals: 搜索实例, 按顺序
  • Month.fromStr帘黑:按名称查找实例

示例代码 :

apr := Month.apr              // direct access
jun := Month.vals[5]          // lookup by ordinal
aug := Month.fromStr("aug")   // lookup by name
Month.fromStr("bad")          // throws ParseErr
Month.fromStr("bad", false)   // returns null

信封 ​

概览 ​

这个 Env 类用于访问和定制 Fantom 环境。 运行时间代表对目前安装的环境的主要方面包括:

  • 标准一/O
  • 家庭、工作和临时目录
  • pod 决议
  • config 属性分辨率
  • locale 属性分辨率
  • Java类加载分辨率(使用JVM时)

目录结构 ​

Fantom设施的标准目录结构积极地平行了:

lib/
  fan/
    podA.pod
    podB.pod
    .. other pods
  java/
    .. jar files
  dotnet/
    .. .NET DLLs
etc/
  sys/
    config.props
    ... other sys config/runtime files
  build/
    config.props
    ... other build config/runtime files
  podA/
    config.props
    ... other config/runtime files
  podB/
    config.props
    ... other config/runtime files

顶级的"lib"目录用于存储包含代码的库文件. "lib"下的目录由平台组织:粉丝,java,dotnet. ".pod"文件本身保存在"lib/fan/"下.

顶层的"etc"目录用于存储系统宽配置文件以及一个pod在运行时可能需要的其他文件. "etc"下的目录由bock组织——"etc"下的每个子目录应该匹配一个bock名称.

启动Env ​

用于拖曳Fantom环境的默认环境被称为BootEnv. 这个 homeDir botEnv总是相对于发射Fantom(fan.bat或fan bash脚本)所使用的位置。

核心图书馆 sys 取得的进步必须被装入“暮夜之旅”。

设置信rina ​

您可以一声不响地覆盖默认环境 FAN_ENV 环境变量。 此变量应映射到该变量的限定类型名称 Env 要使用的子类。 类必须有一个默认的无参数构造器.

BootEnv用于加载指定的环境类. 这需要定义您环境的pod位于"{homeDir}/lib/fan/"下.

路径Env ​

这个 PathEnv 是一个简单的 env 执行,它使用一个搜索路径,而不是要求您的所有文件都位于“ fan. home” 下。 搜索路径与 FAN_ENV_PATH 环境变量。 例如:

C:\dev\fan>set FAN_ENV=util::PathEnv
C:\dev\fan>set FAN_ENV_PATH=/dev/work/

所指定的目录应使用标准结构 conventions。在上述例子中,我们将在“/dev/work/lib/fan/”中寻找舱位。 您可以使用您的操作系统指定多个目录 path separator中,请注意驴尾路径总是含蓄地包括"fan.home"作为搜索路径中最后一个目录.

也可以设置一个名为"fan.props"的文件的目录特定路径Env.的)))的,)的,)的,)的,)的))的上)的上)的上上,)的上上下))的上上下)的上上上下)的上上下)的上上下)的上下下下)的上上上下下)的上上下下下下下下下下下下)的上上上上下下下下下下下下下下下下上上上下下下下上上上上上上下下下上上上下上下下上下下下下上下上下上下上下下下上下下下下下下下上下上下上下上下上下上下上下下上下下下下下上下下下下下下上下下下下上下下下下下下下下下下上下下下下下下下下下下下上下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下下 "fan.props"的位置定义了工作目录. 也可以在路径中指定附加目录属性 path 键 :

// location of fan.props is always implicitly first in path (working dir)
path=/dir-a/;/dir-b/

“ fan.props”文件也可以用来在使用“ key” 前缀的密钥在该特定目录下工作时覆盖特定的环境变量:

env.ENV_VAR_KEY=var-value

路径列表以 * 优先级顺序 * 来指定. 路径中的第一个目录用于 workDir。优先排序用于解析 pock 和配置文件。 通常你会使用这个机制来保存一个原始的启动目录,并在单独的工作目录中做 pock 开发与配置覆盖.

例如,让我们考虑这个目录结构:

boot/
  lib/
    fan/
      podA.pod
      podB.pod
work/
  lib/
    fan/
      podB.pod
      podC.pod

在这个配置中,事情的解决方式如下:

podA  =>  boot/lib/fan/podA.pod
podB  =>  work/lib/fan/podB.pod
podC  =>  work/lib/fan/podC.pod

我们已经发现,

贾迪斯特Env ​

JarDistEnv是JVM唯一的环境,用来将一组Fantom pocks作为Java JAR文件进行部署. 一个JarDistEnv与其他环境不同,因为它无法访问文件系统中的标准目录结构. 相反,环境可能需要的一切都作为JAR文件中的资源自我控制。

JarDistEnv 不支持以下特性:

  • indexed props
  • 文件系统覆盖 config 和 locale
  • homeDir, workDir, tempDir 全部默认为当前工作目录

见 JarDist 任务和任务 example script 如何建立一个JAR来部署。

为了让Fantom从 JarDist 中自行启动,必须配置以下系统属性:

-不构成"fan.home":设置为某个目录 Env.homeDir

  • "fan.jardist":设置为" true",以确保运行时间自 JAR 而不是文件系统加载

索引道具 ​

困扰许多软件平台的一个问题是有效发现目前安装的软件的能力。 例如,我应该使用哪种类型来处理URI计划"foobar"? 注册了哪些插件可以使用“图像/png” MIME 类型 ?

在Fantom建筑中,这些类型的发现功能是用索引道具完成的. 索引道具是一种简单的机制,在这种机制中,pod可以定义名称/值对,它们通过当前环境被连为主索引。 您在您的 pock 构建脚本中定义索引道具 :

index =
[
  // creating plugins for existing APIs like UriScheme
  "sys.uriScheme.foobar": "acme::FoobarScheme",

  // creating plugins for specific target types
  "acme.editor.sys::Bool": "acme::BoolEditor",
  "acme.editor.sys::Int": "acme::IntEditor",

  // registering types for specific functions
  "acme.theme": ["acme::JungleTheme", "acme::WaterTheme"],
]

索引道具是全球范围,所以惯例是将您的密钥名称与pod名称进行范围化. 在上述示例"acm.them"中注意,您可以定义单个键的多个值.

在运行期间,当前环境将为所有安装的pod建立索引道具主索引。 这意味着添加新的功能只需要将一个 pock 文件降入您的 lib 目录.

你找指数道具 Env.index 方法。 定义。 它返回绑定在给定密钥上的值列表 :

// lookup the UriScheme bound to foobar
qname := Env.cur.index("sys.uriScheme.foobar").first

// find editor types responsible for editing target
qnames := Env.cur.index("acme.editor.${target.typeof}")

// find all the theme types installed
qnames := Env.cur.index("acme.theme")

利用名称/值对的基本机制,可以构建相当精密的解决方案来发现被安装的pod中捆绑的种类和资源.


例外 ​

概览 ​

Fantom使用例外作为处理错误的标准机制. 例外是子类的普通对象 Err。例外是 rown * 或 提出 * 以表示错误条件。 当一个例外被提出时,正常的程序执行会中断,呼叫堆栈不会受伤,直到找到一个例外处理器来处理这个例外.

Fantom 不使用 Java 样式 checked exceptions 其中,一种方法必须宣布它提出的例外。 根据我们的经验, 例外是语法盐, 它没有规模到大型项目。 例如,在Java中,真正的例外经常被包裹了几十次,以使编译器在独立子系统集成时感到快乐.

错误类 ​

Fantom 子类中的所有例外 Err。许多API 声明自己 Err 类用于建模特定错误条件。 类型等级是用于分类例外的主要机制,用于将例外与 exception handlers.

全体 Errs 也有一个消息和一个*原因。 信息是人可读的 Str 来描述错误条件。 因为另一个原因 Err 实例是例外情况的根源。 原因往往是 null 若无根因.

Fantom 常规要求所有例外类结束 Err 并宣布一个建筑师叫做 make 至少需要一个 Str 消息作为第一个参数和一个 Err 作为最后一个参数。 通常这两个参数都有默认参数: null.

当一个例外被丢弃时,运行时间会捕捉到调用堆栈,并用 Err 举个例子。 您可以使用 trace 方法 :

err.trace       // dumps to Env.cur.out
err.trace(log)  // dumps to log output stream

数据 ​

这个 throw 语法用于通过下列语法提出例外:

// syntax
throw <expr>

// example
throw IndexErr("index $i > $len")

与 throw 关键字必须评为 sys::Err 类型。 当 throw 葱白处理器。

即便是尝试批量语句 ​

Fantom 使用的是 try-catch-finally 用于例外处理的 Java 和 C# 语法 :

try
{
  <block>
}
catch (<type> <identifier>)
{
  <block>
}
catch
{
  <block>
}
finally
{
  <block>
}

渔获区块列表用于根据例外类型指定例外处理者. 特定类型的渔获区块将捕获该类型的所有上升的例外及其次类。 如果在渔获区块内提出一个没有匹配渔获区块的例外,则例外继续解除调用堆栈(尽管如果 finally 块被指定将执行)。

a 中的代码块 try 可以是 {} 块或单一语句。 每个渔获区块的标识符被用来 Madisonclus-这个变量被设定在渔获区块multiculturalism中.

你可以有这么多 catch 区块,但捕获区块的类型不能重复以前使用过的类型。 例如:

// this is legal
try {...}
catch (CastErr e) {...}
catch (NullErr e) {...}
catch (Err e) {...}

// this is illegal
try {...}
catch (Err e) {...}
catch (NullErr e) {...}

在代码的第一个区块 我们首先宣布一个捕捉者 CastErr 和 NullErr 它会抓住任何例外的这些类型。 我们抓住了 Err 最后,它会捕捉任何其它的,不是一个 CastErr or NullErr然而,第二块代码不会编译,因为 NullErr 接球后会来 Err 捕获块(包括 NullErrs).

如果您不需要访问被捕获的例外,您也可以使用一个 * 捕获所有* 块来使用以下的短手语法来捕获所有例外 :

try
{
  return doSomethingDangerous
}
catch
{
  return null
}

最后块 ​

我们用一个 finally 块,当我们要执行一些运行的代码时,无论如何处理例外。 A级 finally 块总是当一个 try 块形出口。 最后,经常用来确保适当清理资源,不论可能出现的例外情况如何。 举例来说,以下代码保证输入流是封闭的,无论内部可能发生什么错误 try 块 :

void load(InStream in)
{
  try
  {
    // read input stream
  }
  finally
  {
    in.close
  }
}

最后区块业经C#类似限制袍. 您无法退出 页 次 finally 直接使用 return, break,或 continue 语句。


表达式 ​

运算携手优先 ​

Fantom的表达式语法与C,Java,C#和公司非常相似. 排列顺序的运算符 :

围绕着这个主题: (x) x.y x.y() x->y() x?.y x?.y() x?->y() x[y]

  • 一号: ++x --x x++ x-- !x +x -x (T)x &x -与土著人民相比: * / %
  • 补充: + -
  • 范围: .. ..<
  • 小心ظاد 问题时 < <= >= > <=> is isnot as
  • 平等: == != === !==
  • 有条件和: &&
  • 有条件或: ||
  • 如果过期: x?t:f x?:y
  • 任务: = *= /= %= += -=
  • 添加: ,

快捷键操作符 ​

Fantom是一种纯粹的OO语言,因为每件事物都是可以调用方法的物体 - 甚至是数值类型,例如 Bool 和 Int因此,几乎所有的操作员都只是方法调用。 我们称这些操作员为“快捷操作员” 因为他们只是调用特定方法的语法糖:

a + b     =>  a.plus(b)
a - b     =>  a.minus(b)
a * b     =>  a.mult(b)
a / b     =>  a.div(b)
a % b     =>  a.mod(b)
a[b]      =>  a.get(b)
a[b] = c  =>  a.set(b, c)
-a        =>  a.negate()
++a, a++  =>  a = a.increment()
--a, a--  =>  a = a.decrement()
a == b    =>  a.equals(b)
a != b    =>  !a.equals(b)
a <=> b   =>  a.compare(b)
a > b     =>  a.compare(b) > 0
a >= b    =>  a.compare(b) >= 0
a < b     =>  a.compare(b) < 0
a <= b    =>  a.compare(b) <= 0
a,b,c     =>  it.add(a).add(b).add(c)

比如说我们有两个变量 a 和 b 两种类型 Int表达式 a+b 只是用语法糖来称呼 Int.plus as a.plus(b)。见 Method Operators 详细讨论。

前缀和后缀运算符 ​

这个 ++ 和 -- 是按照C家族语言,操作员可以是前缀或后缀.是按照两个操作员的要求,将呼叫结果分配给 increment or decrement 到操作变量。 如果前缀,则表达式对任务进行评价。 如果后缀, 则表达式会评价之前的操作值 increment or decrement 指定。

两个 ​

平等经营者 == 和 != 两者都利用 Obj.equals 虚拟方法. 大多数类型都超越了这种方法来比较数值平等. 若为 equals 不被推翻,那么默认行为就是比较引用平等.

关系运算符 ​

关系运营商喜欢 < 和 > 全部使用这个 Obj.compare 虚拟方法. 许多带有批号的类型会超过这种方法返回 -1, 0, 或 1 。 若为 compare 不被覆盖, 那么默认执行会比较操作的结果 toStr 方法。

编译器将数值返回翻译为布尔条件,以此来使用运算符。 特别节目 <=> 运算符返回 Int 值为 -1, 0, 1 直接。 你们通常使用 <=> 关闭自定义类型的操作员 :

people.sort |Person a, Person b->Int| { return a.age <=> b.age }

如果那个代码对你没有任何意义 那就不用担心 继续读直到我们覆盖 closures.

与 Null 的比较 ​

你好吗? null 因此,一个 NullErr 例外从不提出。 为了平等,一无是处和一无是处永远是平等的,但两个无是处永远是平等的。 对于关系运算符,无效总是小于非无效对象. 无效的特殊处理如果 equals or compare 方法作为普通方法调用。 这种特殊处理也不适用于其他快捷键操作符.

相同的运算符 ​

这个 === 和 !== 运算符称为 *same * 和 * not same * 运算符。 这些运算符用于检查两个变量是否在内存中引用同一个对象实例. 与 == 和 != 快捷键运算符,相同而非相同的运算符不会导致 equals 方法调用。 这些运算符不得用于对等值类型.

有条件的操作员 ​

条件 !, &&,以及 || 运算符使用布尔表达式。 使用 && 吟唱逻辑 and 和 || 吟唱逻辑 or。这两个操作员都是短路的,因为如果第一个测试是决定性的,第二个测试就会跳过(false (单位:千美元) && 和 true (单位:千美元) ||) (中文(简体) ). 那个 ! 运算符执行逻辑 not。有条件操作符的代码示例:

t := true
f := false
t && f  => evaluates to false
t && t  => evaluates to true
f || t  => evaluates to true
!t      => evaluates to false

三级运算符 ​

三进制运算符将三种表达式结合起来,作为根据假设/无效条件分配一个值的方便方式:

condExpr ? trueExpr : falseExpr

这个 condExpr 必须评估布尔。 若为 condExpr 评估到 true 然后整个表达式评价 各国 trueExpr箴言,否则 falseExpr实例

3 > 4 ? "yes" : "no"  => evaluates to "no"
6 > 4 ? "yes" : "no"  => evaluates to "yes"

Fantom 也支持使用一个 throw 声明作为三重廖运算结果之一:

val := isValid(key) ? map[key] : throw ArgErr("invalid key")

Null 方便操作员 ​

Fantom支持在Groovy发现的几个操作员让工作更加便利:

  • ** Elvis 运算器** x ?: y (看着它的侧面 作为一个"笑"的脸)
  • ** 安全Invoke** x?.y
  • ** 安全动态信息** x?->y

埃尔维斯运营商 ​

埃尔维斯操作员评价左手侧. 如果它是无核的,那么它是整个表达的结果。 如果它是无效的,那么整个表达的结果就是右手侧表达. 是呀 右手侧表达式是短路的 如果左手促进对非无线的评价 它与您使用二次操作符的方式类似:

// hard way
file != null ? file : defaultFile

// easy way
file ?: defaultFile

elvis运算符不得用于非nullable类型,因为根据定义它不会是无效的.

喜欢 ternary Elvis 操作员可以使用 throw 语句作为表达式666右侧:

val := map[key] ?: throw ArgErr("key not found")

essa. 安全信号 ​

安全引用操作员被设计为短路,如果方法调用或字段访问的目标无效. 如果短路,则整个表达式评价为无效. 在一个调用链中跳过检查 我是个无效的值很有用 :

// hard way
Str? email := null
if (userList != null)
{
  user := userList.findUser("bob")
  if (user != null) email = user.email
}

// easy way
email := userList?.findUser("bob")?.email

乘以 $2 的 都可以 乘以 $2 的乘数 。 你可以用来 ?-> 作为无保障版本 dynamic invoke operator.

当然 当然的 你还 没错应与参与了 没错的的的三的的的的的的的的的的的的的的的的的的导则的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的暮的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的的 安全援引的结果永远是无效的:

x := str.size   =>  x is typed as Int
x := str?.size  =>  x is typed as Int?

类型检查 ​

投影操作员用于进行类型转换. 铸造语法使用类似C语言的括号——例如: (Int)x。如果类型投影在运行时失败,则 CastErr 提出例外。

这个 is, isnot,以及 as 运行时使用运算符检查对象的类型 :

  • is 运算符返回 a Bool 非操作执行指定的类型( 如 Java ) instanceof 运营商). 如果目标无效,则评价为虚假.
  • isnot 运算符在本质上相当于 !(x is Type)。如果目标无效,则评价为真实。
  • 那个 as 操作员将对象返回到指定的类型或 null 它不是一个此类实例( 如 C# 两个实例) :
Obj obj := 123
obj is Str     =>  evaluates to false
obj is Num     =>  evaluates to true
obj isnot Str  =>  evaluates to true
obj isnot Num  =>  evaluates to false
obj as Float   =>  evaluates to null
obj as Int     =>  evaluates to 123 (expr is typed as Int)

在使用这些类型时,不考虑其无效性。 is, isnot,以及 as 操作员。 例如,这两个表述被认为是等同的:

obj is Str
obj is Str?

这个 as 运算符按定义返回无效类型。 例如,以下表达式评价为 Str?没有 Str:

x := obj as Str  => x is typed as Str?

索引 ​

取决于如何使用, [] 运算符映射到三个不同的快捷键 :

a[b]      =>  a.get(b)
a[b] = c  =>  a.set(b, c)
a[b]      =>  a.getRange(b) if b is Range

通常情况下 a[b] 是呼叫的快捷键 a.get(b)。例如, List.get 方法允许您查看列表项 Int 索引。 当一个班级支持一个 get 附带注释 这次操作的方法 @Operator 侧面可以使用 [] 作为捷径。 考虑这个代码 :

list := ["a", "b", "c"]
list.get(2)
list[2]
list.get("2")  // error
list["2"]      // error

用语 list[2] 与 list.get(2)焰火。后两行导致编译器错误,因为我们试图通过一个 Str 当 Int 预计将。

当一个任务左手边使用索引快捷键时, 例如 a[b] = c 然后索引运算符映射到 a.set(b, c)。例如,这两行代码具有相同的行为:

map.set("tmj", "Too Much Joy")
map["tmj"] = "Too Much Joy"

如果 [] 运算符与 Range 索引,然后我们绘制地图 a.getRange(b) 执行切片的方法。 切片用于创建子字符串和子列表. 一些创建子字符串的示例代码 :

s := "abcd"
s[0..2]  => "abc"
s[3..3]  => "d"
s[0..<2] => "ab"

start := 0; end := 2
s[start..<end] => "ab"

我们用 .. 指定一个包容性终点指数, ..< 以指定一个独家端索引。 也注意我们如何与范围操作员使用任意表达式来定义紧凑的切片表达式.

通过支持整数指数化的Fantom API的常规,允许使用负整数从列表结尾开始索引. 比如说 -1 可用于索引列表中最后一个项目(或字符串中最后一个字符). 使用负索引可以使用所有三个快捷键:

list := ["a", "b", "c", "d"]
list[-2]           =>  evaluates to "c"
list[-1] = "last"  =>  replaces list[3] with "last"
list[1..-1]        =>  evaluates to ["b", "c", "last"]

负指数的使用适用于大多数方法: List 和 Str 这需要索引参数。

位元运算符 ​

Fantom没有位元运算符,而是使用普通的方法调用:

~a      =>   a.not
a & b   =>   a.and(b)
a | b   =>   a.or(b)
a ^ b   =>   a.xor(b)
a << b  =>   a.shiftl(b)
a >> b  =>   a.shiftr(b)

序列化表达式 ​

Fantom 支持三种表达式构造,它们旨在让编程语言成为真实的超级集 serialization 语法 :

  • 简单
  • 它 - 锁
  • 收款 下面

简单 ​

Simples 是通过字符串重排序列化的特殊序列类型。 Fantom 允许使用简单的表达式 :

<type>(<str>)

// for example:
Version("3.2")

// is syntax sugar for
Version.fromStr("3.2")

要使用此表达式, 该类型必须有一个名为 fromStr 这需要一个 Str 参数并返回本身的一个实例派代表出席。 如果有默认值,该方法派代表出席。 该类型无需执行 Serializable 使用此表达式的面值( 虽然如果想要序列化的话它会使用) 。 简单表达式是 construction calls.

带动 锁定 ​

It-blocks 允许您写入复合表达式 - 它们通常用于初始化实例 。 这个功能是清净的 超级集如何 complex 类型被序列化。 一个它块化表达式的例子 :

Address
{
  street = "123 Happy Lane"
  city   = "Houston"
  state  = "TX"
}

// is syntax sugar for (note: can't actually use it as param name)
Address.make.with |Address it|
{
  it.street = "123 Happy Lane"
  it.city   = "Houston"
  it.state  = "TX"
}

收款 ​

如果 it 支持名为“添加”的方法。 以逗号为后缀的它块内的任何表达式都被认为是呼叫 it.add:

a,       =>  it.add(a)
a, b     =>  it.add(a).add(b)
a, b, c  =>  it.add(a).add(b).add(c)

注意逗号操作员将调用到 add因此, add 方法必须与 @Operator 面并返回可链条类型 This.

以下是FWT的例子:

Menu
{
  text = "File"
  MenuItem { text = "Open"; onAction.add { echo("open") } },
  MenuItem { text = "Save"; onAction.add { echo("save") } },
}

// is syntax sugar for (note: can't actually use it as param name)
Menu.make.with |Menu it|
{
  it.text = "File"
  it.add(MenuItem MenuItem { text = "Open"; onAction.add { echo("open") } })
    .add(MenuItem { text = "Save"; onAction.add { echo("save") } })
}

高级运算符 ​

F假如Fantom还有其他几个操作员,

  • Closures 是方法体内创建新函数的表达式。
  • Call 运算符 () 用于引用函数变量。
  • Dynamic invoke 运算符 -> 用于调用不编译时间类型检查的方法。
  • Field storage 运算符 & 用于访问一个字段的原始存储,而无需通过它的获取/改进方法。

面额 ​

概览 ​

Facets是用元数据对类型和槽进行注释的一种机制. 面孔类似于Java 注释或C# 属性. 面额是一个序列式的集合类实例,在类型或槽上声明,用于执行 Facet 混合剂

面额类 ​

面额定义为使用 facet 位置关键字 :

// simple marker facet with no fields
facet class Indexed {}

// struct facet with one or more const fields
facet class Table
{
  const Str name := ""
  const Bool autoCreate
}

所有方块类自动继承 Facet 搅拌。 面孔是隐含的 const classes 这意味着他们所有的田地 必须是康斯特。

Facet类不允许定义自己的构造器. 相反,编译器将为您生成一个 。 如果你的面部有球场,那么它就意味着 Serializable 它的所有领域必须是可序列的。

以上各类将有下列综合定义:

// marker facet with no instance fields is singleton
const class Indexed : Facet
{
  static const Indexed defVal := Indexed()
  private new make() {}
}

// struct facet with instance fields gets it-block constructor
@Serializable
const class class Table : Facet
{
  new make(|This|? f) { f?.call(this) }
}

说明 ​

任何类型或时间段都可以使用 @ 符号 :

@Indexed
@Table { name = "Employees"; autoCreate = true }
class Employee {}

Struct 侧面将其卷曲括号内的字段值指定为 it-block。当面注释中只能使用简单的文字表达式,编译器可以决定如何序列化。

反思 ​

在运行时可采用下列方法:

  • Type.facet
  • Type.facets
  • Type.hasFacet
  • Slot.facet
  • Slot.facets
  • Slot.hasFacet

总是喜欢的范围 facet 和 hasFacet 训练班 facets 因为它的效率要高得多。 一些例子:

// check if a type is serializable, returns null if not
Serializable? ser := obj.typeof.facet(Serializable#, false)

// check if a field is transient
field.hasFacet(Transient#)

继承 ​

A. 设计 FacetMeta 要声明类型级别面应该继承的面 :

@FacetMeta { inherited = true }
facet class F {}

@F class A {}
class B : A {}

在以上的例子中 B 将继承侧面 F. 面部继承在下列任何类型(类别或混合)上起作用: inheritance hierarchy.


字段 ​

概览 ​

字段是用于在类中管理状态的机制. 单字段囊括三个概念:

  1. Storage:存储字段值的引用的内存位置
  2. Getter:获得实地的方法
  3. Setter:设置字段的方法

Fantom中的关键概念是,所有字段 自动 使用一个获取和设定方法(这一规则有一些例外,我们将在下文讨论)。 在Java 字段中,仅仅是一个存储位置. 大部分Java程序员然后用加热器和加热器方法包扎场面,从而产生大量的膨胀,锅炉板码. 然后所有访问都是通过一个方法调用完成的,这个方法并不完全是最漂亮的字段访问语法. C#更好,因为属性是头等语言构造. 然而,储存的管理仍然通过一个场地进行,并且需要同样的锅炉板码。 在Java和C#中使用的这些设计模式并不能非常干净地捕捉程序员的意图. 这些接入器中绝大多数是锅炉板码,实际上除了得到和设置电场之外别无他法. 加上它需要多种语言构造来模拟一个逻辑骗局

简单字段 ​

在最简单的情况下,场声明看起来就像Java或C#:

class Thing
{
  Int id := 0
  Str? name
}

在上述例子中 id 和 name 属于类中的实例字段 Thing我们使用 := 运算符为构造器执行前自动指定的字段提供初始值。 如果不提供初始值,则所有字段默认为 null 除外 value-types.

我们使用 . 将按照以下内容进行操作:

thing := Thing()
thing.name = "bob"
echo(thing.id)
echo(thing.name)

上面的代码中,一个获取器或设置器没有明确指定,所以它们由编译器自动生成. 所有进入球场的通道都是通过获得和设置方法. 所以代码 thing.name 这真的是一种方法调用到 name 获取方法。 这使得您以后可以添加一个明确的访问器,而无需将所有客户端代码重新编译到您的 API 中 。

你也可以说 ?. 操作员安全处理无效目标。 见 safe invokes.

注:如果不在非虚拟字段上提供获取器或设置器,那么编译器将优化字段对您的pod的内部访问. 所以在你的舱内,只有声明明确的接入器,才使用访问器方法. 在舱外,进入器方法总是用于非康斯坦丁字段. 抽象字段和虚拟字段总是使用访问器.

获取方法 ​

朋克语法 :

class Thing
{
  Int id := 0
  {
    get { echo("get id"); return &id }
    set { echo("set id"); &id = it }
  }
}

访问器部分以下列方式表示: {} 在初始值分配后(如果没有初始值,则在字段标识符后)设置。 你可以同时声明 get 和 set - 或者如果你只是宣布一个, 那么另一个是自动生成的。

在布置器内部, it 关键词引用了指定给字段的新值。 需要使用时 it 在关闭中,然后指定本地变量:

set { newVal := it; 3.times { echo(newVal) }; this.&f = newVal }

这个 & 存储运算符用于访问字段的原始存储(思考像C贬义运算符). 这个 & 运算符用于您ẹ访问字段的存储位置而无需通过访问器方法。 你可以用这个 & 操作员在任何类的方法中,但只有声明类被允许使用 & 运算符( 好像存储总是) private (范围) 在进入器内部,您必须使用 & 运算符(否则,您可能会出现无限复发)。

请注意: := 字段初始化总是直接设置存储,而不调用设置方法(见 below (作为本条规则的例外)。

计算字段 ​

在大多数情况下,编译器会自动声明您字段的存储内存. 然而,如果编译器发现一个字段的存储从未访问过,则没有分配内存. 我们称之为“计算场”。 计算过的字段必须同时声明一个不使用 & 操作员访问字段。 注意使用 := 隐含使用存储操作符,并将分配存储.

连接字段 ​

关键词 const 可以应用到您的字段,以表示该字段是不可改变的:

class Thing
{
  new make(Int id) { this.id = id }
  const Int id
}

在上述例子中, id 是一个实例字段。 康斯特实例字段只能设置在构造器中 。 它们的工作与Java Final 字段稍有不同——你可以随意设置多少倍(或者让他们默认为无效),但不能在构建过程之外分配给它们.

Fantom 还允许在建造过程中通过一个 it-block:

class Thing
{
  new make(|This f|? f := null) { if (f != null) f(this) }
  const Str name
}

t := Thing { name = "Brian" }   // ok
t { name = "Andy" }             // throws ConstErr

在它的块内,设置康斯特字段的检查从编译时间移到运行时间. 编译器将允许 ** it- block 设置一个康斯特字段 it但是,如果有人试图 反对中出现的障碍, ConstErr 被丢弃。 这使得您可以灵活地构建 API , 它可以捕捉用于修改不可改变类的代码片段 。

在通过 InStream.readObj 方法。

所有价值 const Alfredo. 字段必须是 immutable。这保证所有孔斯特字段都提到不会改变状态的例子。

Const 字段不使用访问器方法 。 在 const 字段中声明访问者是一个编译时间错误 。 所有外地访问都是直接针对储存进行的。

无法声明康斯特所谓字段 abstract or virtual。无论如何,一个康斯特域可以 override 无参数的虚拟方法。 但孔斯特字段不能覆盖字段,因为这意味着可以设置字段。 使用此技术使用预计算字符串的实例 Obj.toStr:

const class Something
{
  new make(...) { toStr = "..." }
  override const Str toStr
}

( ( ( ​

关键词 static 用于创建静态类级字段:

class Thing
{
  const static Int nullId := 0xffff
}

一个领域 nullId 是一个最静态的领域, 只有一个全球实例。 只能设置在 static initializers. 静态场必须是康斯特 - 这确保 thread safety。静态字段使用 . 该类本身的操作员 :

Thing.nullId

保护范围 ​

您可以使用一个字段的普通槽保护范围关键字: public, protected, internal,以及 private默认值为 public 如果省略了关键字。

可以缩小设置范围如下:

class Thing
{
  Int id { protected set }
  Str name { internal set { &name = it } }
}

在以上的例子中 id 和 name 被公开之用。 我们缩小了之用范围 id 设置要保护。 因为没有方法 机构设置 id,然后编译器自动生成一个。 声明 name 显示缩小范围 * 和 * 宣布明确设置。

只有保护范围关键词可用设置器。 从定义上来说,获取者总是这个领域本身的保护范围. 使用关键词如 const, virtual,或 abstract 在个人得到或设置。

仅读字段 ​

力图公开揭露田园里的占卜者, 可以通过缩小球场设置范围来实现 private 如以下代码所示:

class Thing
{
  Int id { private set }
  internal Int count { private set }
}

虚拟字段 ​

实例字段可以被宣告为虚拟,允许子类覆盖获取器和设置器方法:

class Thing
{
  virtual Int id := 0
}

class NewThing : Thing
{
  override Int id
  {
    get { echo("NewThing.id get"); return super.id }
    set { echo("NewThing.id set"); super.id = it }
  }
}

class AnotherThing : Thing
{
  override Int id := 100
}

在这个例子中 Thing.id 是虚拟的,允许子类覆盖它的获取器和设置器方法(在这种情况下) Thing.id 存取器是由编译器生成的自动). 班级 NewThing 覆盖访问器 id 添加一些追踪,然后呼叫超级接入器。 请注意,覆盖的字段必须指定 override 关键词必须使用相同的类型(不支持一致性)。

被淹没的田地没有获得自己的储存权,相反,他们应该委托给他们的超级接入者使用 super 关键词 如果超载需要自己的存储,您应当声明另一个字段用作存储 。

在上述例子中,阶级 AnotherThing 覆盖 Thing.id 通过提供不同的默认值。 由于没有指定明确的获取器或设置器,编译器会自动生成访问器. 然而,进入者 AnotherThing.id 电话 super.id 而不是直接访问存储。 还注意到,当覆盖的字段指定初始值时,该字段通过虚拟设置器而不是直接存储初始化.

在国际空间 ​

一个字段可以被宣布为抽象 :

abstract class Thing
{
  abstract Int id
}

class NewThing : Thing
{
  override Int id := 0
}

一个抽象的字段基本上是一个抽象的获取器和设定器,子类必须覆盖它. 摘要字段没有存储,不能有初始值. 抽象字段的覆盖通过 & 运算符(这与压倒普通虚拟字段不同)。

指定 ​

在所有代码路径中必须指定不可创建字段. 每个不连锁的建筑师 this 汝须设置不符合下列标准之一的每个不可创建字段:

  • 值类型字段不需要设置(Bool、Int、Float)
  • 有初始化器的字段不需要设置
  • 抽象或覆盖字段不需要设置
  • 本地的田地不需要设置
  • calcualted 字段不需要设置

匹配规则适用于静态初始器中所有代码路径必须设置的静态字段。

作为一种特殊的情况,一个以它块作为最后参数的构造者允许将确定的任务推迟到运行时间。 设置每个不可忽略的字段或一个 FieldNotSetErr 在运行时提高。 举个例子:

class Foo
{
  new make(|This| f) { f(this) }
  Str name
}

Foo {}                   // throws FieldNotSetErr
Foo { name = "Brian" }   // ok

超越方法 ​

一个字段的获取器可以用来覆盖一个没有参数的虚拟方法:

abstract class Named
{
  abstract Str name()
}

class Person : Named
{
  override Str name
}

在上述例子中 Named.name 是一个返回 a 的虚拟方法 Str分类 Person 覆盖 Named.name 使用字段的方法。 覆盖 Named.name 地图到 Person.name字段必须具体说明 override 关键词并必须有匹配类型 。 方法的字段覆盖 covariance - 可以使用比方法返回类型更具体的类型宣布字段。

这种设计图案是您范托姆代码中的一种实用技术.

混合 ​

混音只能宣告康斯特静态字段和抽象字段. 与抽象字段的混合基本上是在宣告一个 getter/setter的签名. 在实施与抽象领域的混合时,子类的责任是提供该领域的实施. 混音器的实地访问使用标准 . 点操作员也。

原生田园 ​

原生田用另一种语言实施,这种语言是每个目标平台的"原生". 原生田一般用爪哇语为Java VM,C#为.NET CLR. 土著田地使用 native 关键词并遵循similier规则来跳过字段 - 没有存储器或访问器 。 支持原住地的基础设施在2008年7月1日讨论。 Natives 章节。


职能 ​

概览 ​

函数是 Fantom 中通过 Func 课。 函数具有正式定义的签名为一组参数类型和一个返回类型. 不返回对象的函数有: Void 返回类型。 功能是三种方式之一:

  1. ** 方法**:所有方法都是真的 wrappers 函数
  2. ** 关闭**: closures 是导致函数的表达式
  3. Bind协助发展中国家: Func.bind 通过将参数绑定在已有函数上来创建新函数的方法

函数签名 ​

这个 Func 类是一个内置的通用类型,有自定义 parameterization syntax:

// format
|A a, B b ... H h -> R|

// examples
|Int a, Int b->Str|  // function which takes two Int args and returns a Str
|Int, Int->Str|      // same as above omitting parameter names
|->Bool|             // function which takes zero args and returns Bool
|Str s->Void|        // function which takes one Str arg and returns void
|Str s|              // same as above, omitting optional void return
|->Void|             // function which takes no arguments and returns void
|->|                 // shortcut for above

上面的例子是类型签名 很像你使用 Str or Str[].

调用函数 ​

功能只是像方顿其他一切一样的物体. 所有函数分类 Func 类,它提供了引用函数的方法。 最基本的方法是 Func.callList 它需要一份参数列表,然后返回结果(如果返回无效则无效):

// func is a function which takes two Int args and returns an Int
|Int a, Int b->Int| func
func.callList([7, 8])

这个 Func 类也支持 call 使用不同比例(0至8)调用的方法。 例如,用两个参数调用上面的函数:

func.call(7, 8)

使用 call ẻ العب العب العي العب الملع الملع العي العي الملع العي الك الك العيري العي العبي المري العي البري المري البري البي العي الريديري العي البي , الريريريد اري ارب ار اري اري اد ال اري اري ارب اري ار ا ار ار ار ار ار ار ار ار ار ار ا ا ا ار ا ا ا ا ا ا ا ا ار ا

Fantom 也支持一些语法糖来调用一个函数,如普通方法调用 () 操作员。 例如,我们可以使用此语 我们去调用上面的函数 :

func(7, 8)    // syntax sugar for func.call(7, 8)

类型兼容性 ​

函数在类型兼容性方面有一些特殊规则. 类型兼容的逻辑是该类型 A 类型兼容 B if A 任何时候都可以使用 B 预计将。 大多数时候,这意味着 A 从 B 通过继承。 比如说 Int 类型兼容 Num 因为任何地方 Num 期待,我们可以通过一个 Int.

一个函数的类型声明意味着"这些就是我要传递到这个函数的参数,以及我期望返回的结果". 所以函数类型 A 与函数类型兼容 B if A 可以接受以下论点: B 声明会通过并返回预期类型 。 具体而言,函数类型 A 与函数类型兼容 B 如果这些规则适用:

  1. A 声明与 B
  2. 每一参数类型 A 与其相应的参数类型兼容 B
  3. A 返回兼容的类型 B (或 B 返回为空,然后 A 能够返回任何东西)

下表的事件表明: Type.fits 将报告:

Num  fits  Int  =>  false
Int  fits  Num  =>  true

|Int a|  fits  |Int a|  =>  true
|Num a|  fits  |Int a|  =>  true
|Int a|  fits  |Num a|  =>  false

|Int a|  fits  |Int a, Int b|  =>  true
|Int a, Int b|  fits  |Int a|  =>  false

|->Void|  fits  |->Int|    =>  false
|->Int|   fits  |->Void|   =>  true
|->Int|   fits  |->Num|    =>  true
|->Num|   fits  |->Int|    =>  false

上表的前 你应该参考两个项目: Int 适合一个 Num但反之亦然 接下来让我们仔细看看这个例子:

|Num a|  fits  |Int a| =>  true

它显示的是,如果一个函数类型被宣布使用 Int我们可以通过一个接受 Num。这在开始时可能显得与直觉相反,但记住函数是普通类型检查的翻转侧面。 以下是这个概念的具体例子,用一个典型的Java或C#程序员可能发现更自然:

class WidgetEvent {}
class ButtonEvent : WidgetEvent {}
addButtonListener(|ButtonEvent event| callback)

已在上面的代码中 ButtonEvent 是一个子类 WidgetEvent。我们有一个方法,在按下按钮时登记回调以引用。 ButtonEvent然而,如果我们碰巧有一个接受任何 WidgetEvent,那么它会相当高兴地接受 ButtonEvent 参数 :

anyWidgetCallback := |WidgetEvent event| {...}

button.addButtonListener(anyWidgetCallback)

这就是函数是正常类型检查的“滑动侧”的含义。 在正常类型检查接受任何类型中的 * 特殊性* 时, 函数类型检查接受任何函数的 * 一般性* 。

参数兼容性 ​

接下来,让我们通过解析这些例子,来研究在功能类型兼容性中存在多大的偏差(参数数):

|Int a|  fits  |Int a, Int b|  =>  true
|Int a, Int b|  fits  |Int a|  =>  false

在这里,我们看到,一个功能 接受一个 Int 与生成两个函数类型的兼容 Ints。这是Fantom中所有函数的一种能力——接受比它们使用更多的参数。 有点像 default parameters 我感觉很正常 我们 我感觉我们经常在核心课上使用这种技术 比如说 Map.each 方法用于对密钥/值配对进行直线化:

// actual signature of Map.each
Void each(|V value, K key| c)

// iterate with function that only accepts value
map.each |Obj value| { echo(value) }

// or iterate with function that accepts both value and key
map.each |Obj value, Obj key| { echo("$key = $value") }

许多接受一个函数的API会通过多个参数,但实际上不需要使用所有这些参数.

方法 ​

在 Fantom 中,所有方法都通过 Method.func 方法。 那个 Func 对于一种方法来说,它是它的反射柄。 这种函数与方法之间的关系是Fantom如何将对象导向和功能编程(翻转的一面是所有函数都是对象)的关键方面.

待态方法映射到函数是直线的:

static Int add(Int a, Int b) { return a + b }

func := type.method("add").func
nine := func(7, 2)

你无法进入 Method.func 不带括号的方法,然后使用括号来引用函数,因为括号将绑定在 Method.func 电话:

type.method("add").func               // returns Func
type.method("add").func()             // same as above

type.method("add").func().call(7,2)   // invoke function
type.method("add").func()(7,2)        // same as above

实例方法映射到第一个参数是隐含的函数 this 参数。 如果你曾经用过Python 这个概念在你的脸上 几乎是明确的 self 参数。 Fantom 允许您使用 Java 或 C# 等实例方法, 但我们仍然需要将这些 OO 方法映射到函数上 。 举个例子:

m := Str#replace
f := m.func

// note method params does not include the
// implicit this, but the function params does
m.params  => [sys::Str from, sys::Str to]
f.params  => [sys::Str this, sys::Str from, sys::Str to]

// both of these result in "hello!"
s1 := "hi!".replace("hi", "hello")
s2 := f("hi!", "hi", "hello")

上面的代码得到 Str.replace 实例方法作为函数。 那个 replace 方法需要两个字符串参数,但当平整成函数时,需要三个字符串参数,因为我们必须考虑隐含的 this 参数。

可移动函数 ​

一个无法改变的ẩn 函数被证明是线性安全。 您可以通过运行时间ẩn 。 Obj.isImmutable 并尝试通过 Obj.toImmutable. 工作时经常需要不可移动的功能 actors.

通用性作用如下:

  • 方法函数总是不可改变的 - 看 Method.func 丫头 :
  • 捕捉非最终或非临界变量的关闭总是可变的;不可变的总是抛出不可变的 Err
  • 关闭以捕捉在运行时间(例如Obj或List)之前无法永久变数的非最终变数将返回不实的易变数,但将提供一个无法变数的方法,试图通过调用每个变量的易变数来约束当前变数
  • 由 Func.bind 如果原始函数是不可改变的,则不可改变。
  • 最终变量* 的定义是一个变量,在初始化后从未被重新分配. 任何重新分配的变量都被视为非最终变量。

语法 ​

你真是个小人物 ​

Fantom BNF语法的传说:

:=      is defined as
<x>     non-terminal
"x"     literal
[x]     optional
(x)     grouping
x*      zero or more times
x|x     or

汇编股 ​

<compilationUnit> :=  <using*> <typeDef>* [<doc>]
<using>           :=  <usingPod> | <usingType> | <usingAs>
<usingPod>        :=  "using" <podSpec> <eos>
<usingType>       :=  "using" <podSpec> "::" <id> <eos>
<usingAs>         :=  "using" <podSpec> "::" <id> "as" <id> <eos>
<podSpec>         :=  <id> | <str> | <ffiPodSpec>
<ffiPodSpec>      := "[" <id> "]" <id> ("." <id>)*

类型Def ​

<typeDef>        :=  <classDef> | <mixinDef> | <facetDef> | <enumDef>

<classDef>       :=  <typeHeader> "class" <id> [<inheritance>] "{" <slotDefs> "}"
<mixinDef>       :=  <typeHeader> "mixin" <id> [<inheritance>] "{" <slotDefs> "}"
<facetDef>       :=  <typeHeader> "facet" "class" <id> [<inheritance>] "{" <slotDefs> "}"
<enumDef>        :=  <typeHeader> "enum" "class" <id> [<inheritance>] "{" <enumValDefs> <slotDefs> "}"

<typeHeader>     :=  [<doc>] <facets> <typeFlags>
<typeFlags>      :=  <typeFlag>*
<typeFlag>       :=  <protection> | "abstract" | "final" | "const" | "native"
<protection>     :=  "public" | "protected" | "private" | "internal"
<inheritance>    :=  ":" <typeList>

槽减法 ​

<enumValDefs>    :=  <enumValDef> ("," <enumValDef>)* <eos>
<enumValDef>     :=  <facets> <id> ["(" <args> ")"]

<slotDefs>       :=  <slotDef>*
<slotDef>        :=  <fieldDef> | <methodDef> | <ctorDef> | <staticInit>

<fieldDef>       :=  <facets> <fieldFlags> <type> <id> [":=" <expr>]
                     [ "{" [<fieldGetter>] [<fieldSetter>] "}" ] <eos>
<fieldFlags>     :=  <fieldFlag>*
<fieldFlag>      :=  <protection> | "abstract" | "const" | "final" | "native" |
                     "override" | "readonly" | "static" | "virtual"
<fieldGetter>    :=  "get" (<eos> | <block>)
<fieldSetter>    :=  <protection> "set" (<eos> | <block>)

<methodDef>      :=  <facets> <methodFlags> <type> <id> "(" <params> ")" <methodBody>
<methodFlags>    :=  <methodFlag>*
<methodFlag>     :=  <protection> | "abstract" | "native" | "once" |
                     "override" | "static" | "virtual" | "final"
<params>         :=  [<param> ("," <param>)*]
<param>          :=  <type> <id> [":=" <expr>]
<methodBody>     :=  <eos> | ( "{" <stmts> "}" )

<ctorDef>        :=  <facets> <ctorFlags> "new" <id> "(" <params> ")" [ctorChain] <methodBody>
<ctorFlags>      :=  [<protection>]
<ctorChain>      :=  ":" <ctorChainThis> | <ctorChainSuper>
<ctorChainThis>  :=  "this" "." <id> "(" <args> ")"
<ctorChainSuper> :=  "super" ["." <id>] "(" <args> ")"

<staticInit>     :=  "static" "{" <stmts> "}"

面额 ​

<facets>        := <facet>*
<facet>         := "@" <simpleType> [<facetVals>]
<facetVals>     := "{" <facetVal> (<eos> <facetVal>)* "}"
<facetVal>      := <id> "=" <expr>

质 ​

<block>          :=  <stmt> | ( "{" <stmts> "}" )
<stmts>          :=  <stmt>*
<stmt>           :=  <break> | <continue> | <for> | <if> | <return> | <switch> |
                     <throw> | <while> | <try> | <exprStmt> | <localDef> | <itAdd>
<break>          :=  "break" <eos>
<continue>       :=  "continue" <eos>
<for>            :=  "for" "(" [<forInit>] ";" [<expr>] ";" [<expr>] ")" <block>
<forInit>        :=  <expr> | <localDef>
<if>             :=  "if" "(" <expr> ")" <block> [ "else" <block> ]
<return>         :=  "return" [<expr>] <eos>
<throw>          :=  "throw" <expr> <eos>
<while>          :=  "while" "(" <expr> ")" <block>
<exprStmt>       :=  <expr> <eos>
<localDef>       :=  [<type>] <id> [":=" <expr>] <eos>
<itAdd>          :=  <expr> ("," <expr>)* [","] <eos>

<try>            :=  "try" <block> <catch>* [<finally>]
<catch>          :=  "catch" [<catchDef>] <block>
<catchDef>       :=  "(" <type> <id> ")"
<finally>        :=  "finally" <block>

<switch>         :=  "switch" "(" <expr> ")" "{" <case>* [<default>] "}"
<case>           :=  "case" <expr> ":" <stmts>
<default>        :=  "default" ":" <stmts>

过期 ​

<expr>           :=  <assignExpr>
<assignExpr>     :=  <ifExpr> [<assignOp> <assignExpr>]
<assignOp>       :=  "=" | "*=" | "/=" | "%=" | "+=" | "-="

<ifExpr>         :=  <ternaryExpr> | <elvisExpr>
<ternaryExpr>    :=  <condOrExpr> ["?" <ifExprBody> ":" <ifExprBody>]
<elvisExpr>      :=  <condOrExpr> "?:" <ifExprBody>
<ifExprBody>     :=  <condOrExpr> | <ifExprThrow>
<ifExprThrow>    :=  "throw" <expr>

<condOrExpr>     :=  <condAndExpr>  ("||" <condAndExpr>)*
<condAndExpr>    :=  <equalityExpr> ("&&" <equalityExpr>)*
<equalityExpr>   :=  <relationalExpr> [("==" | "!=" | "===" | "!==") <relationalExpr>]
<relationalExpr> :=  <typeCheckExpr> | <compareExpr>
<typeCheckExpr>  :=  <rangeExpr> [("is" | "as" | "isnot") <type>]
<compareExpr>    :=  <rangeExpr> [("<" | "<=" | ">" | ">=" | "<=>") <rangeExpr>]
<rangeExpr>      :=  <addExpr> ((".." | "..<") <addExpr>)*
<addExpr>        :=  <multExpr> (("+" | "-") <multExpr>)*
<multExpr>       :=  <parenExpr> (("*" | "/" | "%") <parenExpr>)*
<parenExpr>      :=  <unaryExpr> | <castExpr> | <groupedExpr>
<castExpr>       :=  "(" <type> ")" <parenExpr>
<groupedExpr>    :=  "(" <expr> ")" <termChain>*
<unaryExpr>      :=  <prefixExpr> | <termExpr> | <postfixExpr>
<prefixExpr>     :=  ("!" | "+" | "-" | "++" | "--") <parenExpr>
<postfixExpr>    :=  <termExpr> ("++" | "--")

<termExpr>       :=  <termBase> <termChain>*
<termBase>       :=  <literal> | <idExpr> | <typeBase>
<typeBase>       :=  <typeLiteral> | <slotLiteral> | <namedSuper> | <staticCall> |
                     <dsl> | <closure> | <simple> | <ctorBlock>
<typeLiteral>    :=  <type> "#"
<slotLiteral>    :=  [<type>] "#" <id>
<namedSuper>     :=  <type> "." "super"
<staticCall>     :=  <type> "." <slotExpr>
<dsl>            :=  <type> "<|" <anyChar>* "|>"
<simple>         :=  <type> "(" <expr> ")"
<ctorBlock>      :=  <type> <itBlock>
<termChain>      :=  <dotCall> | <dynCall> | <safeDotCall> | <safeDynCall> |
                     <indexExpr> | <callOp> | <itBlock>
<itBlock>        :=  "{" <stmts> "}"
<dotCall>        :=  "." <slotExpr>
<dynCall>        :=  "->" <slotExpr>
<safeDotCall>    :=  "?." <slotExpr>
<safeDynCall>    :=  "?->" <slotExpr>
<idExpr>         :=  <local> | <slotExpr>
<slotExpr>       :=  <field> | <call>
<local>          :=  <id>
<field>          :=  ["&"] <id>
<call>           :=  <id> ["(" <args> ")"] [<closure>]
<args>           :=  [<expr> ("," <expr>)*]
<indexExpr>      :=  "[" <expr> "]"
<callOp>         :=  "(" <args> ")" [<closure>]

<literal>        :=  "null" | "this" | "super" | "it" |
                     <bool> | <int> | <float> | <decimal> | <str> |
                     <duration> | <uri> | <list> | <map> | <uri>
<list>           :=  [<type>] "[" <listItems> "]"
<listItems>      :=  "," | (<expr> ("," <expr>)*) [","]
<map>            :=  [<mapType>] "[" <mapItems> [","] "]"
<mapItems>       :=  ":" | (<mapPair> ("," <mapPair>)*)
<mapPair>        :=  <expr> ":" <expr>

<closure>        :=  <closureSig> "{" <stmts> "}"
<closureSig>     :=  "|" <closureParams> ["->" <type>] "|"
<closureParams>  :=  [<closureParam> ("," <closureParam>)*]
<closureParam>   :=  <formal> | <id>

见 Literals 用于文字符号的语法.

类型 ​

<type>           :=  <nullType> | <nonNullType>
<nullType>       :=  <nonNullType> "?"
<nonNullType>    :=  <simpleType> | <listType> | <mapType> | <funcType>
<typeList>       :=  <type> ("," <type>)*
<simpleType>     :=  <id> ["::" <id>]
<listType>       :=  <type> "[]"
<mapType>        :=  ["["] <type> ":" <type> ["]"]
<funcType>       :=  "|" [formals] ["->" <type>] "|"
<formals>        :=  [<formal> ("," <formal>)*]
<formal>         :=  <formalFull> | <formalInferred> | <formalTypeOnly>
<formalFull>     :=  <type> <id>
<formalInferred> :=  <id>
<formalTypeOnly> :=  <type>

杂项 ​

<id>             := <idStart> (idChar)*
<idStart>        := A-Z | a-z | _
<idChar>         := A-Z | a-z | _ | 0-9
<eos>            := ; | \n | }

关键词 ​

abstract       foreach        return
as             if             static
assert         internal       super
break          is             switch
case           isnot          this
catch          it             throw
class          mixin          true
const          native         try
continue       new            using
default        null           virtual
do             once           volatile
else           override       void
false          private        while
final          protected
finally        public
for            readonly

位置关键词 ​

enum
facet

继承 ​

概览 ​

继承是在定义新类型时用于重复使用现有类型的机制. 我们使用遗产有两个目的:

合同 ​

类型定义明示和默示 contract. 隐含的合同定义语义,以帮助人类理解代码. 明确合同由一套公共时间段界定。 举个例子:

class File
{
  virtual Int size() {...}
  private Void checkNotDir() {...}
}

在类文件上方的代码中, 声明一种名为 size 返回一个 Int。这是该类型合同的一部分,例如: File我们总是知道会有一种方法 size 返回一个 Int 对于文件中的字节数。

另一方面, checkNotDir 方法不是该类型合同的一部分,因为它是私人合同。 这是班级的落实细节,而不是公共API.

当我们创建一个子类的File, 我们专门的合同:

class HttpFile : File
{
  Str:Str httpHeaders
}

依次分类 File,则 HttpFile 类继承合同 File 并且必须支持所有相同的公共空间。 然而,我们也通过增加HTTP的特定特性,如曝光HTTP头等,专门进行基础类.

执行 ​

如果一个类型只声明抽象的槽,那么一个子类会继承纯粹的类型合同——这就是在Java或C#中宣布接口时发生的情况. 然而,在Fantom语中,无论是类还是混音,都可以宣布其槽的实现. 子类随后可以继承其超级类型槽的执行. 我们称之为“执行再利用” —— 它给了我们一个方便的机制 来组织我们的代码和保持事物的美好, DRY.

语法 ​

继承的语法是,在分号之后的分类声明中列入零或更多的类型定义:

// inheriting from Obj
class SubObj {}
class SubObj : sys::Obj {}

// class inheritance
class SubClassA : BaseClass {}
class SubClassB : MixinA, MixinB {}
class SubClassC : BaseClass, MixinA, MixinB {}

// mixin inheritance
mixin MixinC : MixinA {}
mixin MixinD : MixinA, MixinB {}

在某些情况下,声明的顺序确实很重要。 如果继承类型包括一个阶级和一个或多个混血儿,那么必须首先宣布该阶级类型.

继承规则 ​

以下规则定义子类型如何继承槽 :

  1. 承建者永远不会继承
  2. 私人名额从未继承
  3. 内部槽只按同一舱内的类型继承
  4. 所有其他时间段都继承

这些规则遵循了在讨论 contract specialization私人和内部的插槽是执行细节,所以它们不会成为该类型合同的一部分. 建构者总是被固定在他们的申报类上,所以它们也没有被继承. 这些规则被编译器和反射API都应用,以确定特定类型的槽命名空间.

继承限制 ​

上面列出的继承规则定义了哪些插槽被继承到子类型的插槽命名空间. 记住,一个类型的插槽只按名键,所以一个类型在任何情况下都不能有两个同名的不同插槽. 由于这个轴, 有一些案例防止创建一个子类型 与冲突超级类型:

1.同名静态方法的两种类型不能合并为子类型. 2. 有同名的康斯特字段(实例或静态)的两种类型不能合并为子类型. 3. 两个具有同名实例槽和不同签名的类型不能合并为子类型. 4. 两种具有相同名称和相同签名实例槽的类型可以合并,但前提是:

  1. 一个是具体的,另一个是已将它抽象的 2.两者均为虚拟和子类型,以提供明确的定义

利用上述规则,范通避免了钻石继承问题. 第一混音无法宣布混凝土字段,这意味着它们永远不会存储状态. 其次,由于钻石继承或其他原因而产生的任何模糊之处,要求子类明确分离(或者如果继承的槽不是虚拟的,那么子类型就根本无法创建).

覆盖 ​

当继承一个或多个超级类型的插槽时,一个类型可以选择覆盖超级类型的任何虚拟插槽. 有三种推翻机制:

  1. 方法胜过方法(见 virtual methods)
  2. 外地覆盖 virtual fields)
  3. 外地覆盖方法(见 overriding a method)

共性 ​

通常在超时段时,超时段的签名必须与超级类型的签名完全吻合. 然而,在某些情况下,一种方法的返回类型可能会缩小----这一特征被称为“一致性”。 共变是一种专业化的技术,因为超级型的合约仍然完好无损,我们只缩小了子型的合约. 以下详细介绍了共性支持:

  1. 方法高于: details
  2. 字段覆盖 字段: 不支持
  3. 实地覆盖方法:支持- details

超级 ​

通常在超越一种方法或领域时,最好称超级类型的执行. 使用 super 关键词 有两种方法 super:

// unnamed super
super.someMethod()

// named super
Base.super.someMethod()

以下规则界定了使用 super:

委员会将一个无名的超级人 永远决定超级阶级 永远不要超级混音 2. 很明显,你不能用一个名牌的超级人 来对付不属于你的超级人 3. 命名的超级只能用于混音类型,不能使用带有类型的命名超级;这意味着在子类中,您只能访问您的直接子类的实现(这是对Java's的限制问题). invokespecial 代码) 4. 混音器在超级混音器上可以使用命名的超级音效,但从不使用未命名的超级音效.


接口 ​

概览 ​

Fantom是建立在写代码可移植到多样运行时环境的原则之上的. 今天我们的目标是三次运行:

  • Java: 使用 Java VM 作为主服务器运行时间; 与 Java 代码的互操作性
  • JavaScript:目标浏览器和节点JS;与JavaScript和TypeScript的互操作性
  • .NET73. : 遗产, 不支持的. NET CLR的目标; 与 C# 的互操作性

选项 ​

以下选项提供不同的交叉路径:

  • Natives: 允许在本地 Java 或 JavaScript 中执行 Fantom 类型/ slots
  • JavaFFI:允许 Fantom 代码直接调用 Java API
  • Java: 将 Fantom 转换为 Java 源代码通过
  • JavaScript: 转写 Fantom 到 JavaScript 源代码
  • JarDist: 将 Fantom 包成 Java 罐

也见 Fanc.

爪哇语 ​

Fantom将Java作为它的主要运行时间,特别是服务器侧应用. Java 运行时间完全支持通过 Concurrency 和 Actors. Out of the box, Fantom 设计通过Java VM运行. 您还可以使用 JarDist 和 Fanc 将 Fantom pod包成库,供 Java 代码使用。

Java 脚本 ​

Fantom旨在用TypeScript绑定的方式交叉编译为JavaScript. 这主要用于写可执行浏览器前端的便携式代码. 大多数 sys API也可以在NodeJS中使用,包括文件和zip API. 但请注意,在联署材料环境中没有假设多条线的货币性API。

C#和.NET (中文(简体) ). ​

最初的Fantom框架针对Java和.NET/C#. 然而,目前不支持.NET。 最初的Fantom API大多完全在.NET中工作,基础设施就位. 因此,只要付出一定努力,.NET就可以提高生产质量。


爪哇语 ​

概览 ​

Fantom pocks可以使用两种不同的机制作为Java库:

  • JarDist:建造任务,将Fantom舱建成罐子
  • Fanc:将 Fantom pod移植到 Java 源的命令行

主要区别在于JarDict在运行时将Fantom fcode包上并释放Java类文件. 这种制作体积较小,保证能使用所有的Fantom代码. 使用fanc将Fantom转录到Java中,为Fantom提供了一个Java代码库,更容易融入工具链和IDE提示. 但Fanc对Fantom代码有限制,

Java 转写 ​

使用下列常规将 Fantom 转换为 Java

  • 任何带有“美元”的种类/槽名称都是合成的,不应使用
  • Const字段编译为 Java 获取器方法
  • 可变字段编译为 Java 获取器和设置器方法
  • 构造器被编译成静态工厂方法(从未直接使用Java构造器)
  • Mixins被编译为Java接口,并将使用Java默认方法(注意Java接口除了公开范围不支持任何会暴露不想要的细节)

繁體中文 ​

以下的 Fantom 语言特性在转录到 Java 代码时无法使用 :

  • Null safety and elvis 表达式只能有效使用巷道范围内的最终变量(因为它们必须编译为Java关闭)
  • 在多个情况下切换语句不能使用相同的变量名称
  • ++ 和 -- 仅限于简单的变量和字段(无法在列表索引中使用)
  • 链式任务表达式,例如 a = b = c 可能无法正确编译
  • 无法在混音中覆盖等于

爪哇福尔 ​

概览 ​

Java外国函数接口(或Java FFI)是允许Fantom代码方便使用普通Java库的一种功能. Java FFI基本上是将Java类型系统映射到Fantom类型系统:

  • Java软件包 QQ 扇形pod
  • 爪哇语班 Fantom 课程
  • Java 接口 QQ Fantom 组合
  • 爪哇战区 方图字段 恪守《公约》的规定恪守方图方法

Fantom是设计在JVM上运行的,因此两个世界之间的绘图相当直线,具有高度的互操作性. 然而,这两种系统之间存在着一定程度的阻碍不匹配。 Java FFI 支持的特性 :

  • 与任何Java API接口,但使用多维数组的除外
  • 静态类型检查
  • 从 Fantom 调用超载的 Java 方法
  • 使用 Fantom 构造 Java 类 constructor syntax
  • 扩展Java类(目前只允许一个级别的遗产)
  • 执行Java接口
  • Java原始人和 sys::Int, sys::Float
  • 一维物体阵列和 sys::List
  • Java一维原始阵列的直接绘图
  • 对Java成员的Fantom反射支持
  • 动态引用支持对 Java 类

Java FFI 中尚不具备的特征 :

  • 多维数组
  • 将一个爪哇级分为一个以上深层
  • 试图超越 Java 超载方法; 这意味着您不能从带有抽象超载方法的类型中分级或扩展

不辅佐的特性会导致编译时间错误 。

艰险摘要 ​

下表汇总了Java类型到Fantom类型的映射:

Java Type            Fantom Type
-----------          -----------
foo.bar.Baz          [java]foo.bar::Baz
boolean              sys::Bool
byte                 sys::Int
short                sys::Int
char                 sys::Int
int                  sys::Int
long                 sys::Int
float                sys::Float
double               sys::Float
java.lang.Object     sys::Obj
java.lang.String     sys::Str
java.lang.Boolean    sys::Bool?
java.lang.Long       sys::Int?
java.lang.Double     sys::Float?
java.math.BigDecimal sys::Decimal
Foo[]                Foo[]  // sys::List parameterized with Foo
boolean[]            [java]fanx.interop::BooleanArray
byte[]               [java]fanx.interop::ByteArray
short[]              [java]fanx.interop::ShortArray
char[]               [java]fanx.interop::CharArray
int[]                [java]fanx.interop::IntArray
long[]               [java]fanx.interop::LongArray
float[]              [java]fanx.interop::FloatArray
double[]             [java]fanx.interop::DoubleArray
Foo[][]              // unsupported for both primitivies and Objects

午饭时间 :

Java                         Fantom
--------------------------   --------------------------
import javax.swing           using [java] javax.swing
import java.util.Map.Entry   using [java] java.util::Map$Entry as Entry
JFrame f = new JFrame(...)   f := JFrame(...)
array.length                 array.size
array[i]                     array[i]
array[i] = val               array[i] = val
int[] x = new int[5]         x := IntArray(5)

如何运作 ​

Java FFI没有使用任何特殊的Fantom语法. Java API 通过普通导入到 Fantom 类型系统 using 语句,并带有cock名称的特殊语法。 Java 套件通过前缀字符串映射到 Fantom pod "[java]"Venice. Java 软件包 javax.swing 拥有 Fantom pod名称为 [java]javax.swing.

Fantom 编译器本身对 Java 库没有了解,而是支持基于 pod 的 FFI 插件 "[ffi]"对于爪哇FFI, compilerJava pock是将Java类型系统导入Fantom的编译器插件.

使用 Java FFI 的 Fantom 代码得出一个普通的 pock 文件编译成 fcode 。 唯一的区别是fcode包含类型和成员引用是Java特定. 这意味着带有Java FFI呼叫的Fantom pocks不一定是可移植的;试图在JavaScript或.NET环境中使用Java FFI呼叫将失败.

类路径 - 编译器 ​

在晴朗编译 我告诉你,Java FFI编译器试图使用以下类路径解决Java软件包和类:

1.lice在"sun.boot.class.path"中发现的罐子 2. {java}lib/rt.jar(只有在以上步骤找不到任何东西的情况下) 3. {java}lib/ext/*.jar 4. {fan}lib/java/ext/*.jar 5. {fan}lib/java/ext/{Env.platform}/*.jar 6. 在"java.class.path"系统属性中发现的罐子 7. 依赖链所掌握的任何舱位

您可以测试您的系统类路径, 查看使用此命令发现了哪些文件和软件包 :

fan compilerJava::ClassPath

类路径 - 运行时间 ​

Fantom JVM运行时间使用每舱一个类载荷器. 与上述编译时间相同的类路径在运行时间应用. 唯一的区别是Fantom运行时间代表到标准系统类装载器,从1-3级解决类. 步骤4-7由 FanClassLoader.

JVM 上的 Fantom 类加载的详细步骤 :

  1. 分类文件载于 pock zip: 在特定情况下, foo::Bar 预编译类文件扇/foo/Bar类的地图" 2. Java 类型 acme.foo.Bar 地图到“ ACME/ foo/ Bar. class ”
  2. 方图类型 foo::Bar 这一议题载于下文。
    1. 第一个解析 pock 文件 "foo.pod" 通过 Env
    2. 从"fcode/Bar.fcode"中发送类文件
  3. 在依赖的舱中搜索预编译的类文件
  4. "{fan.home}/lib/java/ext"中的搜索扩展罐
  5. "{fan.home}/lib/java/ext/{平台}>中的搜索扩展罐(见 Env)
  6. 系统级装载器,通常包括“{java.home}/lib/ext”

您可以将 Java 代码捆绑在一个 pock zip 文件中, 它将被显示在您的 pock 内部的类 以及任何依赖您的 pock 的 pock 。 您可以使用包含 JAR 的构建脚本轻松转换为 JAR 文件为 Fantom 显示器 resource file.

初级 ​

特殊的爪哇原始人 boolean, long,以及 double 被默认地映射到 Fantom 类型 sys::Bool, sys::Int,以及 sys::Float 页:1 其他爪哇原始种的分布如下:

byte    sys::Int
short   sys::Int
char    sys::Int
int     sys::Int
long    sys::Int
float   sys::Float

上面的特殊原始语没有Fantom类型系统的直接支持. 因此,您不能把它们用作本地变量, 或用于字段或方法签名 。 他们总是被强迫/从他们的Fantom代理。

反思 ​

所有 Java 对象维护 a Type 重置站以提供Fantom风格反射. 您可以使用动态调用 toClass 获取 java.lang.Class 页:1 sys::Type:

ArrayList#            // evaluates to sys::Type
ArrayList#->toClass   // evaluates to java.lang.Class

您也可以使用这个 fanx.interop.Interop 要转换的类别 java.lang.Class 页:1 sys::Type:

Interop.toFan(cls)     // evaluates to sys::Type
Interop.toJava(type)   // evaluates to java.lang.Class

矩阵 ​

对象阵列被默认为 Fantom 。 sys::List。如果调用返回一系列对象的方法,则将其框列为适当的类型方图。 同样,当需要Java阵列时,您也会通过一个适合类型的 Fantom 列表。

初级阵列在没有任何拳击/脱箱的情况下特别处理。 它们通过特殊类型在Fantom类型系统中得到体现:

boolean[]  [java]fanx.interop::BooleanArray
byte[]     [java]fanx.interop::ByteArray
short[]    [java]fanx.interop::ShortArray
char[]     [java]fanx.interop::CharArray
int[]      [java]fanx.interop::IntArray
long[]     [java]fanx.interop::LongArray
float[]    [java]fanx.interop::FloatArray
double[]   [java]fanx.interop::DoubleArray

这个 make, get, set,以及 size 方法为与原始阵列合作提供了象征性的表示。 它们直接被映射到Java字节代码中的单个opcode:

int[] x = new int[4]  =>  x := IntArray(4)
x[2]                  =>  x[3]
x[3] = 5              =>  x[3] = 5
x.length              =>  x.size

可忽略 ​

任何使用引用类型的Java API都会被映射到 Fantom 类型系统中作为无效:

// Java methods
String foo(Object x, int y, Foo z)
void bar(String[] x) {}

// Fantom representation
Str? foo(Obj? x, Int y, Foo? z)
Void bar(Str?[]? x) {}

说明 String[] 我们假设整个阵列可能是无效的,或者任何阵列项目都可能是无效的,所以方图映射是 Str?[]?.

超载方法 ​

一个阻碍Java和Fantom之间的不匹配是Java允许一个字段和方法拥有同一个名称. Java还允许方法 * overloading *,其中可以以不同的参数类型宣布多种同名方法.

Fantom只允许对给定名称的槽进行单一定义. 然而,当调用到Java类型时,编译器会正确解决超载的字段和方法. 让我们考虑一下Java级:

class Foo
{
  String a;
  String a() { return a; }
  void a(String x) { a = x; }
  String b() { return a; }
}

以上班级 a 被一个字段和两种方法所超载。 让我们看看Fantom代码是如何解决针对Java成员的:

foo.a        // lack of parens indicates field get
foo.a = "x"  // field set
foo.a()      // call Foo.a() method
foo.a("x")   // call Foo.a(String) method
foo.b()      // call Foo.b() method
foo.b        // call Foo.b() method - no ambiguity so we can omit parens

解决对方法超载版本的调用遵循与Java语言规格相同的规则. 如果参数与任何方法不匹配,或者参数与多种方法的匹配模棱两可,则属于编译时间错误。

构造器 ​

封面下方的Fantom将Java构造器和Java VM一样对待它们——作为特殊的方法,命名为: <init>。标准 constructor syntax 用于引用 Java 构造器:

a := Date()
b := Date(millis)
c := Date(2008-1900, 11, 13) // crazy API to create 13-Dec-2008

构造器呼叫是针对 Java 构造器使用同样的规则解决的 overloaded methods.

子分类 ​

Java FFI使您既能从Java类扩展,也能执行Java接口. 例如创建一个子类: java.util.Date:

using [java] java.util::Date as JDate
class FanDate : JDate
{
  new now() : super() {}
  new make(Int millis) : super(millis) {}
}

标准 inheritance syntax 用于扩展和执行 Java 类型。

Fantom子类必须定义Fantom构造器如何称呼Java超级类构造器. 这是通过呼叫 super 作为超载的构造器(如果基级有多个构造器的话). 你可能不会使用 this 构造器链.

Fantom 类的构造器,从 Java 类中分级作为真正的 Java 构造器发射 。 因此,您可能不会声明两个具有相同参数类型的Fantom构造器,因为它会导致重复的Java构造器. 例如,以下代码会导致编译时间错误:

class FanDate : Date
{
  new now() : super() {}
  new epoch() : super(0) {}
}

由于Fantom构造器和Java构造器之间的这种区别,目前有一个限制,你只能从Java一级深处分级. 你可能不会从Fantom级中分级 本身从Java级中分级

另一个限制:因为一个 Fantom 类可能不会超载方法,所以你可能不会创建继承抽象超载方法的混凝土 Fantom 类.

覆盖 ​

当一个 Fantom 类型子类为 Java 类型时, 您可以使用正常的 Fantom 语法来覆盖 Java 方法, 限制如下 :

  • 不能超越静态或最终方法
  • 无法超载同名字段的方法
  • 无法超过参数类型超载的方法
  • 无法推翻在签名中使用多维数组的方法

举个例子:

// Java class
class Java
{
  void alpha() {}
  void alpha(Java x) {}
  void beta(Java x) {}
  int gamma(String[] x) {}
}

在上述例子中,方法 alpha 被参数类型所超载,因此Fantom类不允许将其覆盖。 然而,我们可以超越 beta 和 gamma:

class Fantom : Java
{
  override Void beta(Java? x) {}
  override Int gamma(Str?[]? x) {}
}

当我们超过一个 Java 方法时, 我们使用 Fantom 类型代表签名 。 在那个 gamma 我们映射返回类型的方法 int to sys::Int 参数类型 String[] 到 Fantom 列表 Str[].

内部类 ​

Java源的内类使用点格式化,但在Java VM中则使用"$"美元符号表示. 我们使用 Java VM 名称将内部类导入 Fantom 类型系统:

using [java] java.util::Map$Entry as Entry

请注意,既然"美元"不是方图姆语中的合法识别字符,你必须 rename Java 内类为有效的 Fantom 标识符 as 关键词

动态呼叫 ​

正常的Fantom反射和动态引用与Java类同时存在. Fantom 运行时间将正确映射反射调用和动态调用对超载方法. 因为一个Java类可以同时拥有一个同名的字段和方法, Type.field 和 Type.method 可能为同一名称返回不同的结果。 如果试图在同时具有该名称的字段和方法的槽上使用动态引用,则该方法总是隐藏字段。

作为接口的函数 ​

在爪哇语中,内部班级常被用来代替关闭. Java FFI 允许您使用一个函数,其中需要一个方法的接口。 例如接口 Runnable 定义茁壮成长的一种方法 run,这样我们就可以随时使用 Fantom 函数 Runnable 预期:

// Java API
void execute(Runnable r)

// Fantom code
execute |->| { echo("run!") }

Java和Fantom类型Addolution之间强制的标准规则适用于该函数如何执行接口的方法.

说明 ​

Java 注释被导入到 Fantom 类型系统中作为特殊例 facets. Fantom 类和槽可能使用标准面语法加注Java注释:

// Java annotations
public @interface AnnoA {}
public @interface AnnoB { String value(); }
public @interface AnnoC { int i();  String s(); }

// Fantom syntax
@AnnoA
@AnnoB { value = "foo" }
@AnnoC { i = 34; s = "bar" }
class Fantom {}

假设注解有运行时间保留策略,在Fantom类型或插槽中添加的说明可供Java反射使用. 然而,注释将****不**在Fantom类型系统中作为平面反映.

目前支持下列注释元素类型:

Java                     Fantom
----                     -------
boolean                  sys::Bool literal
byte, short, int, long   sys::Int literal
float, double            sys::Float literal
String                   sys::Str literal
Class                    sys::Type literal
enum                     Java FFI enum field access
arrays of above          sys::List literal of above

Java 脚本 ​

概览 ​

Fantom为编译到JavaScript并在JavaScript VMs中运行提供支持,如网页浏览器和Node.js. 大部分sys API是可用的,但由于JavaScript VM环境的限制,并非所有的pod和API都可以访问.

Js 面额 ​

您必须使用 JavaScript 输入 Js 侧面 :

@Js
class GonnaBeJs
{
  Void sayHi() { Win.cur.alert("Hello!") }
}

部署 ​

JavaScript编译器指导工作,在编译时将Fantom源代码直接翻译为JavaScript源代码. 这与JVM/CLR不同。 work 在运行时使用中间格式并翻译到目标平台。

标注为 @Js 将编译为 JavaScript 源文件。 该文件还将包括运行时所需的反射信息和其他元数据. 编译器以ES6和CommonJS模块格式生成符合现代ECMAScript的JavaScript. 文件在 js/ pod目录为 js/<podname>.mjs 和 js/<podname.js 页:1 该目录中还包含一个与两个模块格式兼容的源图。

编撰的JavaScript文件按照解析的顺序解释,每个 pock JS文件必须按正确的顺序编写. 为了简化这一过程, FilePack API提供生成文件列表的便利,保证正确的依赖命令:

// expands to: sys.js, concurrent.js, graphics.js, web.js, dom.js
files := FilePack.toAppJsFiles([Pod.find("dom")])

浏览器运行时 ​

网页浏览器是Fantom JS的首要目标,因此API大多专注于简化如何对Fantom基于网络的应用软件进行窃听.

使用 FilePack 是将您的依赖性捆绑起来并服务于浏览器的简单方法 :

files := FilePack.toAppJsFiles(pods)
pack  := FilePack(files)

...

override Void onGet()
{
  switch (req.modRel.path.first)
  {
    case "myApp.js": pack.onGet
    ...
  }
}

...

out.head
  .initJs(["main":"myApp::Main"])
  .includeJs(`/myApp.js`)
  .headEnd

完整的示例可见于 js-hello.

环境初始化 ​

Fantom JS运行时间可以使用自定义配置初始化 。 Env.vars API. (英语). 默认情况下不需要明确初始化。 简单的解析源代码将生成一个可用的有效运行时间, 时间为 Win.onLoad:

out.head.includeJs(`/myApp.js`).headEnd

要 文件自定义默认行为, 请使用 WebOutStream.initJs 以初始化想要的 Env.vars这必须发生在联署材料分析之前:

out.head
  .initJs(["main":"myApp::Main", "timezone":"Denver"])
  .includeJs(`/myApp.js`)
  .headEnd

这种方法主要用于指定“ main” 方法, 用于在负载时间拖曳您的应用程序 。 见 WebOutStream.initJs 支持完整列表 Env.vars.

见 js-env 例如,用于设置时区和地方的代码。

其它运行时间 ​

您可以通过将 pock 的 JavaScript 源文件装入任何 JavaScript VM 来运行 Fantom 编译的 JavaScript 。 没有特别的要求。 上述各节的大部分信息应适用于其他JsVMs.

娇娇 ​

Fantom包括对在Node.js运行代码的特殊支持. 将若干工具组合在一起 nodeJs 通过运行可访问的 pock

$ fan nodeJs -help

您可以使用节点运行 Fan 脚本 fan nodeJs run 命令。 你想在Node.js上的任何课都必须有 @Js 平面和主切入点必须位于一个叫做 Main.

" "" """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" fanc 帮你的Fantom代码插一个国家预防机制模块。 这个工具是独一无二的,因为它会生成 JavaScript 用于 * 所有* 类型;而不只是那些拥有该类型 @Js 脸部。 这使得您可以访问更多在Node.js的Fantom API. 见 fanc 关于该工具的更多细节

从 JavaScript 引用 Fantom ​

Fantom类型是普通的JavaScript对象,因此可以从JavaScript代码在本地引用. 在浏览器中,类型格式如下(注-这是用于将类型范围化到全局命名空间的通用Js模式):

fan.<myPod>.<myType>.<method>

fan.myPod.MyType.staticMain();           // invoke a static method
fan.myPod.MyType.make().instanceMain();  // invoke method from instance
  1. 使用 HTML 事件处理器的例子 :
<button onclick="fan.myPod.MyType.doSomething();">Click Me</button>

守则公约 ​

如果您葵花是本地倡仪, 它必须遵循以下惯例与 ES 编译器合作 。 强烈建议花一些时间查看 JavaScript 源代码 。 sys 和 dom 了解如何执行和遵守这些公约。

类 ​

所有 Fantom 类型作为 ES6 类执行

** 外观**

class Foo { }

莽撞

class Foo extends sys.Obj {
  constructor() { super(); }
}

注意,在所有pod(不包括sys)中,编译器会自动生成 import 使用此模式( 或类似模式) 处理所有 pock 依赖关系的语句 require 用于普通js)

import * as <podName> from './<podName>.js';

然后可以参考代码中另一个zove的类型: <podName>.<Type>。在上述例子中通知: Foo 类扩展 sys.Obj.

字段 ​

所有 Fantom 字段都是在 JavaScript 中作为私有生成的,编译器将基于 Fantom 中的 getter/setter 访问标记生成一个获得/设置字段的单一方法. 生成的计数器/计数器将符合 Naming 规则概述于后.

注意这个设计的一个含义是,Fantom中的所有字段只能作为方法在JavaScript中访问. 您从未直接访问 JavaScript 字段的存储 。

** 外观**

class Foo
{
  Int a := 0
  Int b := 1 { private set }
  private Int c := 2
}

** 贾瓦脚本**

class Foo extends sys.Obj {
  constructor() {
    super();
    this.#a = 0;
    this.#b = 1;
    this.#c = 2;
  }

  #a = 0;

  // has public getter/setter
  a(it=undefined) {
    if (it===undefined) return this.#a;
    else this.#a = it;
  }

  #b = 0;

  // has only public getter
  b() { return this.#b; }

  #c = 0;
  // no method generated for #c since it has private getter/setter
}

let f = new Foo();
f.a(100);
console.log(`The value of a is now ${f.a()});

提出的关于Enum的踊跃 ​

这种场面设计对Enums有一定具体的影响. 所有静态的enum字段也是作为方法产生的. 以 " . src/sys/es/Weekday.js一般来说,你不需要关注执行的细节,但任何想与Enums合作的本地法典都需要理解这些公约.

const monday = sys.Weekday.mon();

Funcs和关闭 ​

所有期望关闭或Func的Fantom代码将生成期望本地JavaScript关闭. 例如, sys::List.each 方法定义在 List.fan as

Void each |V item, Int index| c)

已执行 List.js as

each(f) {
  for (let i=0; i<this.#size; ++i)
    f(this.#values[i], i)
}

注意到 f 参数被假定为本地JavaScript函数,直接被引用.

命名 ​

从Fantom到JavaScript的所有类名都保留下来.

与 JS 内置关键词相冲突的插图和参数名被"挑剔"以结束 $。被腌取的名字列表可见于 src/compilerEs/fan/ast/JsNode.fan.

# Fantom
Void name(Str var) { ... }

# JavaScript
name$(var$) { ... }

一般来说,结束于 $ 在节点或浏览器等环境中本土使用JS代码时,应当视为"公用"API. 有几种用于编译器支持的“内部”方法和字段,它们将被前缀为两个下栏 __它们不应被生成的JS代码的消费者使用,而且可随时在没有任何通知的情况下加以修改。

# JavaScript - these should all be considered internal
static __registry = {};

__at(a,b,c) { ... }

土著人 ​

要编译 JavaScript 本地文件, 请使用 jsDirs 字段。 见 Build Pod 举个例子

目前Fantom编译器支持以遗留js格式和新的ES格式生成JavaScript. ES 编译器将通过浏览每个路径的第一部分来寻找本地人 jsDir to es/ 并在那里寻找执行。 比如说

# build.fan
jsDirs = [`js/`, `js/foo/`] // legacy location

# The ES compiler will look for natives in
[`es/`, `es/foo/`]

JavaScript代码必须遵循上面讨论的编译器常规.

测试 ​

Fantom 包括内置支持 fant 在 JavaScript VM 中运行单元测试 -es 旗帜 :

$ fant -es myPod

请注意,这是方便打电话 fan nodeJs test myPod为了进行联署测试,你需要 Node.js 已安装。


文学 ​

概览 ​

下列类型有一个字法:

三类 Bool, Int,以及 Float 已经 value-types。这些类型不一定作为对象引用而通过,而是在调用堆栈上通过值。 当值类型被强制用于/从参考类型中 Obj 和 Num,编译器将生成拳击/解框操作。

布尔 ​

有两个价值: Bool 代表使用 true 和 false 关键词

作为值类型 Bool 字段默认为 false 改为 null不过, Bool? 默认为无效。

输入 ​

Int 用于表示64位签名整数。 Fantom对于较小的精度没有任何整数类型. Fantom还使用Int来代表一个字符串的单个字符作为Unicode代码点(它碰巧是方便的,因为实际上有超过2^16 Unicode字符). 内 const 这意味着所有情况都是不可改变的。

内字用小数位数的字符串表示。 如果使用前缀, Int 也可以用十六进制表示 0x 目录 0b. 八角形标记不支持。 你可以用这个 _ 内在任何地方 Int 以字元作为分隔符,使您的代码更容易读取。

Fantom 也允许 C 样式字符文字代表 Unicode 代码点为 an Int 字义. 字符字形与勾选环绕,支持以下逃逸: Character s s s s

\b  \f  \n  \r  \t  \"  \'  \` \$  \\  \uXXXX \u{XX}

逃脱 \uXXXX 洙. 使用四位数十六进制数字指定Unicode代码点;或 \u{xxxxx} 以小于或小于四个数字的代码点逃避。

Int 实例:

45
-89_039
0xcafebabe
0xCAFE_BABE
0b1011
'?'
'\n'
'\u03ab'
'\u00F2'
'\u{1F60A}'

作为值类型 Int 字段默认为 0 改为 null不过, Int? 默认为无效。

浮点 ​

Float 用于表示一个64位浮点数。 Fantom 没有32位浮标的类型. 浮点是 const 也就是说,情况是不可改变的。

浮点字像C,Java等使用小数位数的字符串,可选点和分数,以及可选代词来表示. Floats上需要使用"f"或"F"后缀来区分Decimals. 你可以用这个 _ 在栏下作为一个分隔符。 实例 Float 诘问:

3.0f
3f
3.0F
123_456.0f
3e6f
0.2e+6f
1_2.3_7e-5_6f

作为值类型 Float 字段默认为 0.0f 改为 null不过, Float? 默认为无效。

小数 ​

Decimal 用于表示十进制浮点,比浮点更精确。 十进制对于Floats可能发生四舍五入错误的财务应用来说是理想的. 十进制由 BigDecimal 在爪哇语和 System.Decimal 输入.NET。 在JavaScript中不支持它们.

小数字的表达方式与浮点字一样,只是使用"d"或"D"后缀. 实例 Decimal 诘问:

4d
4.0D
123_456d
3e6d
0.2e+6D
1_2.3_7e-5_6d

注:小数点在Java和.NET平台之间运行不完全相同. Java使用具有无限范围的BigDecimal,而.NET使用System. 十进制,范围为28个重要数字,范围为1E-28至7.9E+28. 两个平台在平等方面也存在差异:

3.00d == 3.0d   =>  false on Java
3.00d == 3.0d   =>  true on .NET
3.00d <=> 3.0d  =>  zero on both platforms

J宵认为零对平等很重要, 然而,这两个平台都为贸发会议提供了一致的成果。 Obj.compare 方法。

字符串 ​

Str 用于表示Unicode字符的序列。 斯特尔是 const 这意味着斯特尔的所有实例都是不可改变的. 使用 StrBuf 当您需要一个可变字符序列时。

Str 字节由 " 双引文字符. 特殊字符可以使用上面指定的逃跑序列列表来逃避 Int 字符字节。 一对夫妇 Str 实例:

"hello"
"line 1\nline 2"
"It is 73\u00B0 Fahrenheit outside!"
"\u{1F60A} is happy face emoji"

多行字符串 ​

字符串字元可能跨越多个线条,在这种情况下,新线条总是正常化到 \n 无论源代码文本中新行是如何编码的。 每行的第一个非白空字符必须和开口引用的右侧对齐,否则就是编译时间错误:

x :=
  "line 1
    line 2
   line3"

上述实例汇编为 "line1\n line2\nline3"。请注意,引号右侧的间隔保持不变,但左侧的间隔在字符串文字中被剥去。 如果使用制表符,则必须使用匹配数的领先制表符,然后是空格字符:

\t\tx := "line 1
\t\t      line 2"

Str 内插 ​

Str 字节支持字符串插值,允许任意的 Fantom 表达式嵌入字符串字节中. 嵌入式表达式使用前缀 $ 美元标志和周围 { 和 } 护身符 如果表达式是一个简单的标识符或点标识符的序列,则可以省略括号. 使用 \$ 如果你想表达美元符号的话,请按逃生顺序

内插字符串是运行时计算新字符串的表达式——它们只是用于字符串连接的语法糖. 例如:

"x is $x, in hex $x.toHex, and x+8 is ${x+8}"

语法糖用于:

"x is " + x + ", in hex " + x.toHex + ", and x+8 is " + (x+8)

字符串插值使字符串格式化更容易读写. 方 我有"Fantom编码惯例",就是总是使用字符串内插法,而不是收缩法.

语言 ​

Str 插值支持一个特殊的语法来方便本地化字符串的工作:

// qualified pod::key
"$<pod::key>"  =>  Pod.find("pod").locale("key")

// lookup key within current pod
"$<key>" =>  EnclosingType#.pod.locale("key")

// lookup key and automatically add key to `locale/en.props`
"$<key=Text>"  =>  EnclosingType#.pod.locale("key", "Text")

参见 Localization 供深入讨论。

三重引号 ​

Fantom 还支持 """ 三重引用的字符串文字 。 这些作品完全像普通的字符串文字,除了你不需要逃避双引号 " 字符键。 内插和多线工作完全相同:

echo("""Do you know "What lies beneath the shadow of the statue"?""")

字符串 DSL ​

您也可以使用 DSL syntax。一个 Str DSL 可以包含除序列"QQ"之外的任何字符. ""或"$"两个字符都没有特别处理:

echo(Str <|no \ or $ escapes need, and
           multi-line works too|>)

Str DSL 字节可能是遵循标准字符串的主要白空间规则的多行.

会期 ​

实际上,在Java中,一个需要测量时间的API通常使用与毫秒数的长. 这往往有点含糊不清, 当你需要精细精细的精确度时就会变得有问题。 Fantom APIs总是使用打字值的时间. 使用 DateTime 和相对时间的衡量方法如下: Duration - 两者都使用纳秒精度实现正常化。 例如,要代表5秒,您可以使用 Duration.make 构造器 :

Duration.make(5_000_000_000)  // longhand
Duration(5_000_000_000)       // shorthand

但所有这些零点都使其不易操作. 而且,它有点效率低下,因为它需要创造一个新的实例 Duration 每次表达式执行时. 在方顿 Durations a. 以下列一个字表示:

ns:  nanoseconds  (x 1)
ms:  milliseconds (x 1,000,000)
sec: seconds      (x 1,000,000,000)
min: minutes      (x 60,000,000,000)
hr:  hours        (x 3,600,000,000,000)
day: days         (x 86,400,000,000,000)

实例 Duration 诘问:

4ns
100ms
-0.5hr

乌里 ​

这个 Uri class猕猴桃用于代表一个统一资源识别符,它是Fantom子系统命名和分辨率的基础. Uris 使用后面的勾选语法 :

`index.html`
`/some/path/file.txt`
`https://fantom.org/`
`TPS Report.doc`

请注意,当与Fantom的URI合作并作为文字代表时,我们总是使用标准形式. 例如,一个空间使用普通空间来表示,而不是编码为 “% 20 ” :

`TPS Report.doc`.toStr      // yields "TPS Report.doc"
`TPS Report.doc`.encode     // yields "TPS%20Report.doc"
`TPS%20Report.doc`.toStr    // yields "TPS%20Report.doc" (probably not what you want)
`TPS%20Report.doc`.encode   // yields "TPS%2520Report.doc" (probably not what you want)

和弦一样,你可以嵌入标准 escape sequences 输入包含 Unicode 代码点的 Uri 文字。 Unicode 字符是UTF-8编码成octects,在URI按照RFC 3986编码成百分比之前(参见 Uri.encode).

Uris 支持遵循与 Str interpolation:

file := "file.txt"
`/dir/$file`  =>  ("/dir/" + file).toUri

类型 ​

这个 Type 类是 Fantom 反射 APIs 的基础. 通常情况下 Type 使用 Type.of 方法 。 但你也可以代表一个 Type 实例,使用字面语法,该语法只是后面的一类名称。 # 符号 :

Str#
acme::SomeType#

如果没有指定一个完全合格的类型名称,那么类型名称将根据源文件解决 using 发言。

槽 ​

您可以使用语法创建槽字:

Int#plus
#echo

如果类型名称被省略,则槽字句将针对附件类解决。 插槽字面决定为 Field or Method。槽字词与反射的语义相同。 Type.slot 但编译器可以静态检查。

范围 ​

A Range 表示一个从开始到结束的连续整数范围。 范围在 Fantom 源代码中可能以文字表示, 如 start..end 实现包容性目标或 start..<end 独家范围。 包容性和排他性决定最终指数是否包含在范围(起始总是包容性的). 实例 Range 诘问:

0..5    // 0 to 5 (end is inclusive)
0..<5   // 0 to 4 (end is exclusive)
x..<y   // x to y-1 (end is exclusive)
  1. 注意到: .. 和 ..< 运算符可能用于任意表达 operator precedence。这些运算符只是用于构建范围(通过)的语法糖。 Range.make.

列表 ​

这个 List 类存储命令对象列表。 列表可能使用下列文字语法进行即时化:

// syntax format where V is the optional item type, and
// the items are arbitrary expressions:
V[item0, item1, ... itemN]

// examples
Int[10, 20, 30]     // list of the three Ints 10, 20, and 30
[10, 20, 30]        // same as above using type inference
Int[,]              // empty list of Ints
[,]                 // empty list of Obj?

在大多数简单的情况下a List literal只是方括号内逗号分离表达式的列表. 如果省略了类型前缀,则使用类型推论来确定项目的类型. 物品的类型通过计算所有物品共享的最具体类别(混合类型不考虑)来确定. 例如:

[1, 2, 3]        // evaluates to Int[]
[1, null, 3]     // evaluates to Int?[]
[1f, 2f, 3f]     // evaluates to Float[]
[1, 2f, 3]       // evaluates to Num[]
[1, "2", 3]      // evaluates to Obj[]
Num[1, 2, 3]     // evaluates to Num[]
[[10,20], [30]]  // evaluates to Int[][]

在涉及 [1,2f,3] 列表包含 Ints 和 Floats 份额 Num 作为他们最具体的共同基础类. 不过名单 [1,"2",3] 包含 Ints 和 Strs 相同基类的 Obj列表 Num[1,2,3] 评估到 Int[] 如果使用了类型的推论,但如果我们可以 Floats ,然后我们需要明确指定类型。

编译器往往会推断一个列表具有不可忽略的类型. 如果列表可能存储无效值,则需要明确输入:

[1,2,3]       // cannot store null
Int?[1,2,3]   // can store null

空列表使用特殊语法表示 [,]。通常您会指定一个类型 - 例如 Str[,] an an strings 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 或 Obj?[,].

如果一个没有明确类型的列表文字被用作字段初始化器,那么它从字段所声明的类型推断出其类型:

Str[] names := [,]     // initial value inferred to be Str[,]
Num[] nums  := Int[,]  // initial value is Int[,]

见 Appendix 用于清单类型推断的正式规则。

地图 ​

这个 Map 类使用散列表存储一组密钥/值对。 地图可能使用以下在全国范围内的语法进行即时化:

// syntax format where K:V is the optional map type,
// and the keys and values are arbitrary expressions:
[V:K][key0:value0, key1:value1, ... keyN:valueN]

// examples
[Int:Str][1:"one", 2:"two"]  // map of Strs keyed by Int
Int:Str[1:"one", 2:"two"]    // same as above with shorthand type syntax
[1:"one", 2:"two"]           // same as above using type inference
Int:Str[:]                   // empty Int:Str map
[:]                          // empty map of Obj:Obj?

这个 Map 语法就像 List 除非我们用冒号指定密钥值对。 地图文字的前缀类型是有效的 map signature。如果省略了类型前缀,则使用类型推论来确定密钥的类型和值,使用与列表文字相同的规则。 例如:

[1:"one", 2:"two"]    // evaluates to Int:Str
[1:"one", 2:null]     // evaluates to Int:Str?
[1:"one", 2f:"two"]   // evaluates to Num:Str
[1:"one", 2f:null]    // evaluates to Num:Str?
[1:"one", 2f:0xabcd]  // evaluates to Num:Obj
[0:["one"]]           // evaluates to Int:Str[]

空地图使用特殊的语法表示 [:] 带有或没有类型前缀。

注意地图可能不会使用无效的密钥键输入 。 如果您使用类型推论, 您可能需要明确输入将存储无效的地图 :

[1:"one", 2:"two"]           // cannot store null values
Int:Str?[1:"one", 2:"two"]   // can store null values

类型 Int:Str? 是一张带有 Int 键和 Str? 数值。 然而类型 [Int:Str]? 是地图 Int:Str 地图变量本身可能无效。

如果一个没有明确类型的地图文字被用作字段初始化器,那么它从字段宣布的类型推断出其类型:

Str:File[] files := [:]   // initial value inferred as Str:File[:]

见 Appendix 用于地图类型推论的正式规则。


本地化 ​

语言 ​

这个 Locale Class是Fantom本地化基础设施的 API 中心。 地语通过ISO 639两个字母代码来代表一种语言. 某地块还可以通过ISO 3166两个字母代码选择代表特定国家或区域. 常见的地方包括:

Language Only:
  en     English
  es     Spanish
  fr     French
  de     German
  it     Italian
  ja     Japanese
  ko     Korean
  zh     Chinese
Language and Country:
  en-US  United States
  en-GB  United Kingdom
  es-ES  Spain
  es-MX  Mexico
  fr-FR  France
  de-DE  Germany
  en-CA  Canada (English)
  fr-CA  Canada (French)
  zh-CN  China (Simplified)
  zh-TW  Taiwan

当 Fantom VM 启动时, 它会创建基于基础平台的默认语言 。 例如,在 Java VM 上,默认的 Fantom 语言将初始化 java.util.Locale (又从操作系统的默认本地语中初始化).

区域被配置为演员本地变量. 使用 Locale.cur 和 Locale.setCur 以获取和设置当前线程的语境。

公约 Locale 从未作为参数传递到 Fantom APIs。 相反,现在的演员暗含着这个地方。 一般来说,对当地情况敏感的API将包括这个词 locale 在他们的方法名称。

使用 Locale.use 使用不同的区域执行代码集合:

echo(Locale.cur)
Locale("zh-CN").use {  echo(Locale.cur) }
echo(Locale.cur)

本地化属性 ​

所有显示给用户的字符串一般应该拉出到 * 本地化的道具文件 * 中,以便方便语言翻译. 每个pod可以定义一个本地化的属性映射,需要多少密钥. 本地化属性定义于 props files 作为 pock 资源文件 locale 戮录.戮量 :

locale/en.props
locale/en-US.props
locale/en-CA.props
locale/fr.props
locale/fr-CA.props

这个 Env.locale API用于查找本地化属性. 通常你会使用 Pod.locale 方便方法 :

Button(pod.locale("ok"))
Button(pod.locale("cancel"))

查找本地化权授予当前 Env但标准分辨率规则是:

  1. Env.props(pod, `语言/{地方}.props`)
  2. Env.props(pod, `语言/{lang}.props`)
  3. Env.props(pod, `语言/指针`)
  4. 退回到 pod::key 除非 def 指定

Env.props 首先在 pocket 等目录中查看覆盖,然后检查 pock 是否包含资源文件 。

最佳做法是确保将所有属性映射到 en.props 文件为ồ的默认值。 然后在ồn等语言文件中存储本地化翻译 fr.props, de.props等 我真该死 通常 我真希望您只使用特定国家的文件, 如 en-US or en-GB 就区域而言。

语言 ​

字符串插值支持一种特殊模式,用于方便本地化道具的工作. 支持下列插值格式:

  • $<key> 不合格的密钥
  • $<pod::key> 选中密钥
  • $<key=def> 带有默认值的不合格密钥

最简单的语言文字是一个不合格的密钥,它只是一个快捷键 Pod.locale 使用附件类型 :

// original code
class Foo { Void main() { echo("$<bar.baz>") } }

// translated into
class Foo { Void main() { echo(#Foo.pod.locale("bar.baz")) } }

也可以使用一个合格的密钥在外部pod中查找本地化的道具:

// original code
"$<fwt::cancel.name>"

// translated into
Pod.find("fwt").locale("cancel.name")

最后一个格式允许您在 Fantom 源代码中定义键值 :

// orginal code
"$<fileNotFound=File not found>: $file"

// translates into
EnclosingType#.pod.locale("fileNotFound", "File not found") + ": $file"

// and automatically adds the key/value pair to locale/en.props
fileNotFound=File not found

如果您的pod没有明确的"locale/en.props"资源,则自动创建. 如果它确实存在,那么插入的密钥/值会自动合并到现有的道具文件中. 在多个位置声明一个密钥的值是编译时间错误;每个密钥必须在 en.props 或插字符串中精确定义一次 。


日志 ​

日志创建 ​

这个 Log 类规范如何将日志语句嵌入 Fantom 应用程序。 每个 Log 实例在 VM 中有一个独特的名称,根据惯例,这个名称总是从pod名称开始,并使用点分隔器。 一旦为指定名称创建了Log实例,该实例在VM的一生中仍与该名称相关。 日志是所有线程共享的连续、不可改变的实例。

各种各样,你应该用 Pod.log 以获取您的主机标准日志 :

Pod.of(this).log.err("...")

" "" """"""""""""""""""""""""""""""""""""""""""""""""""""""""""" """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" Log.get 它将在第一个呼叫时创建日志,并在随后的呼叫中查找日志。

// get or create a log named "acme"
const static Log log = Log.get("acme")

// find an existing log
Log.find("acme")          // throw exception if not found
Log.find("acme", false)   // return null if not found

// list all the active logs
Log.list

日志语句 ​

以下方法用于生成日志记录:

  • Log.err事情发生了
  • Log.warn事情发生了,可能很糟
  • Log.info: 发生了一些有趣的事情
  • Log.debug邱奇: 发生了一些事情, 只有在你碰巧调试时才有趣

所有伐木方法都采用 Str 和可选 Err。一些简单的例子:

log.err("The freaking file didn't load", err)
log.info("CatchRoadRoader service started on port $port")

在写入调试日志语句时,我们预计它们大部分时间都会被关闭. 因此要意识到弦调和的隐藏成本. 你可以用这个 isDebug 跳过创建日志消息的方法 :

// this code performs string concatenation on every call
log.debug("The values are x=$x, y=$y, and z=$z")

// this code performs string concatenation only when needed
if (log.isDebug)
  log.debug("The values are x=$x, y=$y, and z=$z")

日志级别 ​

每个日志被配置为在给定时间或以上记录事件 LogLevel。这些水平从最低到最严重的:

  • ** 调试**: 记录一切
  • ** info**: 记录除调试以外的一切
  • ** 警告**:记录除调试之外的一切信息
  • err:只记录错误
  • 沉默:无记录

所有日志默认为关卡 info (见 setup 以更改默认级别)。

您可以通过 level 字段。 一些代码示例 :

log.level = LogLevel.warn
log.level < LogLevel.err    // returns true
log.level < LogLevel.info     // returns false

日志处理器 ​

日志处理器是用于处理的函数 LogRecs。以下内容 Log 使用方法管理 VM 中的处理器:

  • Log.handlers: 列出已安装的处理器功能
  • Log.addHandler: 安装处理器函数
  • Log.removeHandler: 未安装处理器函数

帮手一定是一个例子 immutable Func (它们被所有线程共享). 启动时,总是安装一个处理器,通过控制台打印每个记录。 LogRec.print 方法。

以下是安装处理器的简单例子:

Log.addHandler |rec| { echo("My Handler: $rec") }

日志设置 ​

默认情况下,所有日志级别都将默认为 info。您可以通过 Log.level。也可以使用“etc/sys/log.props”文件为任何日志设置默认关卡。 "log.props"文件是标准道具文件,其中日志名称为键,值为字符串 LogLevel:

web=debug
acmeWombat.requests=silent
acmeWombat.responses=warn

方法 ​

概览 ​

一种方法是一个槽,它定义了类或混合体内的函数:

class Boo
{
  static Int add(Int a, Int b) { return a + b }
  Int incr() { return count++ }
  Int count := 0
}

在上述例子中 add 和 incr 是类中的方法槽 Boo。 你觉得 incr 方法是一种实例方法,它意味着在下列实例中总是被引用: Boo。 你觉得 add 方法是静态的,不能在实例中引用:

b := Boo()
x := b.incr()
y := Boo.add(3, 4)

援引方法采用 . 目标上的点操作员。 例如,目标方法就是类型的例子;对于静态方法,目标就是类型名称。

自行类型(或您继承的类型)中的方法会自动设定范围,从而暗示目标类型或实例。 例如:

class Foo : Boo
{
  Int more() { return incr() + add(3, 4) }
}

如果这个方法不取任何参数,那么我们可以离开 () 空括号。 按惯例,空括号为总是省略:

b.incr  // same as b.incr()

你也可以说 ?. 操作员安全处理无效目标。 见 safe invokes.

这个 ​

实例方法总是有一个隐含的第一参数,即通过关键词识别的实例本身 this. 定义 a 和 b 以下例子相同:

class Foo : Boo
{
  Int a() { return incr }
  Int b() { return this.incr }
}

构造器 ​

构造器是用来创建新类实例的特殊方法. 在Fantom中,构造器被命名为方法. 不同的是,他们使用 new 其定义中的关键词而不是返回类型(返回类型被暗示为类型的实例).

class MissingPerson
{
  new make(Str name) { this.name = name }
  Str name
}

根据惯例,主要建筑商应称为 make 并且其他构造器应使用 make。与其他槽一样,构造器必须在其类型中独有命名。 要创建实例,您将构造器称为静态方法:

jack := MissingPerson.make("Jack Shephard")

您也可以使用这个 shorthand syntax:

sayid := MissingPerson("Sayid Jarrah")

Fantom 既支持 * 内置构造器 * 也支持 * 静态构造器 *. 从客户的角度来说,实例和静态构造器都看起来就像被命名的工厂方法. 实证演算法有隐式的实证演算法,因此各种各样实证演算法的实证演算法都与其他有隐式的实证演算法一样。 this 举个例子。 另一方面,静态构造器是正常的静态方法,执行对象分配(使用实例构造器)是你的应答。

class Number
{
  new make(Int val) { this.val = val }
  static new fromStr(Str s) { make(s.toInt) }
}

在以上的例子中,我们有一个实例建筑师命名 make 和一个名为 fromStr。注意到实例构建者有隐含的 this 参数 - 调用时自动执行分配。 不过,自从 fromStr 它负责进行分配(在这种情况下,将权力下放给 make) (中文(简体) ). 说到建筑师 他们都一样工作:

x1 := Number.make(1)
x2 := Number(2)
x3 := Number.fromStr("3")
x4 := Number("4")

静态构造器的返回类型总是定义类的无效版本. 所以在以上的例子中 Number.fromStr 具有隐含的返回类型 Number?。一个实例构造器的返回类型是 Void,但当客户端调用时,会评估为定义类型。

只有班级才能有实例构建器. 在混音符上声明实例构建器是一个编译时间错误 。 然而,混凝土被允许宣布静态构造器.

自动生成构造器 ​

如果您在类中不声明任何实例构造器, 那么编译器将自动生成一个没有调用参数构造器的公众 make.

建筑电话 ​

蓉(Fantom)支持一种特殊的语法,称为“构造调用 ” 。 Type(args)。与运算符一样,这些调用按参数类型支持超载。 标记为 new 关键词可用构建器调用。

《公约》总是倾向于要求建筑,而不是使用 make 明确:

ArgErr.make   // non-preferred
ArgErr()      // preferred

ArgErr.make("bad arg")   // non-preferred
ArgErr("bad arg")        // preferred

如果编译器无法确定从参数中调用哪个构造器,它会报告一个“大构造器”错误。 在此情况下, 您需要解释使用您的建筑工名 。

构造链 ​

在创建子类时, 您必须使用名为“ 建构链 *” 的语法来调用您的父类实例构建器或您自己的另一个构建器 。 调用父建构器的语法基于 C++ 和 C# 使用 : 在正式参数之后,但在方法体之前:

class Foo
{
  new make() {}
  new makeName(Str name) {}
}

class Bar : Foo
{
  new make() : super() {}
  new makeFullName(Str? first, Str last) : super.makeName(last) {}
  new makeLastName(Str last) : this.makeFullName(null, last) {}
}

所有构造器链从 this or super 关键词 使用 this 链到您自己的一个构造器或 super 以呼叫父建构器。 然后,调用构建器被指定为带有名称和参数列表的普通方法调用. 作为快捷键,如果被调用的主建构器名称相同,可以省略名称.

在上述例子中, Bar.make 说明呼叫 Foo.make- 略去名字意味着叫一个同名父母 make 在这种情况下。 Bar.makeFullName 说明一个超级阶级建筑师的名字。 Bar.makeLastName 显示如何在自己的类上调用对等构建器 - 这对于确保您的初始化代码集中在一个构造器中是有用的 。

蝎子启动器 ​

静态初始剂是在类初始化期间执行的特殊方法. 通常用来初始化 static fields。它们使用 Java 像语法:

class Foo
{
  static { echo("initializing Foo...") }
}

静态字段的指派使用自动生成的静态初始化器. 允许使用多种静态初始剂,在这种情况下,它们按声明顺序运行:

class Foo
{
  static const Int a := 10
  static { echo("1st a=$a b=$b") }
  static const Int b := 20
  static { echo("2nd a=$a b=$b") }
  static { a = 30 }
  static { echo("3rd a=$a b=$b") }
}

// outputs
1st a=10 b=null
2nd a=10 b=20
3rd a=30 b=20

默认参数 ​

您可以指定参数的默认参数。 默认可以鳌到最后一个零或多个参数(右到的主要目标左). 例如:

static Int add(Int a, Int b, Int c := 0, Int d := 0)
{
  return a + b + c + d
}

在这个例子中,最后两个参数 c 和 d 默认为零。 这让你可以打电话 add 方法,有2、3或4个参数:

add(3, 4, 5, 6)
add(3, 4, 5)     // same as add(3, 4, 5, 0)
add(3, 4)        // same as add(3, 4, 0, 0)

运算符 ​

Fantom支持操作员使用操作器方法超载. 操作员方法只是普通方法,与 @Operator 标记面。 在确定使用该方法的操作者时,执行下列命名公约:

prefix     symbol    degree
------     ------    ------
negate     -a        unary
increment  ++a       unary
decrement  --a       unary
plus       a + b     binary
minus      a - b     binary
mult       a * b     binary
div        a / b     binary
mod        a % b     binary
get        a[b]      binary
set        a[b] = c  ternary
add        a { b, }

对于无序操作符和三角操作符,方法名称必须精确匹配. 对于二进制运算符,方法只能从给定的名称开始. 这使得二进制运算符被参数类型所超载:

class Foo
{
  @Operator Int plusInt(Int x) { ... }
  @Operator Float plusFloat(Float x) { ... }
}

Foo + Int    =>  calls Foo.plusInt and yields Int
Foo + Float  =>  calls Foo.plusFloat and yields Float

编译器使用非常简单的算法执行运算符的方法解析. 如果有多种可能的匹配, 编译器会报告一个错误, 显示操作员解决得模棱两可 。 编译器确实不考虑阶级等级,试图找到"最佳"匹配.

虚拟方法 ​

虚拟方法被一个子类所取代,以促成多态化. 方法必须使用 virtual 关键词才能被子类所压倒. 子类必须声明它们压倒了使用 override 关键字 :

class Animal
{
  virtual Void talk() { echo("generic") }
}

class Cat : Animal
{
  override Void talk() { echo("meow") }
}

Animal().talk   // prints generic
Cat().talk      // prints meow

默认当子类凌驾under一个方法时,它被暗示为虚拟——它自己的子类可以再次凌驾它. 你可以用来 final 防止进一步覆盖的关键词 :

class Lion : Cat
{
  override final Void talk() { echo("roar!") }
}

摘要方法 ​

抽象方法是指没有执行的虚拟方法. 使用 abstract 关键词 抽象方法被暗示是虚拟的 - 使用这两种方法都是错误的 abstract 和 virtual 关键词 抽象方法不得提供方法体. 如果在其中宣布 class,那么包含的类也必须是 abstract.

一次方法 ​

这个 once 关键词可以用于声明一次方法. 一次方法只在第一次调用时计算结果,然后在后续调用时返回缓存值。 一旦方法是一种伟大的技术 懒惰地创建状态 没有很多锅炉板码:

// hard way
Str fullName
{
  get
  {
    if (&fullName == null) &fullName = "$firstName  $lastName"
    return &fullName
  }
}

// easy way
once Str fullName() { return "$firstName  $lastName" }

限制一次性方法:

  • 不得在混凝土内申报 不是建筑师
  • 一定不是静态向其
  • 一定不是抽象的
  • 必须返回非虚拟
  • 一定没有参数

如果一次方法抛出一个例外,那么没有缓存值 - 随后的调用会重新执行该方法直到返回一个值.

一次性方法可用于带有警告的类别。 在JVM中,缓存字段被编译为波动字段. 然而,无法保证该方法在多个线程上完全称为一次. 因此计算必须是一个纯函数,总是返回相同的值. 此外,无法保证所有线条都看到完全相同的事件。

共性 ​

方图支持 covariance - 允许覆盖方法缩小继承方法的返回类型:

abstract class Animal
{
  abstract Animal mommy()
  abstract Animal daddy()
}

class Cat : Animal
{
  override Cat mommy() {...}
  override Cat daddy() {...}
}

此返回 ​

宣布返回的方法 This 是一种特殊的共变情况,它总是返回所使用的类型。 这种技术通常用于返回的方法 this 以启用方法链。 考虑这个例子:

class Connection
{
  Connection open() { return this }
}

class MyConnection : Connection
{
  MyConnection talk() { return this }
}

API的写法允许方法链,所以我们希望能够写出这样的东西:

MyConnection.make.open.talk

如果你真的试图编译那个代码,你会得到一个错误,像"未知的插槽连接.alk". 我们可以写代码而不使用方法链, 或者我们可以使用“ ->”操作符。 但这种技术很常用,所以Fantom允许您声明返回类型为: This:

class Connection
{
  This open() { return this }
}

class MyConnection : Connection
{
  This talk() { return this }
}

这个 This 类型是一个特殊的标记类型,例如 Void它表明,保证一种方法总是返回目标类型的实例。 在上文的例子中, x.open 将总是评估到 Type.of(x).

使用更积极地使用 This 仅限于非静态方法的返回类型。 挚爱不能用于静态方法,参数类型,局部变量类型,或用于字段. 返回方法的覆盖 This 也必须返回 This.

动态信息 ​

正如任何动态语言建议者可以告诉你的 - 有时静态打字会是一个真正的痛苦. 因此Fantom通过提供两个呼叫操作员来支持混合静态/动力设计. 这个 . 点操作员使用静态打字访问一个槽 - 如果该槽无法在编译时间解决,则导致编译时间错误.

这个 -> 动态引用运算符允许您执行没有编译时间类型检查的调用。 什么样的动态引用 它实际上产生一个呼叫 Obj.trap 方法。 默认 trap 方法使用反老头子来查看和调用方法。 如果名称映射老头字段, 那么 trap 将获取或设置字段,视参数数而定:

a->x        a.trap("x", [,])
a->x = b    a.trap("x", [b])
a->x(b)     a.trap("x", [b])
a->x(b, c)  a.trap("x", [b, c])

在最简单的情况下, -> 操作员是通过静态类型检查和使用反射的语法糖。 但能够推翻 trap 方法是Fantom工具包中用于构建动态解决方案的强大技术.

你也可以说 ?-> 操作员安全处理无效目标。 见 safe invokes.

本地方法 ​

本土方法以另一种语言实施,这种语言是每个目标平台的“本土”方法。 原生方法一般为Java VM和C#为.NET CLR所写. 当地方法使用 native 关键词且不得有方法体(如抽象方法). 支持当地方法的基础设施在下文讨论。 Natives 章节。


混合 ​

概览 ​

Mixins是一组零或更多插槽通过继承进行再利用的机制. Mixins类似于Java/C#接口,但可以包含具体的方法:

  • 混血儿在pod内进行测距,并在全球确定合格名称 podName::MixinName
  • 混音包含零或更多唯一命名的插槽
  • 混音是隐含抽象
  • 混音器可能继承零或更多其他混音器
  • Mixins 无法声明具体实例字段
  • 混合 混合 混合 - - - - - - - - - - - - - M - - - - M - - - - - - - - - - - - - M - - - - - - - - - - - - - - - - - - - -
  • 混音器一旦使用方法就无法声明
  • 混音可以宣布抽象方法
  • Mixins可以宣布具体实例方法
  • 混音器可以宣布静态方法
  • 混音可以宣布抽象字段
  • 混音器可以宣布静态康斯特场
  • Mixins可以宣布静态构造器: static {}

混合物使用 mixin 关键字 :

mixin MyNewMixin {}

要了解混音是如何在封面下实际工作的,看看 tour on mixins 以查看如果翻译到 Java 时混音符可能是什么样子 。

Mixin 修改器 ​

混音符可以附加注释 public or internal 修饰者就像 classes.

可宣布混音符与 const 修饰者要求使用它的任何类别 也必须是 const.

使用“% 1” 是一个编译时间错误 abstract or final 带有混音符的修饰器.


命名 ​

概览 ​

"名字"是一套使用字符串作为标识符的公约和规则. 善名一般是人可读的,虽然不一定需要. Fantom提供一种基于以下内容的统一命名设计: Uri 课。 乌里斯是一个很好的命名选择,因为它们提供了:

  • 能够将名字转换成ASCII字符串(有适当的逃逸)
  • 在“protocol”(化学)处理器中插入定义良好的模型
  • 路径等级模式定义良好
  • 通过查询确定的名称/值对模型
  • 明确界定的相对化和正常化规则
  • Uris 向基于网络的应用程序提供干净的地图
  • Uris以替代语言和平台得到广泛支持

解决乌里斯 ​

在Fantom中,我们可能想要识别出的名字的任何信息 都会被指定为Uri。 我们决定乌里斯 sys::Obj 实例。 实际对象类型取决于Uri. 例如所有"文件:" 乌里斯将决心 sys::File解决Uri是通过 Uri.get 方法 :

File f := `file:/dir/file.txt`.get

如果文件无法解决, 则未解析 Err 会被丢弃 。 你可以通过假的 checked 如果 Uri 无法解决, 参数返回无效 。

默认行为 Uri.get 在相对的乌里(null)计划下,将抛出未解的Err。 但可以在可选基对象中通过. 如果Uri是相对的,那么我们试图通过动态调用来解决基体的尿液 base->uri如果基地的尿液是绝对的 那就解决我们 base->uri + uri:

base := `file:/dir/`.get
`file.txt`.get(base)  =>  resolves to `file:/dir/file.txt`

Uri 计划 ​

这个 UriScheme 类用于插入处理新的 Uri 方案。 标准粉丝运行时间为以下计划提供支持:

  • fan:解析Fantom命名空间中的对象(讨论如下)
  • ** 文件**:决心 File 本地文件系统中的实例 -http:还没有完成,但很快...

您可以通过子分类插入您的计划处理 UriScheme:

const class CustomScheme : UriScheme
{
  override Obj? get(Uri uri, Obj? base) { ... }
}

你覆盖 get 用于执行 uri 到对象解析的方法。 要注册您的计划,请定义 indexed prop 格式为:

sys.uriScheme.{scheme}={qname}

如果方案是小写方案名称,则Qname是您方案类型的合格名称。 索引道具被定义您的构建脚本, 例如:

index = ["sys.uriScheme.fan": "sys::FanScheme"]

扇形图案 ​

Fantom 运行时间包括支持用于识别与 Fantom 命名空间相关的对象的"fan:"方案:

fan://pod/dir/file.txt   =>  sys:Pod.file (resource file)

土著人 ​

概览 ​

原生班级,方法和田地用于执行运行时平台的原生语言Fantom. 例如,JVM的Java,C#的CLR,以及浏览器的JavaScript,都将采用本地方法. 土著是便携式库的粘结码,例如 inet 和 dom.

以下是不写本地代码时的快速检查列表:

向天顶向天顶上 向天顶上 2. 如果您不关心可移植性, 那就使用 FFI 调用到本地图书馆 - 看 JavaFFI 3. 如果您需要调用本地的 API, 但依然希望您的 Fantom room 可以跨多个平台移植, 然后使用本地人

写原生代码时,参考FFI文档如何将Fantom类型映射到其原生平台类型. 例如,见 Java FFI 来映射 Fantom 和 Java 类型。

土著阶层 ​

一个土著阶层是用当地语言编码整个实施的一个阶层。 一般来说,本地性代码必须与编译者/运行时间所发出的代码完全相同。 这个 sys pock完全作为原生类执行,这使得在Java,C#和JavaScript中寻找实例成为了极好的地方.

土著阶层与 native 类标题中的关键词 :

native class Foo
{
  new make(Str arg)
  Int add(Int a, Int b)
  Str? a
  const Str b := "const"
}

所有方法都假定是原生的,不得有 我只是在想 每个平台必须有一个执行类。 以下是Java执行的外观:

class Foo extends FanObj
{
  // constructor factory called by Foo.make
  public static Foo make(String arg)
  {
    Foo self = new Foo();
    make$(self, arg);
    return self;
  }

  // constructor implementation called by subclasses
  public static void make$(Foo self, String arg) {}

  // boiler plate for reflection
  public Type typeof()
  {
    if (type == null) type = Type.find("mypod::Foo");
    return type;
  }
  private static Type type;

  // methods
  public long add(long a, long b) { return a + b; }

  // mutable field
  public String a() { return a; }
  public void a(String it) { a = it; }
  private String a;

  // const field
  public String b = "const";
}

原生同辈 ​

具有本土方法和球场的班级的一般设计是为每个Fantom类型创建一个对等班. 这些对等点可能是所有 Fantom 实例共用的单数, 或者您可以使用每个 Fantom 实例的对等点实例 。 请注意,同龄人不用于土著阶层。

任何定义本地位置的类必须声明对应类:

// Fantom code
class Foo
{
  native Int add(Int a, Int b)
}

// Java peer
package fan.mypod;
public class FooPeer
{
  public static FooPeer make(Foo self) { return new FooPeer(); }
  public long add(Foo self, long a, long b) { return a + b; }
}

// C# peer
namespace Fan.Mypod
{
  public class FooPeer
  {
    public static FooPeer make(Foo self) { return new FooPeer(); }
    public long add(Foo self, long a, long b) { return a + b; }
  }
}

// JavaScript peer
fan.mypod.FooPeer = fan.sys.Obj.$extend(fan.sys.Obj);
fan.mypod.FooPeer.prototype.$ctor = function(self) {}
fan.mypod.FooPeer.prototype.add = function(self, a, b) { return a + b; }

从 Fantom Harper 实例中总是可以通过一个内置的字段访问对等点 peer. 在创建与本地人同级的等级时, 由您的同级工厂来覆盖超级阶级的同级领域 :

public static FooPeer make(Foo t)
{
  FooPeer peer = new FooPeer();
  ((FooBaseClass)t).peer = peer; // override base class's peer field
  return peer;
}

本地方法 ​

poultry. 原用方法总是被引导到同级:

// Fantom
class Foo
{
  native Str a(Bool x)
  static native Void b(Int x)
}

// Java or C#
class FooPeer
{
  public static FooPeer make(Foo self) { return new FooPeer(); }

  // instance methods always take implicit self
  public String a(Foo self, boolean x) { return "a"; }

  // static methods are just normal statics with matching signatures
  public static void b(long x) {}
}

// JavaScript
fan.mypod.FooPeer.prototype.a = function(self, x) { return "a"; }
fan.mypod.FooPeer.b = function(x) {}

所有非静态的方法和字段都会通过Fantom实例作为隐含的第一论据. 这使得您可以对所有实例使用单顿端点 。 通常情况下,如果想要管理对等点上的状态,则只分配对等点实例。

原生田园 ​

原生的田地类似于抽象的田地,因为它们产生一个得到和设置的田地,但没有实际的存储. 发送进程将引导 Fantom 获取器/ 获取器到对等类 :

// Fantom
class Foo
{
  native Str? f
}

// Java
class FooPeer
{
  public static FooPeer make(Foo self) { return new FooPeer(); }
  public String f(Foo t) { return f; }
  public void f(Foo t, String v) { f = v; }
  String f;
}

// C#
class FooPeer
{
  public static FooPeer make(Foo self) { return new FooPeer(); }
  public string f(Foo t) { return m_f; }
  public void f(Foo t, String v) { m_f = v; }
  string m_f;
}

// JavaScript
fan.mypod.FooPeer.prototype.m_f = "";
fan.mypod.FooPeer.prototype.f   = function(t) { return this.m_f; }
fan.mypod.FooPeer.prototype.f$  = function(t, v) { this.m_f = v; }

原生地可以是虚拟的,也可以是超类的,但不能是康斯特的,静态的,或者抽象的.


波兹 ​

概览 ​

Pods是Fantom命名空间的顶部以及部署单位. 一个pod的名字在全球独一无二,用来组织Fantom命名空间的顶层. 在这方面,pod既服务于Java软件包的目的,也服务于JAR文件(或.NET命名空间和DLL)的目的.

波德·梅塔 ​

发扬各种不同语言 Facets 用于类型和槽等元数据。 相反,bock元数据作为名称/值对进行管理。 关于您的 pock 的关键元数据定义于 build script:

class Build : build::BuildPod
{
  new make()
  {
    podName = "myPod"
    summary = "Summary description of my pod"
    depends = ["sys 1.0", "web 1.0"]
    meta    = ["acme.sku": "0xdeadbeef"]
    srcDirs = [`fan/`]
    resDirs = [`locale/`]
  }
}

在构建过程字段中 podName 和 depends 被编译器用来生成 pock 的元数据。 您可以定义自己的附加名称/值对 BuildPod.metaỗb 。此外, 编译器将添加自己的元数据, 内容是创建时间和地点。

元数据存储在 pock zip文件中的"/meta.props". 这个 Pod.meta 用于在运行时访问一个 pock 的元数据。

以下是标准化用户定义的密钥:

  • org.name: 开发pod的组织名称; 该名称应当是在所有吊再见上一致使用的唯一字符串名称
  • org.uri: 本组织的网站URL
  • proj.name: 组合一个或一个pod的项目名称; 应是一个在所有pod上一致使用的独特字符串名称
  • proj.uri: 项目的网站 URL
  • license.name: 项目使用的执照名称, 应为“ 商业” , 或由 OSI
  • vcs.name:版本控制系统的名称:"Mercurial","Git","Subversion"
  • vcs.uri: 公共版本控制存储器的 URI

以下是编译器自动定义的标准化密钥:

  • pod.name: 舱名
  • pod.version: Version 24.1 (商品代号)
  • pod.depends: 分号分隔列表 Depend 字符串
  • pod.summary: rop 的概览解密
  • pod.isScript: 被编译为脚本模式
  • pod.docSrc:如果文档包含源代码,请参见 BuildPod
  • pod.docApi: 显示器是否包含在文档中,见 BuildPod
  • pod.js: room 是否包括客户端 JavaScript 代码( 真假)
  • pod.native.java: pock 是否使用本地 Java 代码( 真假)
  • pod.native.dotnet: cod是否使用本地的.NET代码(真假)
  • pod.native.js: 播客是否使用本地 JavaScript 代码( 真假)
  • fcode.version: 二进制格式版本的fcode
  • build.ts: 以本地时区格式编译时间 DateTime
  • build.platform: 编译 env 平台格式化为 Env.platform
  • build.host: 编译env主机名,参见 Env.host
  • build.user: 编译env 用户名,参见 Env.user
  • build.compiler: 编译器 pock 版本

序列化 ​

概览 ​

序列化是将对象写入输出流的过程,从输入流读回来. 序列化提供了一个简单的机制,可以将对象粘着到一个文件或者将对象传递到一个网络上. 序列化也用于 actors 作为在演员之间传递信息的安全方式. Fantom序列化使用人类友好的文本格式,其外观与Fantom源代码(实际上它是源语法的一个子集)很像.

数据模型 ​

Java中的序列化是 *graph * 基于 - 它会处理任意引用特定对象的数量. Fantom 序列化严格基于 *tree *, 它不会试图保持对对象引用的跟踪 - 这取决于您将您的数据模型设计为树 。 如果您需要在树上交叉引用对象,那么您应该使用Uri或其他标识符.

树上的每个物体被分类为 *literal *, *simple *, 或 *complex *. 大部分标准方图文字如: Bool, Int, Str 以及收藏类型 List 和 Map简单是叶节点通过串式表示序列化的. 复合体(Complex)是一个集合节点,由它们存储嵌套对象的字段所定义(或者字面,简单,或者其他复合体). 也可以将任何复杂类型标记为 collection.

可序列 ​

这个 Serializable acet 用于标记可序列的类型。 默认情况下,一个可序列的对象会将其所有非静态字段序列化. 你可以用这个 Transient 用于注释一个不应序列化的字段。 一个巧妙的例子:

@Serializable
class Rectangle
{
  Int x; Int y
  Int w; Int h
  @Transient Int area
}

可序列对象必须支持 make 构造器,它要么不使用参数,要么使用它块。 如果构造器用它块,则在使用时传递字段值 Field.makeSetFunc 并且该对象有机会进行验证。 例如:

@Serializable
const class Rec
{
  new make(|This| f)
  {
    f(this)
    if (id <= 0) throw ArgErr("id <= 0")
  }
  const Int id
}

"Rec { id = 3 }".in.readObj  // ok
"Rec {}".in.readObj          // throws ArgErr

简单 ​

设定 最后 Serializable.simple 字段来标记一个在原子上序列化的类型,然后从字符串表示式中转动。 这个 Obj.toStr 方法必须返回对象的合适字符串表示。 每个简单的类型还必须声明一个名为 fromStr 当第一个参数是 Str 并返回对象实例。 一个例子是:

@Serializable { simple = true }
class Point
{
  static new fromStr(Str s) { t := s.split(','); return make(t[0].toInt, t[1].toInt) }
  new make(Int x, Int y) { this.x = x; this.y = y }
  override Str toStr() { return "$x,$y" }
  Int x := 0
  Int y := 0
}

收藏 ​

设定 最后 Serializable.collection 字段,将某一类型标记为儿童物品的容器。 藏品为巢巢零或以上儿童物品提供了简洁的语法,范围与任何串行字段相同. 这使得您可以将配置项目和孩子们嵌入一个卷曲的套套里. 每个收藏类型都必须支持 add 和 each 使用的方法 readObj 和 writeObj 页:1 见 example code.

流线 ​

系列化的方法如下:

  • InStream.readObj: 读从一开始就是单对象
  • OutStream.writeObj: 写入单个对象

串行于溪流之间是一件小事:

// write an object to an output stream
out.writeObj(obj)

// read an object from an input stream
obj := in.readObj

两者 Buf 和 File 为 不偏不倚 为

obj := [true, 5, "hi", `file.txt`]
f := File(`test.txt`)
f.writeObj(obj)
obj2 := f.readObj

默认 writeObj 将优化性能。 但是,如果你正在生成一个文件,它应该看起来漂亮 人类阅读和编辑, 你可以使用选项控制输出。 例如,将输出树的每个级别缩进2个空格,并按默认值跳过字段:

out.writeObj(obj, ["indent":2, "skipDefaults":true])

语法 ​

Fantom序列化语法的设计是方便人类阅读和书写,但也高效的机器处理. 语法基于Fantom编程语言本身,尽管它纯粹是宣示性的(没有表达或行为). 对象的定义是:

  • 字形:使用与您在源代码中使用的完全相同的表示法(包括: List 和 Map)
  • 简单:简单类型的字符串代表
  • 复合体:类型及其字段名称/值对列表

Fantom编程语言是序列化语法的完整超级集——你可以将任何序列化对象粘贴到源文件中作为表达.

使用 ​

您可以包含零或更多 using 语句位于序列对象文档的顶端。 使用语句允许使用不合格的类型名称 :

// qualified names
["red":fwt::Color("#f00"), "blue":fwt::Color("#0f0")]

// unqualified names
using fwt
["red":Color("#f00"), "blue":Color("#0f0")]

你可以使用任何一个 standard using 序列文件中的语句 :

using pod                  =>  import all types in pod
using pod::name            =>  import single type
using pod::name as rename  =>  import type with new name

请注意,与普通源代码不同, sys pod是不自动导入的。 如果您想要对 sys pod使用不合格的类型名称, 您需要明确导入 using sys.

文学 ​

大多数标准Fantom文字都是用Fantom编程语言定义的相同表示来序列化的:

注: Float 数值 天啊! NaN, INF,以及 -INF 列有: 必须使用简单的语法表示: sys:: float ("NaN") sys: float ("INF") sys: float ("-INF")

简单 ​

一个简单被序列化为: <type>("<toStr>")。写对象时, Obj.toStr 方法被调用以获取字符串表达式。 当读取对象时 fromStr 用于将字符串解码为对象。 实例:

sys::Version("1.2")
sys::Depend("foo 1.2-3.4")

您可以通过 simple expression.

复杂 ​

一个复合体被豺狼序列化为用新线或分号分隔的字段名称/值对的列表(和Fantom语句一样). 任何字段都可以省略,在这种情况下使用字段的默认值. 复合体的语法是:

<type>
{
  <field1> = <value1>
  <field2> = <value2>
  ...
}

可序列分类和序列化实例的例子:

@Serializable
class Person
{
  Str name
  Int age
  Str[] children
  Str address
}

acme::Person
{
  name = "Homer Simson"
  age = 39
  children = ["Bart", "Lisa", "Maggie"]
}

您可以直接在源代码中通过 it-blocks.

收藏 ​

收藏的序列化就像一个复合体——所有字段的序列化为名称/值对. 在田园连载之后,所有的儿童物品都由 each 方法用逗号分隔符序列化。 在去序列化期间儿童通过 add 方法。

我们可以重写 Person 以上为集合示例 :

@Serializable { collection = true }
class Person
{
  Void add(Person kid) { kids.add(kid) }
  Void each(|Person kid| f) { kids.each(f) }
  Str name
  @transient private Person[] kids := Person[,]
}

acme::Person
{
  name = "Homer Simson"
  acme::Person { name = "Bart" },
  acme::Person { name = "Lisa" },
  acme::Person { name = "Maggie" },
}

此语法也直接在源代码中通过 with-blocks.

语法 ​

Fantom 序列化格式的正式语法 :

// .fog file format for single object
objDoc    := header obj

header    := [using]*
using     := usingPod | usingType | usingAs
usingPod  := "using" id eos
usingType := "using" id "::" id eos
usingAs   := "using" id "::" id "as" id eos
obj       := literal | simple | complex
literal   := bool | int | float | decimal | str | duration | uri |
             typeLiteral | slotLiteral | list | map
simple    := type "(" str ")"
complex   := type ["{" [children] "}"]
children  := child [(eos|",") child]*
child     := field | item
eos       := ";" | newline
field     := id "=" obj
item      := obj

这个 literal, using,以及 type 生产使用相同的 grammar 作为Fantom编程语言. 不过 type 生产永远不能是一个函数类型。


槽数 ​

概览 ​

类型由唯一命名的 slots。槽定义对象类型的状态和行为。 插槽有两种类型:

槽修改器 ​

槽可附加下列修改词之一: 1.

保护 ​

可用下列修改符之一附加一个槽,以定义其能见度:

  • public: 每个人都可以访问槽
  • protected峦:只有申报舱内的子类和类型才能访问槽
  • internal大使 : 只有声明舱内的类型才能访问槽
  • private: 只有声明类型才能访问槽

如果没有指定保护关键字,该槽默认向公众开放.


爸: ​

概览 ​

Fantom代码是像Java或C#一样被写成一系列语句的. 然而与Java/C#不同,Fantom语句不需要用分号终止. 方顿语句可以终止三种方式之一:

  1. 新线
  2. 分号
  3. 区块的在现有资源范围内终止 } 卷轴

根据公约,一种简单的新线是分离声明的首选机制。 分号只在在同一行放置多个语句时使用. 例如, 这些都是有效的 Fantom 语句 :

if (authenticated)
{
  sendToHomePage
  return true
}

if (authenticated)
{
  sendToHomePage();
  return true;
}

if (authenticated) { sendToHomePage; return true }

第一个版本是首选的语法. 第三版还展现出良好的摩霍,如果它足够紧凑,可以保持代码的可读性.

Fantom语法在省略分号时并不完全明确. 所以有时你会遇到一个情况 当编译器抱怨,你可能需要坚持一个分号 - 但实际上这种情况很少发生。 另一种副作用是,Fantom要求一个方法调用的开口括号与方法标识符相同:

// ok
call(...)

// not ok
call
  (...)

该规则也适用于索引操作的开方括号:

// ok
list[...]

// not ok
list
  [...]

表达式声明 ​

最常见的语句类型是独立表达. 下列表达方式被视为有效陈述:

  • 任何任务表达式,包括增减运算符
  • 方法呼叫(或方法呼叫链)

返回语句 ​

这个 return 语与国家语句用于退出方法:

// if Void method:
return
return <expr>

// if non-Void method:
return <expr>

如果附件方法是 Void,则 return 将控制返回到名为方法的代码。 页:1 return 21. Void 方法中的语句可以选择包括返回前运行的表达式。 通过Void方法返回的表达式可以评价包括Void在内的任何类型.

如果所附方法无表决权,则 return """"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""

如果返回一个表达式,则表达式必须开始于与 return 关键字 :

// this is ok
return call(x,
  y, z)

// this is illegal
return
  call(x, y, z)

厮杀令您省略 return 如果您的方法或关闭 包含一个声明 :

Str name() { return "Bob" } // long version
Str name() { "Bob" }        // short version

公约应省略 return 单语句返回的关键字 。

本地变量 ​

可用下列语法宣布本地变量:

// syntax
<type> <identifier> := <init>

// example
Str name := "Jack Shephard"

方图支持 type inference 对于本地变量,允许您省略类型签名。 如果类型签名被省略,那么变量会按照初始化表达式进行打字. 在以上的例子中,右手一方决定 Str,因此我们可以将上面的声明改写为:

name := "Jack Shephard"

Fantom Convention鼓励尽可能使用类型推论. 右手 108. 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 4 : 3 : 4 : 4 : 4 : 4 : 4 : 4 : : 4 : 4 : 4 : 3 : : 4 : : 4 : 4 : 4 : : 4 : : 4 : 4 : 4 : : : : : 4 4 : : 4 : : : 4 : : : : : : : : : : 当类型推论不起作用时,最常见的情况是需要初始化本地变量到 null.

Fantom 使用的是 := 本地变量初始化而不是标准操作符 = 任务操作员。 A. 《公约》的主要目的 := 语法是区分正常的指派和当地的申报. 这种语法抓取程序员的意向更好,使编译器能够像拼错本地字一样抓取分类.

Fantom不支持逗号运算符声明Java或C#等多个本地变量. 在实践中,虽然大多数当地人将使用类型推论进行申报。

如果一个本地变量没有明确指定初始值,则默认默认为: null, false, 0,或 0.0f 对字段默认遵守相同的规则。 这与Java或C#有一点不同,它们需要确定的任务.

如果声明 ​

方图支持 if 和 else 使用 Java/C# 语法:

if (<cond>)
  <block>

if (<cond>)
  <block>
else
  <block>

if (<cond>)
  <block>
else if (<cond>)
  <block>
else
  <block>

这个 if 条件必须评估到 sys::Bool 表达式。 块可以是单个语句,也可以是多个语句的块,以 { } 卷发

循环语句 ​

方图支持 while 和 for 循环使用熟悉的Java和C#语法. 虽然你写Fantom时会发现 大部分循环是用 closures.

声明期间 ​

方图支持 while 使用 Java/C# 语法的循环 :

// syntax
while (<cond>)
  <block>

// example
while (p != null)
  p = p.next

这个 while 循环执行它的块,直到其条件评价到 false。 你觉得 while 条件必须评估到 sys::Bool 表达式。 块可以是单个语句,也可以是多个语句的块,以 { } 卷发

蝎子现在不支持 do while 循环。

声明 ​

方图支持 for 使用 Java/C# 语法的循环 :

// syntax
for (<init>; <cond>; <update>)
  <block>

// example
for (i:=0; i<10; ++i)
  echo(i)

这个 for 条件必须评估到 sys::Bool 表达式。 块可以是单个语句,也可以是多个语句的块,以 { } 卷发 所有三种表达式 for 循环是可选的。 如果省略了康德表达式,则默认为 true。init表达式在循环条目上执行一次,在循环每次运行后执行更新表达式。 这个 for 循环运行,直到条件评估到 false.

就像Java/C# 的输入表达式 for 循环可以是本地变量声明,它定义的本地范围仅限于 for 循环。 与Java/C#不同的是,在init和更新表达式中不支持逗号运算符.

断开语句 ​

这个 break 语句与 for 和 while 循环以突破循环نأ 例如:

for (i:=0; i<10; ++i)
{
  if (i == 3) break
  echo(i)
}
echo("done")

// prints
0
1
2
done

这个 break 语句总是适用于最内部的循环。 Fantom 不支持标签 breaks.

继续声明 ​

这个 continue 语句与 for 和 while 循环可以跳回循环的顶端。 例如:

for (i:=0; i<4; ++i)
{
  if (i == 2) continue
  echo(i)
}
echo("done")

// prints
0
1
3
done

这个 continue 语句总是适用于最内部的循环。 Fantom 不支持标签 continues.

切换语句 ​

Fantom 支持一个 switch 语句用于执行一个代码块,将表达式的值与 * case 标签 * 列表相匹配。 语法与Java/C#非常相似:

switch (<cond>)
{
  case <label1>:
    <block1>
  case <label2>:
    <block2>
  ...
  default:
    <defaultBlock>
}

条件表达式与所有案件标签相匹配。 如果找到匹配,则执行该案例块中的 两个语句. 如果找不到匹配, 可选 default 块已执行。 与 if, for,以及 while 语句,案件块没有包裹 { } 卷发

方汤的 switch 语句允许条件和大小写表达式成为任何有效的表达式。 这样也可以在字符串或类型上切换. 当条件评估为 Int or Enum 页:1 case 标签对常数进行评价,然后 switch 编译为 tableswitch 密码 否则 switch 语句匹配 case 标签,通过 Obj.equals 方法。 例如:

// tableswitch
switch (weekday)
{
  case Weekday.sat:
  case Weekday.sun:
    return "it's the weekend baby!"
  default:
    return "back to work!"
}

// equality switch
switch (method.upper)
{
  case "GET":  serviceGet
  case "POST": servicePost
  default:     methodNotSupported
}

在方顿 switch 语句不需要 break 以结束像 Java 或 C# 这样的特定案件的代码块。 你不能从一个案件区到另一个案件区 不过你们可以像我们一样 将多个案子的标签组合在一起 Weekday.sat 和 Weekday.sun 在上述例子中。

例外处理 ​

方图支持 throw 和 try 语法与Java和C#非常相似的语法语句。这些语句在其中分别讨论。 exception handling 章节。


结构 ​

概览 ​

Fbantom软件采用三种主要抽象结构:

  • Pods:部署和版本模块
  • Types:面向对象类型系统的基本单位
  • Slots: 领域和方法

这些抽象物被组织成一个三级命名空间,它使用下列语法来表示合格的名称:

  • pod
  • pod::Type
  • pod::Type.slot

波兹 ​

Pods是Fantom命名空间的顶部以及部署单位. 一个pod的名字在全球独一无二,用来组织Fantom命名空间的顶层. Pod名称类似于Java软件包或C#命名空间. 为了保证独特性,尝试使用一个不会产生冲突的命名惯例(参见: conventions).

方案也是部署、版本化和依赖管理的基本单位。 在这个角色舱中像Java JAR文件或.NET DLLs. 一个 pock 是一个标准 zip 文件,它将 Fantom 代码捆绑在 socket 的类型,元数据,加上任何关联的文件资源.

这个 Pod 类型是用于使用安装在给定的 Fantom 安装中的pod的反射 API。 常见 pock 操作的代码示例 :

Pod.list                       // list the pods installed
Pod.find("acmeFoo")            // find a pod (throws exception if not found)
Pod.find("acmeFoo", false)     // find a pod (returns null if not found)
myPod.file(`/img/icon.png`)    // lookup a resource file in myPod
`fan://myPod/img/icon.png`.get // lookup a resource file in myPod
someObj.typeof.pod             // get the pod of an object/type
Pod.of(someObj)                // get the pod of an object

见 Pods 详见第一章。

类型 ​

类型(Type)是一个ẳ所有状态和行为的面向对象的类. 类型包含在pod内,并以该pod内独有的名称识别. 那个 :: 双结号用于将 pock 名称与类型名称合并,以创建 * 合格名称 * 或 *qname *. 由于pock名称是全球独有的,一个类型的Qname也是全球独有的. 比如说 sys::Str 名称为 Str 类型 sys 舱。

Fantom中的类型有两种不同: classes 和 mixins.

这个 Type 类型是运行时用于类型工作的反射 API。 常见类型操作的代码片段 :

someObj.typeof            // get the type of the an object
myPod.types               // list the types in myPod
myPod.findType("Foo")     // find a type within myPod by its simple name
Type.find("myPod::Foo")   // lookup a type by its qualified name
Int#                      // type literal for sys::Int
someType.fits(Num#)       // reflective version of is/instanceof operator

槽数 ​

类型将状态和行为封装为槽集. 槽在指定类型内独有命名. 这个 . 点用于将父类型中的 qname 合并,以创建槽中的 qname 。 比如说 sys::DateTime.now 全球独有的限定名称 now 方法 DateTime 类型 sys 舱。

插槽有两种类型:

这个 Slot 类型是运行时使用槽工作的反射 API。 常用槽操作的代码示例 :

someType.slot("xyz")         // lookup the slot called xyz on someType
Slot.find("myPod::Foo.xyz")  // looukp a slot by its qualified name
method.call([arg0, arg1])    // invoke method using reflection
method.func                  // the function which implements the method
field.get(instance)          // get a field using reflection
SomeType#xyx                 // slot literal for slot on SomeType
#xyx                         // slot literal current type

所有插槽都由一个独特的名称键入. 这意味着Fantom不支持像Java或C#这样的参数类型超载的方法. 虽然你有时会发现这是个拖累,但Fantom中有一些功能使得这个限制变得相当可口. 第一方法参数可能存在默认——这消除了Java APIs中常用的便利方法. 其次, Obj 的所有类型子类 - 这消除了支持 API 中所有原始人所需的 API bloat java.io.PrintWriter最后,Fantom的构造器被命名,从而取消了基于参数的超载性的另一个共同要求。zed 这种限制的好处是,能够通过名称或qname来寻找方法,使反射式编程和动态引用更简单化。

方法 ​

方法是封装方顿行为的基本单位. 方法其实只是插槽包装 function。每种方法都有返回类型和零或更多输入参数。 不返回对象的方法有返回类型 Void.

这个 Method API用于在运行时反射使用方法.

深入讨论方法 Methods 章节。

字段 ​

妇科医生 妇科医生 环球环球网

  • Getter:一种用于访问该字段当前值的方法;
  • Setter:用于改变该字段当前值的方法;
  • 存储:存储当前值的存储位置;

救命 大部分字段都有三个组件,但一般情况下,获取器和设置器是由编译器自动生成的. 康斯特球场只有存储器,没有固定器。 Fantom 也允许抽象的和计算出来的字段,这些字段具有获取器和设置器,但没有存储.

这个 Field API用于在运行时反省地与字段工作.

我们稍后会潜入农田 Fields 章节。


类型系统 ​

概览 ​

Fantom类型系统主要有两个目的:

  • 封装:将字段和方法组合在一起的机制
  • 合同:模拟语义的机制

封装 ​

类型封装一组唯一命名的 slots,槽有两种类型: *场 *存储状态和 *方法 *模式行为. 将一组插槽组合在一起,使我们能够创建符合我们域模型的软件包单位。

这种封装有几种目的:

  • Contracts:它使接下来讨论的明确合同成为可能
  • Structure: 它能够实现以下三个部分的命名空间: pod::type.slot
  • Inheritance: 将插槽组合在一起,通过继承可以重新使用

合同 ​

类型还用来说明明示和默示的*合同。 明确的合同规定了该类人可以通过它所暴露的一组领域和方法做些什么. 例如,给定 File我们知道,它将支持一套明确的方法,例如: exists, isDir,以及 size。这组方法界定了合同中我们能够和不能与一个 File。如果您试图使用未知的插槽,编译器可以使用此信息进行类型检查并报告错误。 有时候你会发现编译时间类型 检查会妨碍到 - 如果那样的话,你只是从 . 运算符到 -> 操作员将类型检查推迟到运行时间( 或在您的操作中做一些聪明的事情) trap 页:1

隐含的契约规定了一个人类能够理解的语义——如果我告诉你一个变量是一个 File,那么你可能有一个很好的理解 是什么变量的模型。 编程主要是将一个问题域映射到代码中——类型系统帮助我们用特定域名来注释我们的代码.

类型 ​

方顿语有两种类型:

  • 类
  • 混合剂

类 ​

类是指定类型的主要机制. 所有对象都是完全属于一类的情况,可在运行时通过 Type.of 方法。 类支持单 这是你的 inheritance 就像Java或C#一样

混合 ​

混音是一种不单独使用的一种类型。 相反,一个混音组合将一组插槽一起继承到一个类(或另一个混音).

Mixins与Java或C#中的接口类似,但更加灵活. 一个Java或C#接口纯粹是抽象方法的类型定义,实际上不能包括任何行为本身. Fantom混音器可以宣布具体的方法,这些方法提供了更多的动力.

无法创建混音实例 - 它们是一种抽象类型,旨在在继承到类时提供再利用. Mixins 也无法存储状态 - 虽然它们可以包含 abstract fields 以定义需要字段签名的类型合同。

纯对象定向 ​

Fantom本质上是一种面向对象的语言. 这是"纯粹"的意思,即一切事物都是包括核心类型,例如:本身就是一个物体. Int。例如,Java原始类型,例如: boolean 和 int 不从子类 java.lang.Object 从而形成一个破碎的类型系统。 Fantom 定义了统一的阶级等级 sys::Obj 作为根. 既然一切都是物体,你可以把方法叫做一切:

Type.of(false) =>  sys::Bool
1972.isEven    =>  true
'x'.toChar     =>  "x"

将收到以下数据:将收到以下数据: java.lang.Object 类型,但他们不是合适的课 与良好的OO方法。 在大多数情况下, List 类被使用而不是数组。 加上你会用 Buf 改为 byte[] 和 StrBuf 改为 char[].

可忽略类型 ​

类型可能无效或不可更改。 不可忽略类型保证永远不已为空 你是个空值 。 编号为 " ?" 也就是说,除非另有说明,已为不可 你是个默认:

Str   // never stores null
Str?  // might store null

在使用无效通过的表达式时, 编译器可以防止明显的错误 :

  • 无文字
  • 安全援引方法呼叫或外地访问
  • as 运算符

附加检查在运行时的具体需要是,当强制一种无效类型变成一种不可更改类型时。 这使得您的代码在无臭虫被引入与传播到不相关的代码时快速失败.

值类型 ​

特殊类型 Bool, Int,以及 Float 是 * 值类型 *。 这些类型通过运行时间进行优化,通过值传递,而不是作为对象的参考. 这使得Fantom可以实现与Java中使用原始 他妈的和C#中使用的值类型相同的性能.

价值类型也可以是无效的. 例如,一个变量被宣布为 Int? 无法存储。

数值类型与该字段的参考类型不同,默认为假/零而不是无效。 然而,无效的版本的值类型确实默认为无效.

Fantom 类型与运行时间描述的映射:

Fantom   Default   Java                .NET
------   -------   ----                ------
Bool     false     boolean             bool
Bool?    null      java.lang.Boolean   bool?
Int      0         long                long
Int?     null      java.lang.Long      long?
Float    0.0f      double              double
Float?   null      java.lang.Double    double?

根据公约Fantom API使用 null 以表示非正常状况。 例如,通常在返回 an 的 Java API 中 int 例如, String.indexOf() or InputStream.read() 特殊值为 -1 表示非正常结果。 这在-1是有效结果时可能特别成问题。 在Fantom APIs,我们返回 Int? 和使用情况 null 而不是像 -1 这样的特殊值。

固定类型 ​

Fantom是静态输入的 - 所有的方法和字段签名都需要类型声明. 这对许多开发 你很虔诚,但我们认为 类型声明对代码分析和可读性来说 增加的值太高了, 无法把它们扔出去做一点代码压缩。

然而,确实有时静态类型系统会阻碍优雅的解决方案。 Fantom也提供一些动态打字功能:

隐性铸造 ​

编译时间类型检查通常需要在 Java 或 C# 中进行铸造的任何地方, 编译器会默认为您插入铸造 。 投影确保JVM或CLR在类型检查失败时生成运行时例外. 如果编译器知道这些类型是不兼容的,那么它就会产生编译时间错误.

在形式上,规则以使用A型和预期使用B型的任何时候表示:

  1. 如果A.Fifit(B)的电话静态已知正确
  2. 否则,如果B.FTIL(A),我们就插入一个隐含的投影
  3. 否则就是编译时间错误

例如:

Int func(Int x) { ... }

Int i := 5
Num n := 5
Str s := "foo"

// statically correct as is: Int.fits(Int)
func(i)  =>  func(i)

// implicit cast inserted: Int.fits(Num)
func(n)  =>  func((Int)n)

// compile time error: !Int.fits(Str)
func(s)  =>  error

此特性允许您使用 Obj 作为任意的通配符类型。 这往往与返回的动态引用结合使用。 Obj?:

Str name := x->person->name
if (test->isTrue) {...}
File(x->uri)

从无核型到无核型的胁迫是安全的. 从无效到不可撤销的胁迫是默认允许的,但会在运行时检查:

Str? x := null
Str  y := x      // implicit cast as y := (Str)x

上面的代码会和暗影一起编译. 但是,在运行时,它会失败 NullErr.

签名类型 ​

我们称用于表达类型声明的语法为类型签名. 类型签名在您的源代码,fcode格式和反射竿API中广泛使用. 一种类型的正式签名是其限定名称或 qname虽然在源代码中,我们通常使用简单的名称与 using 语句。 还有一种特殊的语法来表达 generic types.

收款 ​

并予以审查。 List 和 Map。这两种类型都有一个特殊的字面语法和一个特殊的类型签名语法。

列表 ​

Lists是一系列具有快速整数索引功能的对象集合. 一个Fantom列表非常类似于一个 ArrayList 在 Java 或 C# 中具有类似的性能权衡:快速索引和附加,但较慢的插入和去除中间部分. 名单有: literal 语法和特殊 type signature 语法.

地图 ​

的确是一等一等一等 一等一等一等一等一等一等 HashMap or Hashtable 在 Java 或 C#. 地图有 literal 语法和特殊 type signature 语法.

常规 ​

虽然目前还没有通用通用机机制,但”.fantom'确实有限地使用通用机. 具体来说,有三个班级使用通用的:

这些是方图姆语中仅有的三种基因类型. 每个通用类型在其方法签名中使用一组基因参数. 通用参数总是以下单个ASCII字母之一: A-H, L, M, R,以及 V。下文将讨论每个通用参数的含义。

要使用通用参数,我们必须为每个通用参数指定一个类型——我们称之为“参数化”。 Fantom 不使用一般目的参数化语法 List<Str> 用于 Java 和 C# 。 相反,三种通用类型中的每一类都有下面讨论的自定义参数化语法。

列表类型 ​

这个 List 类使用两个通用参数:

  • V: 列表中存储的项目类型
  • L: 参数化列表的类型

参数化语法 List 用于模仿Java和C#的数组语法:

// format
V[]

// examples
Str[]     // list of Strs
Int?[]    // list of Int?
Int[][]   // list of Int[] (list of a list of Ints)

这个 L 通用参数用于表示参数化类型本身。 举例来说,下面是: List.add 方法 :

L add(V item)

特定类型 Str[],则 V 地图到 Str 和 L 地图到 Str[]因此, add 方法 Str[] 参数化为:

Str[] add(Str item)

地图类型签名 ​

这个 Map 类使用三个通用参数:

  • K: 地图存储的密钥类型
  • V: 地图存储的值类型
  • M: 参数化地图的类型

参数化语法 Map 用来模仿 map literal 语法 :

// format
[K:V]          // formal signature
K:V            // brackets are optional in most cases

// examples
[Str:User]     // map of Users keyed by Str
Str:User       // same as above without optional brackets
Uri:File?      // map of File? keyed by Uri
[Uri:File]?    // map of Uri:File where the entire map variable might be null
Str:File[]     // map of File[] keyed by Str
[Str:File][]   // list of Str:File (brackets not optional)

正式语法 Map 参数化为 [K:V]通常括号是可选的,按公约规定予以删除。 但在某些复杂的类型声明中,你需要使用括号,例如: [Str:File][] 例见上文。 Brackets总是用于返回正式签名的API.

Func 类型签名 ​

这个 Func 类使用九个通用参数:

  • A to H: 函数参数类型
  • R: 函数返回类型

参数化语法 Func 用于匹配 closures:

// format
|A a, B b ... H h -> R|

// examples
|Int a, Int b->Str|  // function which takes two Int args and returns a Str
|Int, Int->Str|      // same as above omitting parameter names
|->Bool|             // function which takes zero args and returns Bool
|Str s->Void|        // function which takes one Str arg and returns void
|Str s|              // same as above, omitting optional void return
|->Void|             // function which takes no arguments and returns void
|->|                  // shortcut for above

功能签名在功能编程和关闭中被广泛使用. 一开始可能有点难理解 但我们的参数化是 Func 类本身 - 函数和返回类型的参数。

为了更好理解这一点,让我们考虑一下Java的例子. 我们经常想要宣布“召回方法”的类型——在Java中,我们通常通过创建接口来做到这一点。 然后,每当我们需要指定一种需要召回的方法时,我们就使用该接口类型:

interface Comparator
{
  int compare(Object a, Object b);
}

void sort(Comparator comparator)

在Fantom中,我们跳过接口部分,只需使用位置内函数签名来宣告回调类型:

Void sort(|Obj a, Obj b->Int| comparator)

这个签名说 sort 需要一个名为 comparator 参考文献 a Func 这需要两个 Objs 返回一个 Int.

但我们通常在整理 List 它本身已经被参数化。 List 带内置的 sort 实际签名的方法:

L sort(|V a, V b->Int| c := null)

这种方法结合了列表的通用 V 带有函数签名的参数。 因此给出了类型列表 Str[],然后将参数化的版本 sort 将:

Str[] sort(|Str a, Str b->Int| c := null)

函数签名在 Functions 章节。

替代类型 ​

这个 Liskov Substitution Principle 定义一个子类型可以替换一个超级类型,行为保持不变。 一般来说,这一原则适用于Fantom类型系统和标准库。

替代规则用于确定A型是否可转让给B型。 在编译过程中,我们使用这些规则检查可变任务、实地任务和方法调用参数。 这些规则也用于: is/isnot/as 运算符和 Type.fits 方法。

Fantom的子打字替换规则的定义如下,如果:

  1. A为B
  2. A是扩展B类的类别
  3. A类是执行混音B的类别。
  4. A是一种混合体,用于执行混合体B
  5. A类和B类为:
    1. A:V符合B:V
  6. A类和B类是:
    1. A:K符合B:K
    2. A:V符合B:V
  7. A类和B类是:
    1. B:R为Void或A:R符合B:R
    2. 空气 QQ B 平均(参数数)
    3. Madison 每一参数位置的B参数i符合A参数i

注解函数类型有特殊的“反向”替换规则,参见 Functions chapter 进行更广泛的讨论。

在以上所有情况下,均特别处理无效性,A型非无效性符合无效性B。 is 操作人员家属,或 Type.fits.

注意收藏类型 List 和 Map 允许类似 Java 阵列工作的反变异 。 比如说 Int[] 被认为可以替代 Num[]对于清单获取方法来说,这是事实。 然而,对于增加/设定方法,情况并非如此,因为可以添加 Float 改为: Num[]但是这是非法的 Int[]尽管存在这一漏洞,但我们仍然出于务实的原因进行这种权衡,因为大多数公共API都如此广泛地使用只读或不可更改的清单。

然而,请注意,在JVM的执行中,列表由适当类型的Java阵列支持,一般在设置/添加操作中进行运行时间检查. 然而,无法绝对保证会检查反变异的套/加。