Home » Лямбда-функции

Лямбда-функции

Одно из основных преимуществ Elixir и секрет его надежности и масштабируемости заключается в том, что в его основе лежат неизменяемые данные. Однако иногда неизменность просто не очень подходит для конкретной задачи, но эта задача является лишь частью большого проекта. Можно ли пользоваться преимуществами модели данных Elixir повсюду, но создать небольшое изменяемое исключение для одной области?

Да!

Давний проект Elixir, в котором я участвую, имеет именно эту проблему. Проект реализуется через Интернет, поэтому отказ от Elixir в целом невозможен, потому что Феникс это просто чит-код для веб-разработки. Мы мог выделить изменяемый раздел в микросервис, но это потребует значительных архитектурных и управленческих накладных расходов. Все, что нам действительно нужно, — это небольшой аварийный люк для ограниченного фрагмента кода, оставаясь при этом внутри той же виртуальной машины и имея возможность нормально взаимодействовать с остальной частью сервиса.

Это просто то, что Растлер предложения.

Rustler — это «библиотека для написания Erlang NIF в безопасном коде Rust» — другими словами, вы можете писать код, который выглядит как стандартные функции Elixir, но «за кулисами» фактически реализован на Rust.

НИФы были особенностью Erlang VM задолго до того, как на сцене появились Elixir или Rust. Что добавляют Rust и Rustler:

  • безопасность — это очень важно, поскольку сбой в NIF приведет к выходу из строя всей виртуальной машины.

  • много доработок и интерфейса, что позволяет писать более амбициозные интеграции, которые вы, возможно, захотите попробовать, используя C и стандартную поддержку NIF Erlang.

  • доступ ко всем библиотекам Rust

К сожалению, большинство примеров Rustler в сети фокусируются на преимуществах скорости и демонстрируют реализацию тривиальной задачи. add функционировать, а затем остановиться на этом. Хотя это вполне подходит для демонстрации минимально необходимого уровня интеграции, для меня интересной частью Rustler является возможность контролируемым образом уйти из неизменного мира — я хочу изучить, как безопасно управлять небольшим изменяемым фрагментом памяти. Хотя Rustler, безусловно, способен на это, здесь очень мало учебных пособий или примеров.

Надеюсь, эта статья поможет.

Цель

Как упоминалось выше, я хочу изучить управление памятью. Точнее, я хочу иметь возможность хранить в мире Rust часть данных, которая сохраняется между несколькими вызовами различных функций «Эликсира» (Rustler). Эти функции должны позволить миру Elixir передавать данные в мир Rust, изменять хранящиеся там данные, а затем получать результаты.

Чтобы дать нам что-то существенное для игры и избежать необходимости создавать собственное хранилище данных для этой демонстрации, я воспользуюсь Oxigraph.

Оксиграф — это библиотека базы данных графов Rust, реализующая стандарт SPARQL. Давайте предположим, что мы хотим обернуть его, чтобы иметь доступ к быстрой базе данных графов из Elixir. Мы назовем нашу обертку FeGraph.

Мы хотим иметь возможность:

  1. Создайте новую базу данных в памяти.

    db = FeGraph.new()
  2. Добавьте в него данные

    FeGraph.set(db, "http://foo.bar.com")
    FeGraph.set(db, "http://foo.baz.com")
  3. Экспортируйте базу данных как
    Черепаха нить:

    FeGraph.dump_db(db) |> IO.puts()

Чтобы сохранить разумную длину, мы не собираемся реализовывать все, что потребуется для раскрытия всех возможностей Oxigraph; ровно столько, чтобы продемонстрировать хранение данных на стороне Rust и действия с ними из Elixir.

Выполнение

(Если вы хотите следовать инструкциям, я рекомендую поработать с одним из многих Rustler. add руководства, которые я упоминал перед тем, как приступить к коду, чтобы у вас был запущен базовый проект и вы поняли, как функции Rust и Elixir связаны друг с другом. Все нижеследующее предполагает, что вы уже находитесь на этом этапе.)

Ключом ко всему этому подходу является способность пройти
Ресурс между двумя мирами. Это действует как дескриптор фрагмента памяти; его можно вернуть из NIF, а затем передать обратно в другой вызов. Именно то, что нам нужно.

ЛУЧ Resource представлен в Rustler
rustler::resource::ResourceArc структура. Чтобы начать реализацию, давайте определим новый тип, который мы можем использовать в качестве дескриптора для представления состояния нашего хранилища графов.

В производственном сценарии мы, вероятно, захотим управлять большим количеством состояний, но на данный момент достаточно определить MyGraph структура, которая просто содержит (через мьютекс) хранилище данных Oxigraph; это представляет собой изменяемые данные, которыми мы хотим управлять вне Elixir. В будущем в него можно будет добавить больше полей. MyGraph
как надо.

Чтобы превратить это во что-то, что можно будет передавать туда и обратно между Elixir и Rust, нам нужно обернуть это в ResourceArc. Чтобы сделать сигнатуры наших функций более читабельными, мы определим новый тип GraphArc представлять MyGraph структура в ResourceArc.

В lib.rs:

use std::sync::Mutex;
use oxigraph::store::Store;
use rustler::resource::ResourceArc;
use rustler::OwnedBinary;
use rustler::{Env, Term};

