名称: Gleam Erlang 互操作 用户可调用: false 描述: 当涉及Gleam-Erlang互操作性时使用,包括从Gleam调用Erlang代码、使用Erlang库、外部函数、处理Erlang类型、NIFs,以及从Gleam应用程序利用BEAM生态系统。 允许工具: []
Gleam Erlang 互操作
引言
Gleam编译到Erlang,实现与广泛的Erlang生态系统的无缝互操作性。这种互操作允许Gleam开发者在编写类型安全的Gleam代码时,利用经过实战测试的Erlang库,将现代语言特性与数十年生产验证的库相结合。
互操作机制使用外部函数调用Erlang代码,Gleam的类型系统在边界提供安全性。Gleam可以调用任何Erlang函数,使用Erlang进程,并与OTP行为集成,同时保持类型安全。
本技能涵盖外部函数声明、调用Erlang模块、处理Erlang类型、使用Erlang标准库、NIFs和端口,以及在与Erlang代码集成时的安全类型边界模式。
外部函数声明
外部函数用Gleam类型声明Erlang函数,为Erlang代码提供类型化接口。
// 基本外部函数
@external(erlang, "erlang", "list_to_binary")
pub fn list_to_binary(list: List(Int)) -> BitArray
// 使用外部函数
pub fn convert_list() {
let list = [72, 101, 108, 108, 111]
let binary = list_to_binary(list)
io.debug(binary)
}
// 带多个参数的外部函数
@external(erlang, "string", "concat")
pub fn string_concat(a: String, b: String) -> String
pub fn join_strings() {
let result = string_concat("Hello, ", "World!")
io.debug(result)
}
// 返回Result的外部函数
@external(erlang, "file", "read_file")
fn erlang_read_file(path: String) -> Result(BitArray, Dynamic)
pub fn read_file(path: String) -> Result(String, String) {
case erlang_read_file(path) {
Ok(contents) -> {
case bit_array.to_string(contents) {
Ok(str) -> Ok(str)
Error(_) -> Error("Invalid UTF-8")
}
}
Error(_) -> Error("File read error")
}
}
// 带Erlang类型的外部函数
@external(erlang, "maps", "get")
fn map_get(key: a, map: Map(a, b)) -> Result(b, Nil)
@external(erlang, "maps", "put")
fn map_put(key: a, value: b, map: Map(a, b)) -> Map(a, b)
// 使用Erlang的timer模块
@external(erlang, "timer", "sleep")
pub fn sleep(milliseconds: Int) -> Nil
pub fn wait_a_second() {
io.println("Waiting...")
sleep(1000)
io.println("Done!")
}
// 带元组的外部函数
@external(erlang, "calendar", "local_time")
pub fn local_time() -> #(#(Int, Int, Int), #(Int, Int, Int))
pub fn get_current_time() {
let #(date, time) = local_time()
let #(year, month, day) = date
let #(hour, minute, second) = time
io.debug(#(year, month, day, hour, minute, second))
}
// 带原子的外部函数
pub type Atom
@external(erlang, "erlang", "binary_to_atom")
fn binary_to_atom(binary: BitArray) -> Atom
@external(erlang, "erlang", "atom_to_binary")
fn atom_to_binary(atom: Atom) -> BitArray
// 创建安全包装器
pub opaque type ErlangAtom {
ErlangAtom(atom: Dynamic)
}
pub fn atom_from_string(str: String) -> ErlangAtom {
let bits = bit_array.from_string(str)
let atom = binary_to_atom(bits)
ErlangAtom(atom: dynamic.from(atom))
}
// 带回调的外部函数
@external(erlang, "lists", "map")
pub fn erlang_map(f: fn(a) -> b, list: List(a)) -> List(b)
pub fn double_list(list: List(Int)) -> List(Int) {
erlang_map(fn(x) { x * 2 }, list)
}
// 进程相关的外部函数
@external(erlang, "erlang", "self")
pub fn self() -> process.Pid
@external(erlang, "erlang", "spawn")
pub fn spawn(f: fn() -> Nil) -> process.Pid
// 系统外部函数
@external(erlang, "erlang", "system_time")
fn system_time_nanos() -> Int
pub fn current_timestamp() -> Int {
system_time_nanos() / 1_000_000
}
外部声明桥接Gleam的类型系统与Erlang的动态运行时。
使用Erlang标准库
Gleam可以利用Erlang全面的标准库进行各种操作。
// Crypto模块
@external(erlang, "crypto", "hash")
fn crypto_hash(algorithm: Atom, data: BitArray) -> BitArray
pub fn sha256(data: String) -> BitArray {
let algo = atom_from_string("sha256")
let bits = bit_array.from_string(data)
crypto_hash(algo.atom, bits)
}
pub fn md5(data: String) -> String {
let algo = atom_from_string("md5")
let bits = bit_array.from_string(data)
let hash = crypto_hash(algo.atom, bits)
bit_array.base16_encode(hash, False)
}
// Random模块
@external(erlang, "rand", "uniform")
fn uniform() -> Float
@external(erlang, "rand", "uniform")
fn uniform_int(n: Int) -> Int
pub fn random_float() -> Float {
uniform()
}
pub fn random_int(max: Int) -> Int {
uniform_int(max)
}
pub fn random_choice(list: List(a)) -> Option(a) {
case list {
[] -> None
items -> {
let index = uniform_int(list.length(items))
list.at(items, index - 1)
}
}
}
// ETS(Erlang Term Storage)
pub type EtsTable
@external(erlang, "ets", "new")
fn ets_new(name: Atom, options: List(Atom)) -> EtsTable
@external(erlang, "ets", "insert")
fn ets_insert(table: EtsTable, tuple: #(a, b)) -> Bool
@external(erlang, "ets", "lookup")
fn ets_lookup(table: EtsTable, key: a) -> List(#(a, b))
pub fn create_cache() -> EtsTable {
let name = atom_from_string("my_cache")
let public = atom_from_string("public")
let set = atom_from_string("set")
ets_new(name.atom, [set.atom, public.atom])
}
pub fn cache_put(table: EtsTable, key: String, value: String) -> Bool {
ets_insert(table, #(key, value))
}
pub fn cache_get(table: EtsTable, key: String) -> Option(String) {
case ets_lookup(table, key) {
[#(_, value)] -> Some(value)
_ -> None
}
}
// 通过Erlang的httpc进行HTTP客户端调用
@external(erlang, "httpc", "request")
fn httpc_request(url: String) -> Result(Dynamic, Dynamic)
pub fn http_get(url: String) -> Result(String, String) {
case httpc_request(url) {
Ok(response) -> {
// 解析Erlang响应元组
Ok("Response received")
}
Error(_) -> Error("HTTP request failed")
}
}
// 通过jiffy库处理JSON
@external(erlang, "jiffy", "encode")
fn jiffy_encode(term: Dynamic) -> BitArray
@external(erlang, "jiffy", "decode")
fn jiffy_decode(json: BitArray) -> Result(Dynamic, Dynamic)
pub fn encode_json(data: Dict(String, String)) -> String {
let dynamic_data = dynamic.from(data)
let bits = jiffy_encode(dynamic_data)
case bit_array.to_string(bits) {
Ok(str) -> str
Error(_) -> "{}"
}
}
// OS模块
@external(erlang, "os", "getenv")
fn os_getenv(var: String) -> Result(String, Nil)
pub fn get_env(var: String) -> Option(String) {
case os_getenv(var) {
Ok(value) -> Some(value)
Error(_) -> None
}
}
@external(erlang, "os", "cmd")
pub fn shell_command(cmd: String) -> String
pub fn list_files() -> String {
shell_command("ls -la")
}
// 代码加载
@external(erlang, "code", "ensure_loaded")
fn code_ensure_loaded(module: Atom) -> Result(Atom, Atom)
pub fn ensure_module_loaded(module_name: String) -> Bool {
let atom = atom_from_string(module_name)
case code_ensure_loaded(atom.atom) {
Ok(_) -> True
Error(_) -> False
}
}
Erlang的标准库为常见操作提供生产测试的功能。
处理Erlang类型
Gleam的Dynamic类型实现与Erlang动态类型系统的安全互操作。
import gleam/dynamic
// 处理动态值
pub fn handle_dynamic(value: Dynamic) -> String {
case dynamic.string(value) {
Ok(str) -> "String: " <> str
Error(_) -> case dynamic.int(value) {
Ok(n) -> "Int: " <> int.to_string(n)
Error(_) -> "Unknown type"
}
}
}
// 解码Erlang元组
pub fn decode_tuple(value: Dynamic) ->
Result(#(String, Int), List(dynamic.DecodeError)) {
dynamic.tuple2(dynamic.string, dynamic.int)(value)
}
// 解码Erlang列表
pub fn decode_string_list(value: Dynamic) ->
Result(List(String), List(dynamic.DecodeError)) {
dynamic.list(dynamic.string)(value)
}
// 复杂Erlang术语解码器
pub type Person {
Person(name: String, age: Int, email: Option(String))
}
pub fn person_decoder() -> dynamic.Decoder(Person) {
dynamic.decode3(
Person,
dynamic.field("name", dynamic.string),
dynamic.field("age", dynamic.int),
dynamic.optional_field("email", dynamic.string),
)
}
pub fn decode_person(value: Dynamic) ->
Result(Person, List(dynamic.DecodeError)) {
person_decoder()(value)
}
// 编码到Erlang术语
pub fn person_to_dynamic(person: Person) -> Dynamic {
dynamic.from([
#("name", dynamic.from(person.name)),
#("age", dynamic.from(person.age)),
#("email", case person.email {
Some(email) -> dynamic.from(email)
None -> dynamic.from(Nil)
}),
])
}
// 处理Erlang记录(元组)
pub fn erlang_record_decoder(
tag: String,
) -> fn(Dynamic) -> Result(List(Dynamic), List(dynamic.DecodeError)) {
fn(value) {
use tuple <- result.try(dynamic.tuple(value))
use first <- result.try(case list.at(tuple, 0) {
Ok(elem) -> dynamic.string(elem)
Error(_) -> Error([dynamic.DecodeError("Expected tuple con string tag", "")])
})
case first == tag {
True -> Ok(list.drop(tuple, 1))
False -> Error([dynamic.DecodeError("Wrong record tag", "")])
}
}
}
// 处理Proplist(Erlang键值列表)
pub type Proplist =
List(#(Dynamic, Dynamic))
pub fn proplist_get(proplist: Proplist, key: String) -> Option(Dynamic) {
list.find_map(proplist, fn(pair) {
let #(k, v) = pair
case dynamic.string(k) {
Ok(str) if str == key -> Ok(v)
_ -> Error(Nil)
}
})
|> result.to_option
}
// 通过dynamic处理原子
pub fn decode_atom(value: Dynamic) -> Result(String, List(dynamic.DecodeError)) {
case dynamic.string(value) {
Ok(str) -> Ok(str)
Error(_) -> Error([dynamic.DecodeError("Expected atom", "")])
}
}
// BitString操作
@external(erlang, "erlang", "bit_size")
pub fn bit_size(bits: BitArray) -> Int
pub fn analyze_bitarray(bits: BitArray) -> String {
let size = bit_size(bits)
"BitArray of " <> int.to_string(size) <> " bits"
}
// 引用处理
pub opaque type Ref {
Ref(ref: Dynamic)
}
@external(erlang, "erlang", "make_ref")
fn make_ref() -> Dynamic
pub fn new_reference() -> Ref {
Ref(ref: make_ref())
}
pub fn compare_refs(a: Ref, b: Ref) -> Bool {
dynamic.from(a.ref) == dynamic.from(b.ref)
}
动态解码器提供从Erlang动态类型到Gleam静态类型的安全转换。
NIFs和端口
本地实现函数和端口实现与C代码和外部程序的集成。
// NIF声明
@external(erlang, "my_nif_module", "fast_computation")
pub fn fast_computation(input: Int) -> Int
// 用于外部程序通信的端口
pub type Port
@external(erlang, "erlang", "open_port")
fn open_port(name: #(Atom, String), options: List(Atom)) -> Port
@external(erlang, "erlang", "port_command")
fn port_command(port: Port, data: BitArray) -> Bool
@external(erlang, "erlang", "port_close")
fn port_close(port: Port) -> Bool
pub fn start_external_program(program: String) -> Port {
let spawn = atom_from_string("spawn")
let binary = atom_from_string("binary")
open_port(#(spawn.atom, program), [binary.atom])
}
pub fn send_to_port(port: Port, data: String) -> Bool {
let bits = bit_array.from_string(data)
port_command(port, bits)
}
pub fn close_port(port: Port) -> Bool {
port_close(port)
}
// 使用端口与外部进程交互
pub fn python_integration() {
let port = start_external_program("python3 script.py")
send_to_port(port, "input data
")
// 接收响应
close_port(port)
}
// NIF加载模式
@external(erlang, "my_module", "init")
fn init_nif() -> Atom
pub fn load_nif_library() {
let result = init_nif()
io.debug(result)
}
// 带消息传递的端口
pub type PortMessage {
PortData(data: BitArray)
PortClosed
}
pub fn port_receiver(port: Port) {
let selector = process.new_selector()
|> process.selecting(fn(msg) {
case dynamic.tuple2(dynamic.dynamic, dynamic.dynamic)(dynamic.from(msg)) {
Ok(#(port_atom, data)) -> {
case decode_atom(port_atom) {
Ok("data") -> PortData(bit_array.from_string(""))
_ -> PortClosed
}
}
Error(_) -> PortClosed
}
})
case process.select(selector, 5000) {
Ok(PortData(data)) -> {
io.println("Received data from port")
port_receiver(port)
}
Ok(PortClosed) -> io.println("Port closed")
Error(_) -> port_receiver(port)
}
}
// 异步NIF调度
@external(erlang, "my_nif", "async_computation")
fn async_nif(input: Int, callback: fn(Int) -> Nil) -> Nil
pub fn use_async_nif() {
async_nif(42, fn(result) {
io.println("NIF result: " <> int.to_string(result))
})
}
NIFs提供性能关键的原生代码,而端口实现安全的外部进程通信。
OTP集成
Gleam无缝集成OTP行为和监督树。
// 直接使用Erlang gen_server
@external(erlang, "gen_server", "call")
fn gen_server_call(server: process.Pid, request: Dynamic) -> Dynamic
@external(erlang, "gen_server", "cast")
fn gen_server_cast(server: process.Pid, request: Dynamic) -> Nil
// 包装Erlang gen_server
pub fn call_server(server: process.Pid, request: String) ->
Result(String, String) {
let dynamic_request = dynamic.from(request)
let response = gen_server_call(server, dynamic_request)
case dynamic.string(response) {
Ok(str) -> Ok(str)
Error(_) -> Error("Invalid response")
}
}
// 使用Erlang supervisor
@external(erlang, "supervisor", "start_link")
fn supervisor_start_link(module: Atom, args: List(Dynamic)) ->
Result(process.Pid, Dynamic)
// 应用行为
@external(erlang, "application", "start")
fn app_start(name: Atom) -> Result(Atom, Atom)
@external(erlang, "application", "stop")
fn app_stop(name: Atom) -> Result(Atom, Atom)
pub fn start_app(name: String) -> Bool {
let atom = atom_from_string(name)
case app_start(atom.atom) {
Ok(_) -> True
Error(_) -> False
}
}
// 使用Erlang注册表
@external(erlang, "erlang", "register")
fn register_process(name: Atom, pid: process.Pid) -> Bool
@external(erlang, "erlang", "whereis")
fn whereis(name: Atom) -> Result(process.Pid, Nil)
pub fn register(name: String, pid: process.Pid) -> Bool {
let atom = atom_from_string(name)
register_process(atom.atom, pid)
}
pub fn find_process(name: String) -> Option(process.Pid) {
let atom = atom_from_string(name)
case whereis(atom.atom) {
Ok(pid) -> Some(pid)
Error(_) -> None
}
}
// 全局注册
@external(erlang, "global", "register_name")
fn global_register(name: Dynamic, pid: process.Pid) -> Result(Atom, Atom)
pub fn register_globally(name: String, pid: process.Pid) -> Bool {
case global_register(dynamic.from(name), pid) {
Ok(_) -> True
Error(_) -> False
}
}
OTP集成实现基于成熟模式的生产级应用构建。
最佳实践
-
使用安全接口包装外部函数,处理错误并提供Gleam类型。
-
使用动态解码器处理从Erlang接收的所有数据,确保类型安全。
-
文档化外部函数行为,因为Erlang代码缺乏静态类型信息。
-
显式处理所有Erlang错误情况,而不是假设成功。
-
使用不透明类型处理没有直接Gleam等效的Erlang类型。
-
彻底测试互操作边界,因为类型不匹配会导致运行时错误。
-
当功能在两者中都存在时,优先使用Gleam标准库而非Erlang。
-
使用Result类型处理可能失败的Erlang调用,使错误显式。
-
在调用有复杂要求的Erlang代码时验证边界数据。
-
将互操作代码隔离在特定模块中,便于维护。
常见陷阱
-
未处理Erlang错误导致运行时意外崩溃。
-
外部函数上的不正确类型注解导致类型混淆。
-
忘记动态解码器绕过Erlang边界的类型安全。
-
假设Erlang返回特定类型而不验证导致崩溃。
-
未用实际Erlang值测试错过类型不匹配问题。
-
到处使用Dynamic削弱Gleam的类型安全优势。
-
忽略Erlang原子在响应处理中导致解码失败。
-
未正确处理Erlang元组格式导致模式匹配错误。
-
忘记Erlang函数的错误原子返回ok/error元组。
-
阻塞在同步Erlang调用上可能导致进程死锁。
何时使用此技能
应用互操作以利用现有Erlang库,这些库在Gleam中不可用。
使用外部函数访问Erlang标准库功能。
利用NIFs进行需要原生代码的性能关键操作。
集成OTP以构建需要可靠性的生产系统。
使用端口与外部程序或系统命令通信。
应用动态解码器处理从Erlang或外部系统接收的数据。