{"id":13774212,"url":"https://github.com/extism/haskell-pdk","last_synced_at":"2025-04-08T07:32:20.660Z","repository":{"id":139443962,"uuid":"572256076","full_name":"extism/haskell-pdk","owner":"extism","description":"Extism Plug-in development kit (PDK) for Haskell","archived":false,"fork":false,"pushed_at":"2025-03-21T20:47:17.000Z","size":72,"stargazers_count":8,"open_issues_count":1,"forks_count":1,"subscribers_count":4,"default_branch":"main","last_synced_at":"2025-03-21T21:28:49.546Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Haskell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/extism.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-11-29T22:01:30.000Z","updated_at":"2025-03-21T20:47:21.000Z","dependencies_parsed_at":"2023-11-29T19:47:54.871Z","dependency_job_id":"1b1c11e8-0511-4f13-93ed-fae53a19733d","html_url":"https://github.com/extism/haskell-pdk","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fhaskell-pdk","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fhaskell-pdk/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fhaskell-pdk/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/extism%2Fhaskell-pdk/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/extism","download_url":"https://codeload.github.com/extism/haskell-pdk/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247796295,"owners_count":20997545,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-08-03T17:01:24.712Z","updated_at":"2025-04-08T07:32:20.647Z","avatar_url":"https://github.com/extism.png","language":"Haskell","funding_links":[],"categories":["\u003ca name=\"extism\"\u003e\u003c/a\u003e[Extism](https://github.com/extism/extism) \u003csup\u003e[top⇈](#contents)\u003c/sup\u003e","Haskell"],"sub_categories":[],"readme":"# Extism Haskell PDK\n\nThis library can be used to write [Extism Plug-ins](https://extism.org/docs/concepts/plug-in) in Haskell.\n\nDocs are available on Hackage: [https://hackage.haskell.org/package/extism-pdk](https://hackage.haskell.org/package/extism-pdk)\n\n## Install\n\nMake sure you have [wasm32-wasi-ghc](https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta) installed, then generate an `Executable` project with cabal:\n\n```bash\ncabal init\n```\n\n**Note**: As of [aa2d85dc](https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta/-/commit/aa2d85dccbce5e18a9ce31ac92511dcdd9a95b6c) the Wasm tail-call\nproposal is enabled by default. Some Wasm runtimes, like the go-sdk, don't support this yet so it might be necesarry to pin ghc-wasm-meta to\n[ada3b8fa](https://gitlab.haskell.org/haskell-wasm/ghc-wasm-meta/-/commit/ada3b8fa0f763e4dccb2b1f6bbf2518bff2a7c6e), which seems to be the last commit to not\nrequire tail-calls.\n\nAdd the library from [Hackage](https://hackage.haskell.org/package/extism-pdk) to your cabal file:\n\n```bash\nbuild-depends: extism-pdk\n```\n\nWe will also need to add some additional ghc options to expose the correct functions:\n\n```\nghc-options:\n  -optl -Wl,--export=greet -optl -Wl,--export=hs_init -optl -Wl,--allow-undefined -no-hs-main -optl-mexec-model=reactor \n```\n\n## Getting Started\n\nThe goal of writing an [Extism plug-in](https://extism.org/docs/concepts/plug-in) is to compile your Haskell code to a Wasm module with exported functions that the host application can invoke. The first thing you should understand is creating an export. Let's write a simple program that exports a `greet` function which will take a name as a string and return a greeting string. \n\n```haskell\n{-# LANGUAGE DeriveDataTypeable #-}\n\nmodule Hello where\n\nimport Data.Maybe\nimport Extism.PDK\nimport Extism.PDK.JSON\n\ndefaultGreeting = \"Hello\"\n\ngreet g n =\n  output $ g ++ \", \" ++ n\n\ntesting = do\n  -- Get a name from the Extism runtime\n  name \u003c- inputString\n  -- Get  configured greeting\n  greeting \u003c- getConfig \"greeting\"\n  -- Greet the user, if no greeting is configured then \"Hello\" is used\n  greet (fromMaybe defaultGreeting greeting) name\n\nforeign export ccall \"greet\" testing :: IO ()\n```\n\nThis example also shows how to use the `getConfig` function to load runtime configuration values set by the host.\n\nDespite not needing any system access for this plugin, we will still compile it for `wasm32-wasi`, since there is no Haskell compiler targeting `wasm32-unknown-unknown`:\n\n```bash\nwasm32-wasi-cabal build\n```\n\nThis will put your compiled wasm somewhere in the `dist-newstyle` directory:\n\n```bash\ncp `find dist-newstyle -name example.wasm` .\n```\n\nWe can now test it using the [Extism CLI](https://github.com/extism/cli)'s `run`\ncommand:\n\n```bash\nextism call ./example.wasm greet --input \"Benjamin\"\n# =\u003e Hello, Benjamin!\n```\n\nConfigure a new greeting we can update the `greeting` config key using the [Extism CLI](https://github.com/extism/cli)'s `--config` option that lets you pass in `key=value` pairs:\n\n```bash\nextism call ./example.wasm greet --input \"Benjamin\" --config greeting=\"Hi there\"\n# =\u003e Hi there, Benjamin!\n```\n\n\u003e **Note**: We also have a web-based, plug-in tester called the [Extism Playground](https://playground.extism.org/)\n\n### More About Exports\n\nFor a function to be available from your Wasm plug-in, you will need to add a `foreign export`:\n\n```haskell\nforeign export ccall \"greet\" greet:: IO Int32\n```\n\nAnd there are some flags to make the function public on the linker side:\n\n```\nghc-options:\n    -optl -Wl,--export=greet -optl -Wl,--export=hs_init -optl -Wl,--allow-undefined -no-hs-main -optl-mexec-model=reactor \n```\n\nThis will export the `greet` function, the `hs_init` function and compile a reactor module instead of a command-style module.\n\n### Primitive Types\n\nA common thing you may want to do is pass some primitive Haskell data back and forth.\n\n```haskell\n-- Float\naddPi = do\n  -- Get float value\n  value \u003c- (input :: IO Float)\n  output $ value + 3.14\n  return 0\n\n-- Integers\nsum42 = do\n  value \u003c- (input :: IO Int)\n  output $ value + 42\n  return 0\n\n-- ByteString\nprocessBytes = do\n  bytes \u003c- inputByteString\n  -- process bytes here\n  output bytes\n  return 0\n\n-- String\nprocessString = do\n  s \u003c- inputString\n  output s\n  return 0\n```\n\n### Json\n\nWe provide a [JSON](https://hackage.haskell.org/package/extism-manifest-0.3.0/docs/Extism-JSON.html) class that allows you to pass JSON encoded values into \nand out of plug-in functions:\n\n```haskell\n{-# LANGUAGE DeriveDataTypeable #-}\n\nmodule Add where\nimport Extism.PDK\nimport Extism.PDK.JSON\n\ndata Add = Add\n  { a :: Int,\n    b :: Int\n  } deriving (Data)\n\ndata Sum = Sum { sum :: Int } deriving (Data)\n\nadd = do\n  value \u003c- input\n  output $ JSON $ Sum (a value + b value)\n  return 0\n\nforeign export ccall \"add\" add :: IO Int32\n```\n\n## Variables\n\nVariables are another key-value mechanism but it's a mutable data store that\nwill persist across function calls. These variables will persist as long as the\nhost has loaded and not freed the plug-in. You can use [getVar](https://hackage.haskell.org/package/extism-pdk-0.2.0.0/docs/Extism-PDK.html#v:getVar) and [setVar](https://hackage.haskell.org/package/extism-pdk/docs/Extism-PDK.html#v:setVar) to manipulate them.\n\n```haskell\ncount = do\n  c \u003c- fromMaybe 0 \u003c$\u003e getVar \"count\"\n  setVar \"count\" (c + 1)\n  output c\n  return 0\n```\n\n## Logging\n\nBecause Wasm modules by default do not have access to the system, printing to stdout won't work (unless you use WASI). Extism provides some simple logging macros that allow you to use the host application to log without having to give the plug-in permission to make syscalls:\n\n```haskell\nmodule Log where\nimport Extism.PDK\nlogStuff = do\n  logInfo \"Some info!\"\n  logWarn \"A warning!\"\n  logError \"An error!\" \n  return 0\nforeign export ccall \"logStuff\" logStuff:: IO Int32\n```\n\nFrom [Extism CLI](https://github.com/extism/cli):\n\n```bash\nextism call my_plugin.wasm logStuff --log-level=info\n2023/09/30 11:52:17 Some info!\n2023/09/30 11:52:17 A warning!\n2023/09/30 11:52:17 An error!\n```\n\n\u003e *Note*: From the CLI you need to pass a level with `--log-level`. If you are running the plug-in in your own host using one of our SDKs, you need to make sure that you call `set_log_file` to `\"stdout\"` or some file location.\n\n## HTTP\n\nSometimes it is useful to let a plug-in make HTTP calls.\n\n\u003e **Note**: See [Request](https://hackage.haskell.org/package/extism-pdk/docs/Extism-PDK-HTTP.html#t:Request) docs for more info on the request and response types:\n\n```haskell\n module HTTPGet where\n\nimport Data.Int\nimport Extism.PDK\nimport Extism.PDK.HTTP\nimport Extism.PDK.Memory\n\nhttpGet = do\n  -- Get JSON encoded request from host\n  JSON req \u003c- input\n  -- Send the request, get a 'Response'\n  res \u003c- sendRequest req (Nothing :: Maybe String)\n  -- Save response body to output\n  output $ responseData res\n  -- Return code\n  return 0\n\nforeign export ccall \"httpGet\" httpGet :: IO Int32\n```\n\n## Imports (Host Functions)\n\nLike any other code module, Wasm not only let's you export functions to the outside world, you can\nimport them too. Host Functions allow a plug-in to import functions defined in the host. For example,\nif you host application is written in Python, it can pass a Python function down to your Haskell plug-in\nwhere you can invoke it.\n\nThis topic can get fairly complicated and we have not yet fully abstracted the Wasm knowledge you need\nto do this correctly. So we recommend reading out [concept doc on Host Functions](https://extism.org/docs/concepts/host-functions) before you get started.\n\n### A Simple Example\n\nHost functions in the Haskell PDK require C stubs to import a function from a particular namespace:\n\n```c\n#include \u003cstdint.h\u003e\n\n#define IMPORT(a, b) __attribute__((import_module(a), import_name(b)))\nIMPORT(\"extism:host/user\", \"a_python_func\")\nuint64_t a_python_func_impl(uint64_t input);\n\nuint64_t a_python_func(uint64_t input) {\n  return a_python_func_impl(input);\n}\n```\n\nThis C file should be added to the `extra-source-files` and `c-sources` fields in your cabal file.\n\nFrom there we can use `foreign import ccall` to call our stub:\n\n```haskell\nimport Extism.PDK.Memory\nimport Extism.PDK\n\nforeign import ccall \"a_python_func\" aPythonFunc :: Word64 -\u003e IO Word64\n\nhelloFromPython :: String -\u003e IO String\nhelloFromPython = do\n  s' \u003c- allocString \"Hello!\"\n  resOffset \u003c- aPythonFunc (memoryOffset s')\n  resMem \u003c- findMemory resOffset\n  logInfo =\u003c\u003c loadString resMem\n  return 0\n\nforeign export ccall \"helloFromPython\" helloFromPython :: IO Int32\n```\n\nTo call this function, we write our input string into memory using `allocString` and call the function with the returned memory handle. We then have\nto load the result string from memory to access it from our Haskell program.\n\n### Testing it out\n\nWe can't really test this from the Extism CLI as something must provide the implementation. So let's\nwrite out the Python side here. Check out the [docs for Host SDKs](https://extism.org/docs/concepts/host-sdk) to implement a host function in a language of your choice.\n\n```python\nfrom extism import host_fn, Plugin\n\n@host_fn()\ndef a_python_func(input: str) -\u003e str:\n    # just printing this out to prove we're in Python land\n    print(\"Hello from Python!\")\n\n    # let's just add \"!\" to the input string\n    # but you could imagine here we could add some\n    # applicaiton code like query or manipulate the database\n    # or our application APIs\n    return input + \"!\"\n```\n\nNow when we load the plug-in we pass the host function:\n \n```python\nmanifest = {\"wasm\": [{\"path\": \"/path/to/plugin.wasm\"}]}\nplugin = Plugin(manifest, functions=[a_python_func], wasi=True)\nresult = plugin.call('helloFromPython', b'').decode('utf-8')\nprint(result)\n```\n\n```bash\npython3 app.py\n# =\u003e Hello from Python!\n# =\u003e An argument to send to Python!\n```\n\n### Reach Out!\n\nHave a question or just want to drop in and say hi? [Hop on the Discord](https://extism.org/discord)!\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextism%2Fhaskell-pdk","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fextism%2Fhaskell-pdk","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fextism%2Fhaskell-pdk/lists"}