GleamErlang互操作Skill GleamErlangInterop

Gleam Erlang 互操作技能用于在Gleam编程语言中实现与Erlang生态系统的无缝集成,允许开发者调用Erlang代码、使用Erlang库和外部函数,同时保持类型安全。它包括外部函数声明、Erlang标准库使用、动态类型处理、NIFs和端口、OTP集成等关键功能,适用于后端开发和高可靠性应用构建。关键词:Gleam, Erlang, 互操作, 外部函数, 类型安全, BEAM生态系统, NIF, 端口, OTP, 后端开发。

后端开发 0 次安装 2 次浏览 更新于 3/25/2026

名称: 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集成实现基于成熟模式的生产级应用构建。

最佳实践

  1. 使用安全接口包装外部函数,处理错误并提供Gleam类型。

  2. 使用动态解码器处理从Erlang接收的所有数据,确保类型安全。

  3. 文档化外部函数行为,因为Erlang代码缺乏静态类型信息。

  4. 显式处理所有Erlang错误情况,而不是假设成功。

  5. 使用不透明类型处理没有直接Gleam等效的Erlang类型

  6. 彻底测试互操作边界,因为类型不匹配会导致运行时错误。

  7. 当功能在两者中都存在时,优先使用Gleam标准库而非Erlang

  8. 使用Result类型处理可能失败的Erlang调用,使错误显式。

  9. 在调用有复杂要求的Erlang代码时验证边界数据

  10. 将互操作代码隔离在特定模块中,便于维护。

常见陷阱

  1. 未处理Erlang错误导致运行时意外崩溃。

  2. 外部函数上的不正确类型注解导致类型混淆。

  3. 忘记动态解码器绕过Erlang边界的类型安全。

  4. 假设Erlang返回特定类型而不验证导致崩溃。

  5. 未用实际Erlang值测试错过类型不匹配问题。

  6. 到处使用Dynamic削弱Gleam的类型安全优势。

  7. 忽略Erlang原子在响应处理中导致解码失败。

  8. 未正确处理Erlang元组格式导致模式匹配错误。

  9. 忘记Erlang函数的错误原子返回ok/error元组。

  10. 阻塞在同步Erlang调用上可能导致进程死锁。

何时使用此技能

应用互操作以利用现有Erlang库,这些库在Gleam中不可用。

使用外部函数访问Erlang标准库功能。

利用NIFs进行需要原生代码的性能关键操作。

集成OTP以构建需要可靠性的生产系统。

使用端口与外部程序或系统命令通信。

应用动态解码器处理从Erlang或外部系统接收的数据。

资源