wireform-yaml
wireform-yaml implements YAML 1.2 read and write paths for Haskell
applications. Configuration files, CI manifests, and Kubernetes-style multi-doc
streams all map cleanly onto typed records via Generic, while the value layer
still exposes anchors, aliases, tags, and both block and flow styles when you
need full YAML expressiveness. The decoder and emitter are validated against the
upstream yaml-test-suite with 100%
conformance.
Key features
Section titled “Key features”| Capability | Why it matters |
|---|---|
deriveYAML Template Haskell deriver | Derive config types with wireform-derive annotations; Generic defaults work for simple cases |
| Block and flow styles | Human-readable block output; compact flow for inline structures |
| Anchors and aliases | Preserve shared references and cyclic graphs in the value layer |
| Tags | Explicit scalar typing (!!int, application-specific tags) |
| Literal and folded scalars | Round-trip multiline strings (| and >) |
| Multi-document streams | --- separated documents for kubectl-style files |
| YAML 1.2 core schema | Plain scalars that look like bools or numbers stay quoted on encode |
Basic usage
Section titled “Basic usage”Typed records
Section titled “Typed records”Derive codecs with the Template Haskell deriver and use encodeYAML /
decodeYAML for the common case.
{-# LANGUAGE DeriveGeneric #-}{-# LANGUAGE TemplateHaskell #-}{-# LANGUAGE OverloadedStrings #-}import GHC.Generics (Generic)import Data.Text (Text)import YAML.Class (ToYAML, FromYAML, encodeYAML, decodeYAML)import YAML.Derive (deriveYAML)
data Server = Server { host :: !Text , port :: !Int , tls :: !Bool } deriving stock (Generic)
$(deriveYAML ''Server)
loadConfig :: Text -> Either String ServerloadConfig = decodeYAMLFor simple cases with no wire-format customization, Generic defaults also
work: add deriving Generic and declare empty instance ToYAML Server and
instance FromYAML Server declarations.
Field naming follows the same Wireform.Derive annotation vocabulary as other
wireform formats (rename, omitEmpty, and friends via YAML.Derive).
Value-level API for anchors and tags
Section titled “Value-level API for anchors and tags”When the shape is dynamic or you must preserve YAML-specific features, work in
YAML.Value and encode with YAML.Encode.
{-# LANGUAGE OverloadedStrings #-}import Data.Text (Text)import qualified YAML.Decode as YDimport qualified YAML.Encode as YE
roundtripAnchors :: Text -> Either String TextroundtripAnchors doc = do val <- YD.decode doc pure (YE.encode val)Multi-document streams
Section titled “Multi-document streams”Kubernetes and other tools emit several YAML documents in one file. Decode the
stream as a YAML.Value.Stream, or encode many values with encodeStream.
{-# LANGUAGE OverloadedStrings #-}import qualified Data.Vector as Vimport YAML.Encode (encodeStream)import YAML.Value (Document(..), Stream(..), mapping, string)
writeStream :: Stream -> TextwriteStream = encodeStream
exampleStream :: StreamexampleStream = Stream $ V.fromList [ Document True False (mapping [(string "apiVersion", string "v1")]) , Document True False (mapping [(string "kind", string "ConfigMap")]) ]The emitter chooses block style by default and falls back to flow for empty
containers. Output is round-trippable: encode followed by decode recovers
the same Value.
Performance
Section titled “Performance”wireform-yaml is a pure Haskell parser that consistently outperforms the C libyaml bindings by 3-32x across all input sizes, making it one of the fastest YAML parsers in any language. Against HsYAML (the other pure Haskell option), it is 244-408x faster.
wireform-yaml vs libyaml (C bindings via Hackage yaml package)
Section titled “wireform-yaml vs libyaml (C bindings via Hackage yaml package)”| Operation | wireform-yaml | yaml (libyaml) | ratio |
|---|---|---|---|
| tiny | 0.44 µs | 6.14 µs | 13.95x |
| small | 6.73 µs | 34.6 µs | 5.15x |
| flow | 7.51 µs | 44.2 µs | 5.88x |
| literal | 4.71 µs | 11.2 µs | 2.38x |
| big | 37.5 µs | 122 µs | 3.26x |
Last run 2026-11-02 14:35:47 UTC. ghc-9.10.3 on darwin-aarch64, criterion 1.6.5.
wireform-yaml vs HsYAML (pure Haskell)
Section titled “wireform-yaml vs HsYAML (pure Haskell)”| Operation | wireform-yaml | HsYAML | ratio |
|---|---|---|---|
| tiny | 0.44 µs | 119 µs | 270.86x |
| small | 6.73 µs | 1068 µs | 158.75x |
| flow | 7.51 µs | 1322 µs | 176.03x |
| literal | 4.71 µs | 1504 µs | 319.30x |
| big | 37.5 µs | 5027 µs | 133.95x |
Last run 2026-11-02 14:35:47 UTC. ghc-9.10.3 on darwin-aarch64, criterion 1.6.5.
Escaped strings and derived records
Section titled “Escaped strings and derived records”| Operation | wireform-yaml | yaml (libyaml) | ratio |
|---|---|---|---|
| escaped decode | 7.12 µs | 13.6 µs | 1.91x |
| record decode | 1.85 µs | 11.9 µs | 6.43x |
| record encode | 1.25 µs | 5.36 µs | 4.29x |
Last run 2026-11-02 14:35:47 UTC. ghc-9.10.3 on darwin-aarch64, criterion 1.6.5.
The record fixture uses the actual deriveYAML output. Every benchmark fixture
is checked for successful decoding before timing; the HsYAML comparison forces
the parsed contents as well as the outer document list.
ASCII validation scans eight bytes at a time and skips line normalization when there are no carriage returns. Unicode and unusual controls retain lexical validation. Literal scalars measure their final chomped length and copy their UTF-8 lines into one exact buffer, so discarded trailing blank lines are neither copied nor retained. Escaped scalars likewise write directly into a bounded UTF-8 buffer. Optimized Core shows bulk byte copies, direct byte writes, and unboxed offset loops without per-escape result allocation.
The charts and tables above are regenerated by wireform-stats from wireform-yaml/bench-results/summary/*.json — the same source the README charts are built from.
Notable modules
Section titled “Notable modules”| Module | Role |
|---|---|
YAML.Class | ToYAML / FromYAML, encodeYAML, decodeYAML |
YAML.Value | AST for mappings, sequences, scalars, anchors, and streams |
YAML.Encode | Block and flow emitter with YAML 1.2 quoting rules |
YAML.Decode | Parser for documents and multi-doc streams |
YAML.Derive | Template Haskell deriver wired to Wireform.Derive annotations |
YAML.JSON | Bridge between YAML values and JSON |