hsjuiz: Write juiz processes and containers in Haskell

[ ffi, library, robotics ] [ Propose Tags ] [ Report a vulnerability ]

Write Process Container Component plugins of juiz (middleware for robots and IoT) in Haskell. A plugin is a shared library (a cabal foreign-library) that juiz loads as language: "haskell". Values are passed through the C functions of libjuiz_sdk. . Building a plugin needs libjuiz_sdk of the same version as juiz. Download it from GitHub Releases (juiz_sdk-<version>-<target>.tar.gz, or scripts/install-juiz-sdk.sh in the juiz repository) and add it to cabal.project: . > package hsjuiz > extra-lib-dirs: sdk/lib > ghc-options: -optl-Wl,-rpath,sdk/lib . juiz (ロボット・IoT 向けミドルウェア) の Process / Container を Haskell で書くためのライブラリ。 使い方は README を参照。


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

  • No Candidates
Versions [RSS] 0.2.6, 0.2.7
Change log CHANGELOG.md
Dependencies base (>=4.18 && <5), bytestring (>=0.11 && <0.13), containers (>=0.6 && <0.9), template-haskell (>=2.19 && <2.26), text (>=2.0 && <2.3) [details]
Tested with ghc >=9.6 && <9.7
License (MIT OR Apache-2.0)[multiple license files]
Author ysuga
Maintainer ysuga
Uploaded by sugarsweetrobotics at 2026-11-12T01:55:56Z
Category Robotics, FFI
Home page https://github.com/OpenJUIZ/juiz/tree/main/bindings/hsjuiz
Bug tracker https://github.com/OpenJUIZ/juiz/issues
Source repo head: git clone https://github.com/OpenJUIZ/juiz.git(bindings/hsjuiz)
Distributions
Downloads 5 total (5 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for hsjuiz-0.2.7

[back to package description]

hsjuiz

juiz の Process / Container を Haskell で書くためのライブラリです(Linux / macOS / Windows、GHC 9.6 以降。Windows の注意は「Windows」)。

Haskell の関数を cabal の foreign-library(共有ライブラリ)として公開し、juiz が設定ファイルの language: "haskell" のプラグインとして読み込みます。値の受け渡しには libjuiz_sdk の C 関数 (bindings/cjuiz/include/juiz_sdk.h)を使います。

Process を書く

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module IncrementProcess () where

import Juiz

manifest :: ProcessManifest
manifest = processManifest "increment_process_hs"
  & description "Adds 1 to arg1."
  & addIntArg "arg1" "value to be incremented" 1

increment :: Int64 -> IO Int64
increment arg1 = pure (arg1 + 1)

juizProcess 'increment 'manifest
  • processManifest に渡す名前が、設定ファイルの type_name とライブラリの名前になります。
  • 引数は addBoolArg / addIntArg / addFloatArg / addStringArg / addArrayArg / addObjectArg で足します。 足した順が関数の引数の順 です。マニフェストの引数の数と関数の引数の数が違うと、juiz が読み込むときにエラーになります。
  • juizProcess 'func 'manifest が、juiz が探す C の関数(manifest_entry_point、process_factory_entry_point など)を生成します。
  • 関数の結果は IO r でも、IO の付かない r でもかまいません。型は具体的に書いてください(多相な関数は使えません)。
  • 文字列の引数は OverloadedStrings を付けて Text で書きます。

使える型

引数の型は FromCapsule、結果の型は ToCapsule のインスタンスにします。用意してあるのは次の型です。

Haskell juiz の値
Bool 真偽値
Int64, Int, Int8〜Int32, Word8〜Word32 整数(範囲外ならエラー)
Word64 0 以上の整数
Double, Float 小数(整数も受け取れる)
Text 文字列
ByteString base64 の文字列
[a] 配列
Map Text a オブジェクト
Maybe a null なら Nothing
() null(結果のみ)
Value どんな値でも(JNull, JBool, JInt, JUInt, JFloat, JString, JArray, JObject)

オブジェクトを返すときは object ["x" .= (1 :: Int64), "name" .= ("abc" :: Text)] のように書けます。 自分の型は fromValue :: Value -> Either Text a / toValue :: a -> Value を書いてインスタンスにします。

型付きの引数・戻り値(IDL の型)

juiz の IDL(.jidl)で定義した型を使うと、引数・戻り値が型付きになります(manifest に型の情報が載り、 ほかの言語の Process と同じ型で受け渡しでき、接続のときに型が検査されます)。juiz-idl -g haskell で Haskell のモジュールを作り(Template Haskell は使いません)、cabal の hs-source-dirs と other-modules に足します。

juiz-idl -g haskell -o gen idl/geometry.jidl   # gen/Robot/Geometry.hs など
import Juiz
import Robot.Geometry   -- Pose2D (..)、Point2D (..)、MotorMode (..) など

moveManifest :: ProcessManifest
moveManifest = processManifest "pose_move_hs"
  & addTypedArgNoDefault (Proxy :: Proxy Pose2D) "pose" "current pose"
  & addFloatArg "dx" "" 1.0

poseMove :: Pose2D -> Double -> Pose2D
poseMove pose dx = pose { pose2DPosition = (pose2DPosition pose) { point2DX = point2DX (pose2DPosition pose) + dx } }

juizProcess 'poseMove 'moveManifest
  • 型付きの引数は addTypedArg 名前 説明 既定値(既定値はその型の値)、addTypedArgNoDefault (Proxy :: Proxy T) 名前 説明(既定値なし)、 addTypedArgValue (Proxy :: Proxy T) 名前 説明 辞書の既定値 で足します。関数の引数が型付きの型なのにマニフェストが addObjectArg などの型なしの引数だと、juiz が読み込むときにエラーになります。
  • 関数の結果が型付きの型なら、戻り値の型(output_schema)は自動で載ります(returns (Proxy :: Proxy T) で書いても同じ)。
  • 生成した record のフィールドの関数は「型名 + フィールド名」(pose2DPosition、frame_id なら pose2DFrameId)、 列挙の構築子は「型名 + 記号」(MotorModeIdle)です。すべてのフィールドに既定値がある record には default<型名> もあります。
  • 引数は juiz が型で読んでから渡します(足りないフィールドは既定値で埋め、型にないキーは無視)。型に合わない値は JUIZ_VALUE_DOES_NOT_MATCH_TYPE_ERROR(-702)になります。戻り値は辞書にして返します。
  • IDL を使わずに手で書いた型も、Avro のスキーマから unsafeTypeInfo で型の情報を作り、JuizType・FromCapsule・ToCapsule のインスタンス(fromCapsuleType / toCapsuleType で型の情報を返す)を書けば型付きにできます(Juiz.Type を参照)。

詳しくは juiz の docs/spec/datatype.md §22 にあります。

画像

画像は Image(Juiz.Image)で受け取り、返します。マニフェストでは addImageArg で引数を足します。 juiz の中では Rust の画像になるので、ほかの言語の Process と接続でき、IPC のブローカー越しにも生バイトのまま渡ります。

invertManifest :: ProcessManifest
invertManifest = processManifest "image_invert_hs"
  & addImageArg "img" "image to invert"

invert :: Image -> Image
invert img = img { imageData = BS.map (255 -) (imageData img) }   -- 8 bit の形式のとき

juizProcess 'invert 'invertManifest
  • Image は imageWidth・imageHeight・imageFormat・imageStride(1 行のバイト数)・imageData(ByteString)を持ちます。 受け取った画像は行の余白がありません。新しく作るときは newImage 幅 高さ 形式 バイト列 を使います(バイト数が合わなければ Left)。
  • 形式 PixelFormat は L8・LA8・RGB8・RGBA8・L16・LA16・RGB16・RGBA16・RGB32F・RGBA32F と、 返すときだけ使える BGR8・BGRA8(juiz が赤と青を入れ替えて RGB8・RGBA8 にします)。16 bit・f32 の画素はホストのバイト順です。
  • 画像は辞書の中([Image] や record のフィールド)には入れられません。

詳しくは juiz の docs/spec/large-data.md §10 にあります。

エラー

関数の中の例外は C の呼び出し元(juiz)に出さず、エラーコードに変えて返します。内容は標準エラー出力に書きます。

  • 引数が無い・型が合わない: JUIZ_VALUE_NOT_FOUND_ERROR(-102)・JUIZ_VALUE_CONVERTER_ERROR(-103)
  • throwJuiz code "説明" で投げた JuizError: そのコード
  • それ以外の例外(error など): JUIZ_PROCESS_FUNCTION_NULL_OPT_RETURNED(-501)

どの場合も juiz の呼び出しはエラーになり、プロセスは落ちません。

Container を書く

Container は状態を持つもので、ContainerProcess はその状態を第 1 引数に受け取る関数です。 juiz の設定ファイルでは、Container と ContainerProcess はそれぞれ別の共有ライブラリになります (1 つのライブラリにまとめるときは Component にします)。

状態の型は、Container と ContainerProcess のどちらのライブラリからも同じモジュールを使ってそろえます。

-- ExampleContainerState.hs (両方のライブラリの other-modules に入れる)
module ExampleContainerState (ExampleContainer (..)) where

import Data.IORef (IORef)
import Data.Int (Int64)

newtype ExampleContainer = ExampleContainer (IORef Int64)
-- ExampleContainer.hs: Container (example_container_hs)
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module ExampleContainer () where

import Juiz
import ExampleContainerState

manifest :: ContainerManifest
manifest = containerManifest "example_container_hs"
  & description "Holds an integer value."
  & addIntArg "value" "initial value" 0

create :: Int64 -> IO ExampleContainer
create value = ExampleContainer <$> newIORef value

juizContainer 'create 'manifest
-- ExampleContainerIncrement.hs: ContainerProcess (example_container_hs_increment)
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module ExampleContainerIncrement () where

import Juiz
import ExampleContainerState

manifest :: ProcessManifest
manifest = containerProcessManifest "example_container_hs_increment" "example_container_hs"
  & description "Adds arg0 to the value of the container."
  & addIntArg "arg0" "value to be added" 1

increment :: ExampleContainer -> Int64 -> IO Int64
increment (ExampleContainer ref) arg0 = atomicModifyIORef' ref (\v -> (v + arg0, v + arg0))

juizContainerProcess 'increment 'manifest
  • containerManifest の引数(addIntArg など)は、Container を作る関数の引数 になります。 設定ファイルの containers に書いた値を受け取り、書いていなければ既定値になります。
  • Container を作る関数の結果(IO s または s)が状態です。書き換える値は IORef などに入れてください (Juiz から IORef / newIORef / readIORef / writeIORef / modifyIORef' / atomicModifyIORef' も使えます)。 juiz は同じ Container の ContainerProcess を同時には呼びません。
  • containerProcessManifest "型名" "Container の型名" で ContainerProcess のマニフェストを作ります。 引数は第 1 引数(状態)の後に続く引数の順です。
  • 状態は StablePtr に包んで juiz に渡します。Container を削除すると解放され、GC に回収されます。 デバイスを閉じるなど、削除のときにしたいことがあれば juizContainerWithDestroy 'create 'destroy 'manifest (destroy :: s -> IO ())を使います。
  • ContainerProcess の状態の型と Container の状態の型が違う(別の型の Haskell の Container や、C++ など別の言語の Container に結びつけた)ときは、呼び出しが JUIZ_HASKELL_CONTAINER_TYPE_ERROR(-602)のエラーになります。 型は Typeable で得たモジュール名つきの型名(ExampleContainerState.ExampleContainer など)で比べるので、両方のライブラリで同じモジュールの同じ型を使ってください(Container と ContainerProcess はライブラリごとにパッケージの識別子が変わることがあるため、cast は使っていません)。

設定ファイルは C++ の Container と同じ形です。

plugins:
  container_factories:
    example_container_hs:
      language: "haskell"
      path: "./target"
      processes:
        example_container_hs_increment:
          path: "./target"
containers:
  - type_name: "example_container_hs"
    name: "hscont0"
    value: 15
    processes:
      - type_name: "example_container_hs_increment"
        name: "hsinc0"

ContainerPose を読み書きする

ContainerProcess の中から、その Container の位置姿勢(ContainerPose、docs/spec/transformations.md §12.3)を Juiz.Pose の関数で読み書きできます(Juiz から使えます)。

move :: IORef Int64 -> Double -> IO Value
move _ dx = do
  (p, _) <- containerPose "map"                          -- map から見たこの Container の位置姿勢
  let (x, y, z) = cpPosition p
  r <- setContainerPose "map" p { cpPosition = (x + dx, y, z) }   -- 親 → この Container の Transform を書き換える
  pure (poseStampedToValue "map" r)                      -- juiz_std.geometry.PoseStamped の辞書
  • ContainerProcess の関数は Container の状態だけを受け取るので、読み書きの相手は「今呼ばれている ContainerProcess の Container」です。juiz が呼んだスレッドの上(関数の中)でだけ使えます。forkIO した先や関数から戻った後では juizTransformationsNotAttachedError(-901)の JuizError になります。
  • 四元数の並びは (x, y, z, w)。cpOrientation が (0, 0, 0, 0) のように長さ 0 なら juizInvalidPoseValueError(-904)。
  • 共通の Anchor がない基準は juizNoCommonAnchorError(-902)、書けない基準(根の Anchor・自分・子孫)は juizPoseNotWritableError(-903)。捕まえなければ、エントリポイントがそのコードを juiz に返します。

Component にまとめる

Component は、1 つのライブラリに複数の Process / Container / ContainerProcess をまとめたものです(C++ の COMPONENT_PROCESS_FACTORY などと同じ仕組み)。ライブラリが一つで済み、Container と ContainerProcess が 同じライブラリに入るので、状態の型を別のモジュールに分ける必要もありません。

要素の関数とマニフェストは、単体で公開するときと同じものを使います。juizComponent に Component の マニフェストと要素の一覧を渡します。

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
module ExampleComponent () where

import Juiz

component :: ComponentManifest
component = componentManifest "example_component_hs"
  & description "Haskell component example."

incrementManifest :: ProcessManifest
incrementManifest = processManifest "example_component_hs_increment"
  & addIntArg "arg0" "value to be incremented" 3

increment :: Int64 -> Int64
increment arg0 = arg0 + 1

newtype Counter = Counter (IORef Int64)

counterManifest :: ContainerManifest
counterManifest = containerManifest "example_component_hs_counter"
  & addIntArg "value" "initial value" 0

createCounter :: Int64 -> IO Counter
createCounter value = Counter <$> newIORef value

counterGetManifest :: ProcessManifest
counterGetManifest = containerProcessManifest "example_component_hs_counter_get" "example_component_hs_counter"

counterGet :: Counter -> IO Int64
counterGet (Counter ref) = readIORef ref

juizComponent 'component
  [ componentProcess 'increment 'incrementManifest
  , componentContainer 'createCounter 'counterManifest
  , componentContainerProcess 'counterGet 'counterGetManifest
  ]
  • 要素は componentProcess / componentContainer / componentContainerWithDestroy / componentContainerProcess で 書きます。引数は juizProcess などと同じです。別のモジュールに書いた関数とマニフェストも(公開して import すれば)使えます。
  • componentManifest に渡す名前が、設定ファイルの components に書く名前とライブラリの名前になります。
  • juizComponent は C++ と同じ名前の C の関数を生成します: マニフェストを返す component_manifest_entry_point、 要素ごとの <関数名>_factory_entry_point(Container は <関数名>_factory_destroy_entry_point も)と <関数名>_manifest_entry_point。要素のマニフェストの factory は <関数名>_factory に付け直すので、自分で書く必要はありません。 そのため 関数名は Component の中で重ならないように してください(重なるとコンパイルエラーになります)。
  • ContainerProcess の Container は同じ Component に入れます。Component に無い Container の ContainerProcess を入れたり、 型名が重なっていたりすると、juiz が読み込むときにエラーになります。

設定ファイルでは plugins の components に書き、Process や Container は要素の型名で作ります。

plugins:
  components:
    example_component_hs:
      language: "haskell"
      path: "./target"
processes:
  - type_name: "example_component_hs_increment"
    name: "hsinc0"
containers:
  - type_name: "example_component_hs_counter"
    name: "hscounter0"
    value: 15
    processes:
      - type_name: "example_component_hs_counter_get"
        name: "hsget0"

一つの juiz で複数の Haskell のライブラリ(単体の Process や Container、Component)を読み込むこともできます。

ビルドする

Process / Container / ContainerProcess / Component ごとに foreign-library を一つ作ります。-threaded は必須です(juiz は Process をワーカースレッドから呼ぶため。 付け忘れると、juiz が読み込むときに juiz_hs_init が JUIZ_HASKELL_NOT_THREADED_RTS_ERROR(-601)を返してエラーになります)。

foreign-library increment_process_hs
    type:             native-shared
    default-language: Haskell2010
    hs-source-dirs:   src
    other-modules:    IncrementProcess
    build-depends:    base, hsjuiz
    ghc-options:      -threaded

libjuiz_sdk と hsjuiz を用意する

ビルドには、使う juiz と 同じ版 の libjuiz_sdk が必要です。版の違う libjuiz_sdk でビルドしたライブラリは、juiz が読み込むときにエラーにします。

libjuiz_sdk は、v0.0.26 から GitHub Releases に OS・CPU ごとのアーカイブ (juiz_sdk-<版>-<ターゲット名>.tar.gz、Linux x86_64・aarch64 と macOS arm64・x86_64)を置いています。 scripts/install-juiz-sdk.sh で取得して ~/.juiz/sdk/<版> に展開できます。

curl -fsSL https://raw.githubusercontent.com/OpenJUIZ/juiz/main/scripts/install-juiz-sdk.sh | sh -s -- 0.2.7

それより前の版では、juiz のソースを同じ版のタグで取得し、cargo build --release -p juiz_sdk でできる target/release の libjuiz_sdk を使います。

hsjuiz は v0.2.6 から Hackage に公開しています。自分のパッケージの .cabal の build-depends に hsjuiz ==<版>.* のように juiz と同じ版を書き、cabal.project で hsjuiz に libjuiz_sdk の場所を渡します。 ghc-options の rpath は、Template Haskell(juizProcess など)を実行するときに GHC が hsjuiz を読み込むのに必要です。 できたライブラリにも libjuiz_sdk の場所が rpath として入ります。

packages: .

package hsjuiz
  extra-lib-dirs: /home/me/.juiz/sdk/0.2.7/lib
  ghc-options: -optl-Wl,-rpath,/home/me/.juiz/sdk/0.2.7/lib
cabal update
cabal build all

Hackage に無い版(0.2.5 以前)や、未公開の変更を試すときは、juiz のソースを同じ版のタグ(または枝)で取得し、 その bindings/hsjuiz を cabal.project の packages: に足します。

packages: . /home/me/juiz-0.2.7/bindings/hsjuiz
git clone --depth 1 --branch v0.2.7 https://github.com/OpenJUIZ/juiz.git /home/me/juiz-0.2.7

juiz のリポジトリの中でビルドするときは、cargo build でできる target/debug の libjuiz_sdk を --extra-lib-dirs で渡します(examples/haskell/build.sh がこの方法です)。

cabal build all --extra-lib-dirs=<juiz>/target/debug --ghc-options="-optl-Wl,-rpath,<juiz>/target/debug"

macOS で、選んでいる Xcode より新しい Command Line Tools が入っている(たとえば Xcode 26 に Command Line Tools 27 の MacOSX27.0.sdk)と、リンクが tapi error: malformed file ... unknown architecture arm64e.x1-macos で失敗します。 GHC はリンクのときに Command Line Tools の SDK(/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk)を -L で渡すので、 Xcode の古いリンカが新しい SDK の .tbd を読めないためです(issue #143)。Command Line Tools のリンカを使えば通ります。

DEVELOPER_DIR=/Library/Developer/CommandLineTools cabal build all ...

Xcode を Command Line Tools と同じ版に上げても直ります。examples/haskell/build.sh はこの組み合わせを見つけると自動で DEVELOPER_DIR を切り替えます。

できた lib<名前>.so(macOS は .dylib)を、設定ファイルの path のディレクトリに置きます。

plugins:
  process_factories:
    increment_process_hs:
      language: "haskell"
      path: "./target"
  • RTS(GHC のランタイム)は GHC の共有ライブラリで、同じプロセスに読み込んだ Haskell のライブラリすべてで共有されます。 一つの juiz で読み込む Haskell のライブラリは、すべて同じ GHC でビルドしてください。(Windows は違います。下の「Windows」)
  • RTS の初期化(juiz_hs_init)は hsjuiz が用意しているので、自分で書く必要はありません。 GHC は hs_exit の後に初期化し直せないため、hsjuiz は hs_exit を呼ばず、juiz_hs_exit も用意していません。
  • hsjuiz の juiz_hs_init は、RTS を初期化する前に setKeepCAFs() を呼び、CAF(トップレベルの値)を GC で 回収しないようにしています。2 つ目以降に開いたライブラリの foreign export が GC の根にならず、 エントリポイントが回収されてプロセスが落ちるのを防ぐためです(issue #133)。

例は examples/haskell にあります。

Windows

Windows(GHC 9.6、x86_64)では、できるライブラリは lib の付かない <名前>.dll です。Linux / macOS と次の点が違います(issue #137)。

  • Windows の GHC は Haskell の共有ライブラリを持たないので、RTS と依存するパッケージを DLL に静的に取り込みます。 foreign-library ごとに options: standalone を書きます(common に書いても cabal は受け付けません)。

  • 公開するシンボルを .def ファイルに並べ、mod-def-file で渡します。書かないと、リンカがすべてのシンボルを公開しようとして DLL が公開できる数(65535)を超えるか、GHC がモジュールをアーカイブにするためにプラグインのコードが DLL に入りません。 並べるのは juiz_hs_init と、juiz が探す関数(manifest_entry_point、process_factory_entry_point など。 bindings/cjuiz/README.md の「プラグインが公開する関数」)です。Component では component_manifest_entry_point と、 componentProcess 'func ごとの func_manifest_entry_point / func_factory_entry_point (Container は func_factory_destroy_entry_point も)です。

    foreign-library increment_process_hs
        type:             native-shared
        if os(windows)
            options:      standalone
            mod-def-file: increment_process_hs.def
        ...
    
    EXPORTS
        juiz_hs_init
        manifest_entry_point
        process_factory_entry_point
    
  • Rust が作る libjuiz_sdk のインポートライブラリは juiz_sdk.dll.lib という名前で、GHC の -ljuiz_sdk は見つけられないので、 juiz_sdk.lib という名前で複製して --extra-lib-dirs で渡します。Windows に rpath は無く、juiz_sdk.dll は juiz の実行ファイルと 同じディレクトリ(または PATH)から読み込まれます。

  • DLL ごとに RTS が別になります。 そのため、ある DLL の Container を別の DLL の ContainerProcess に渡すことはできません (JUIZ_HASKELL_CONTAINER_TYPE_ERROR(-602)になります)。Container とその ContainerProcess は juizComponent で 一つの Component(一つの DLL)にまとめてください。別の GHC でビルドした DLL を同じ juiz に読み込むことはできます。 別々の DLL にできるようにすることは issue #348 で検討しています。

  • -fno-PIC を付けてください。 cabal は foreign-library を -fPIC でコンパイルしますが、Windows の -fPIC の ジャンプテーブルは飛び先の差を 32 bit しか書けません。そのため、構成子が 7 個以上ある型の値で枝分かれする case (deriving Show の show を含む)を実行すると、プロセスが落ちます(issue #262、GHC の不具合 ghc#24016)。-fno-PIC では表が絶対番地になり、DLL を読み込むときに正しく直されます。 options: standalone と違い、ghc-options は common に書けます。

    common plugin
        if os(windows)
            ghc-options:  -fno-PIC
    
  • プロジェクトは浅い場所に置いてください。 cabal の foreign-library の中間ファイルはライブラリ名を 3 回含むパス (dist-newstyle/build/…/f/<名前>/build/<名前>/<名前>-tmp/…)に作られ、深い場所では 260 文字を超えて GHC のファイルのコピーが does not exist で失敗します。OS の長いパスの設定(LongPathsEnabled)を有効にしても同じです(issue #279)。

examples/haskell/build.sh は Git Bash(MSYS2)で動き、上の手順を行います。

Template Haskell を使わずに書く

juizProcess が生成するものは、Juiz の manifestEntryPoint と processEntryPoint を使って手でも書けます。 Process の関数の結果は IO r にしてください。

{-# LANGUAGE OverloadedStrings #-}
module IncrementProcess () where

import Juiz

manifest :: ProcessManifest
manifest = processManifest "increment_process_hs"
  & description "Adds 1 to arg1."
  & addIntArg "arg1" "value to be incremented" 1

increment :: Int64 -> IO Int64
increment arg1 = pure (arg1 + 1)

hsManifest :: ManifestFunction
hsManifest = manifestEntryPoint manifest increment

hsProcess :: ProcessFunction
hsProcess = processEntryPoint manifest increment

hsFactory :: ProcessFactoryFunction
hsFactory = pure p_hsProcess

foreign export ccall "manifest_entry_point" hsManifest :: ManifestFunction
-- Process の関数そのもの。名前はほかのライブラリと重ならないものにする
foreign export ccall "increment_process_hs_process_entry_point" hsProcess :: ProcessFunction
foreign import ccall "&increment_process_hs_process_entry_point" p_hsProcess :: FunPtr ProcessFunction
foreign export ccall "process_factory_entry_point" hsFactory :: ProcessFactoryFunction

Container と ContainerProcess は containerManifestEntryPoint / containerCreateEntryPoint / containerDestroyEntryPoint と containerProcessManifestEntryPoint / containerProcessEntryPoint で同じように書けます。

hsManifest :: ManifestFunction
hsManifest = containerManifestEntryPoint manifest create
hsCreate :: ContainerCreateFunction
hsCreate = containerCreateEntryPoint manifest create
hsDestroy :: ContainerDestroyFunction
hsDestroy = containerDestroyEntryPoint manifest
hsFactory :: ContainerFactoryFunction
hsFactory = pure p_hsCreate

foreign export ccall "manifest_entry_point" hsManifest :: ManifestFunction
foreign export ccall "example_container_hs_create_entry_point" hsCreate :: ContainerCreateFunction
foreign import ccall "&example_container_hs_create_entry_point" p_hsCreate :: FunPtr ContainerCreateFunction
foreign export ccall "container_factory_entry_point" hsFactory :: ContainerFactoryFunction
foreign export ccall "container_factory_destroy_entry_point" hsDestroy :: ContainerDestroyFunction

Component は、要素ごとに上と同じ関数を <関数名>_factory_entry_point などの名前で公開し、 マニフェストの factory をその名前(<関数名>_factory)にしてから、componentManifestEntryPoint で component_manifest_entry_point を書きます。processPart などは、要素のマニフェストと関数の引数の数を確かめるために使います。

incrementManifest' :: ProcessManifest
incrementManifest' = incrementManifest & factory "increment_factory"

hsComponentManifest :: ManifestFunction
hsComponentManifest = componentManifestEntryPoint component
  [ processPart incrementManifest' increment
  , containerPart counterManifest' createCounter
  , containerProcessPart counterGetManifest' counterGet
  ]

foreign export ccall "component_manifest_entry_point" hsComponentManifest :: ManifestFunction
-- increment_factory_entry_point、createCounter の factory の entry_point と destroy_entry_point なども同じように公開する

公開する関数の決まりは bindings/cjuiz/README.md にあります。

ライセンス

MIT OR Apache-2.0