struct MyGraph { store: Mutex }

type GraphArc = ResourceArc;

Чтобы сообщить Растлеру, что MyGraph это то, что можно использовать в качестве Resource:

fn on_load(env: Env, _info: Term) -> bool {
    rustler::resource!(MyGraph, env);
    true
}

Зная эти определения, мы можем написать new функция, которая выделяет новое хранилище данных и возвращает его дескриптор:

#[rustler::nif]
fn new() -> GraphArc {
    ResourceArc::new(
        MyGraph {
            store: Mutex::new(Store::new().unwrap()),
        }
    )
}

Что касается Эликсира, в fe_graph.ex:

defmodule FeGraph do
  use Rustler, otp_app: :myapp, crate: "fegraph"

  def new(), do: :erlang.nif_error(:nif_not_loaded)
end

На этом этапе мы можем протестировать iexи увидим, что заглушка Elixir, указанная выше, была заменена определенным нами Rust NIF, который мы можем запустить и который возвращает нам ссылку:

iex(1)> FeGraph.new
#Reference<0.2659174309.2607677441.115195>

Конечно, мы пока не можем делать что-нибудь с этим, но мы уже определяем хранилище данных в Rust и видим доказательства этого в Elixir; а за кулисами BEAM и Rustler берут на себя всю тяжелую работу за нас.

Как насчет простой функции для добавления данных в наш новый магазин? Это будет очень знакомо коду Elixir: ему нужно будет как принять, так и вернуть
GraphArc ручка. Мы также сделаем так, чтобы он принимал одну строку, которая будет использоваться для хранения всех трех частей тройки (обычно, конечно, мы используем разные строки для субъектной, предикатной и объектной частей тройки, но наше внимание здесь сосредоточено не на в SPARQL — нам просто нужно сохранить некоторые данные.)

#[rustler::nif]
fn set(state: GraphArc, iri: &str) -> GraphArc {
    let store = state.store.lock().unwrap();

    let ex = NamedNode::new(iri).unwrap();
    let quad =
        Quad::new(ex.clone(), ex.clone(), ex.clone(), GraphName::DefaultGraph);
    (*store).insert(&quad).unwrap();

    drop(store);

    state
}

Внутри функции мы можем использовать наш GraphArc аргумент состояния, чтобы получить доступ к хранилищу Oxigraph, которое мы создали еще в new функция. Получив его, мы можем добавить на график некоторые тестовые данные, как обычно, а затем вернуть неизмененные значения.
state.

Последняя часть головоломки — получить некоторые данные из нашего магазина. Вместо того, чтобы выполнять запрос (который потребует дополнительного изучения SPARQL), мы просто выгрузим всю базу данных и вернем ее в виде строки. Как и раньше, наша новая функция должна будет принять GraphArcно на этот раз мы вернем OwnedBinaryчто позволяет нам отправить двоичный файл обратно в BEAM, а затем умыть руки.

#[rustler::nif]
fn dump_db(state: GraphArc) -> OwnedBinary {
    let store = state.store.lock().unwrap();

    let mut buffer = Vec::new();
    (*store)
        .dump_graph(
            &mut buffer,
            GraphFormat::Turtle,
            GraphNameRef::DefaultGraph,
        )
        .unwrap();

    let mut result = OwnedBinary::new(buffer.len()).unwrap();
    result.as_mut_slice().copy_from_slice(&buffer);

    result
}

Большая часть этой функции связана с получением данных из Oxigraph в буфер, а затем из буфера в буфер. OwnedBinary; оболочка Rustler стала практически невидимой, на что я изначально и надеялся.

Теперь мы можем продемонстрировать выделение некоторой памяти в Rust, возврат дескриптора этой памяти, затем использование ее для хранения данных вне модели памяти BEAM и, наконец, получение данных обратно в мир Elixir:

iex(1)> db = FeGraph.new
#Reference<0.2749498138.3684302852.140448>
iex(2)> FeGraph.set(db, "http://foo.com")
#Reference<0.2749498138.3684302852.140448>
iex(3)> FeGraph.dump_db(db)
"   .\n"

Обратите внимание на важную часть; ссылка одинакова оба раза, несмотря на изменение данных, и мы нет сохранение его после set вызов; обычно нам нужно сделать что-то вроде db = FeGraph.set(db, "http://foo.com") вместо. Единственная причина set возвращает ссылку для удобного использования с оператором канала или аналогичным.

Заключение

Хотя показанный выше код пропускает большую часть обработки ошибок, надеюсь, понятно, насколько доступным Rustler делает его для связывания кода Rust с проектами Elixir таким образом, чтобы вы могли объединить сильные стороны обоих.

Rustler — потрясающее дополнение к экосистеме Elixir, открывающее гораздо больше возможностей, чем просто более быстрые вычисления. Возможность отказаться от стандартной модели памяти BEAM для определенных разделов кода может открыть двери для пользовательских хранилищ данных и других функций, которые обычно не подходят для Elixir, но при этом позволяет вам использовать возможности Phoenix для большая часть вашего приложения… и все это с практически бесшовной интеграцией.

2024-02-15 13:09:51


1708003236
#Лямбдафункции

Read more:  Принстон ошеломляет Миссури и достигает Sweet 16

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.