# Serializing Haskell functions to disk

**URL:** <https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603>\
**Category:** Learn\
**Created:** [May 21, 2024, 1:55pm UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603 "2024-05-21T13:55:22Z")\
**Posts on this page:** 11\
**Page:** 2

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [May 22, 2024, 9:14am UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603/21 "2024-05-22T09:14:48Z")

</div>

Every data type you define is typeable and I believe all built-in types too, but existentially or universally quantified types are not.

---

<div class="post-metadata">

**Author:** ![chrisdone](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/chrisdone/32/1408_2.png) [@chrisdone](https://discourse.haskell.org/u/chrisdone)\
**Post date:** [May 22, 2024, 3:34pm UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603/22 "2024-05-22T15:34:48Z")

</div>

You could fork [hell](https://github.com/chrisdone/hell), which is 1.2k lines of code in a single file and gets you a mini-Haskell for scripting. It’s pretty easy to configure [what types are supported](https://github.com/chrisdone/hell/blob/20d5327ff5ac2d7a29cd0b54edbc6b561babff0b/Hell.hs#L670), and what primitives, both [monomorphic](https://github.com/chrisdone/hell/blob/20d5327ff5ac2d7a29cd0b54edbc6b561babff0b/Hell.hs#L687) and [polymorphic](https://github.com/chrisdone/hell/blob/20d5327ff5ac2d7a29cd0b54edbc6b561babff0b/Hell.hs#L851). It has some basic type clases like `Eq`, `Ord`, `Show` and `Monad`, but you can’t write your own within the object language.

I kept it intentionally one file to artificially limit the size of the implementation, and to make it easy for someone to fork and re-use for another purpose.

You could generate an untyped AST in haskell-src-exts format, and that’s easy to serialize to/from disk. [The subset of] Haskell syntax is stable over time, unlike some binary format which is likely to lead you to trouble. GHC’s API and general infrastructure is also fast-moving and wobbly, so I would never use it in a deployed app. If you’re always planning on generating code that has type annotations, then you probably don’t need type-inference and could cut out that whole block of code and bring it down to 500~ lines.

Performance-wise, a [basic `fib` implementation](https://github.com/chrisdone/hell/blob/main/examples/12-fib.hell) [outperforms GHCi slightly](https://github.com/chrisdone/hell/blob/88ba23a7fe2c4fb127fb9bd6ee84cf9761c62df9/readme.md?plain=1#L161), which isn’t a brag or rigorous, but does indicate that its performance isn’t bad. Which makes sense, [the evaluator](https://github.com/chrisdone/hell/blob/20d5327ff5ac2d7a29cd0b54edbc6b561babff0b/Hell.hs#L205) would fit on a napkin and doesn’t really do anything.

One small: if performance or distribution becomes a problem, you can generate Haskell code from a Hell AST and compile it with GHC ([haskell-src-meta: Parse source to template-haskell abstract syntax.](https://hackage.haskell.org/package/haskell-src-meta)), as it doesn’t “add” any features that Haskell doesn’t already have, and output WASM down the line if needed. That adds the burden of depending on the GHC toolchain, but it’s a path forward nonetheless.

---

<div class="post-metadata">

**Author:** ![ocramz](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ocramz/32/3713_2.png) [@ocramz](https://discourse.haskell.org/u/ocramz)\
**Post date:** [May 22, 2024, 4:41pm UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603/23 "2024-05-22T16:41:13Z")

</div>

more to the point, do things like Vector a → a have a Typeable interface? I suspect we’re talking of trainable models in the machine learning sense here.

---

<div class="post-metadata">

**Author:** ![jaror](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/jaror/32/3271_2.png) [@jaror](https://discourse.haskell.org/u/jaror)\
**Post date:** [May 22, 2024, 5:50pm UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603/24 "2024-05-22T17:50:36Z")

</div>

That’s a great practical example and it shows what I mean, namely the function and vector are typeable, but the polymorphic `a` is slightly problematic. Instead you’d have to use `Typeable a => Vector a -> a`.

---

<div class="post-metadata">

**Author:** ![enobayram](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/enobayram/32/1815_2.png) [@enobayram](https://discourse.haskell.org/u/enobayram)\
**Post date:** [May 24, 2024, 5:21am UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603/25 "2024-05-24T05:21:28Z")

</div>

Based on the description of the problem, I would personally look into defunctionalizing your `Strategy`. I.e. instead of serializing the function, serialize whatever data you used to construct that function. After all, you’re not reading Haskell code from an input field in the UI or from a database, serialize the thing you’re reading or maybe serialize an intermediate stage between the raw input and the eventual `Strategy`.

Independent of the language used, serializing functions is very messy conceptually (except maybe for languages like C where functions can’t have closures).

I think of defunctionalization sort of as an incremental way to implement a DSL. Initially, there’s only one `Strategy`, so your DSL consists of the grammar `()`, i.e. that one `Strategy` is the only thing that’s expressible:

```haskell
theOnlyStrategy :: UTCTime -> Bid
theOnlyStrategy = the . winning . formula

data DSLv1 = TheStrategy

toStrategy :: DSLv1 -> Strategy
toStrategy TheStrategy = theOnlyStrategy

```

You add 2 more `Strategy`s in the next release, one parameterized over an integer and another over a boolean, then your DSL is represented as `S1 | S2 Integer | S3 Bool` and you have a simple migration from the first version of the DSL and you have a new `toStrategy` from `DSLv2`:

```haskell
migrate :: DSLv1 -> DSLv2
migrate TheStrategy = S1

toStrategy :: DSLv2 -> Strategy
toStrategy S1 = theUsedToBeOnlyStrategy
toStrategy (S2 i) = ...
toStrategy (S3 b) = ...

```

This would give you the following benefits:

- You have complete introspection into serialized strategies, you can display them, design domain-specific UIs for modifying them, statically analyze them and even translate them to an SMT solver and prove properties about them.
- New releases can improve existing strategies or fix bugs in them
- It’s just simple Haskell, no need to worry about unspeakable horrors involved in serializing code + runtime closures.

---

<div class="post-metadata">

**Author:** ![LaurentRDC](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/laurentrdc/32/4950_2.png) [@LaurentRDC](https://discourse.haskell.org/u/LaurentRDC)\
**Post date:** [May 24, 2024, 12:29pm UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603/26 "2024-05-24T12:29:40Z")

</div>

That seems to be the best approach for now. Thank you!

---

<div class="post-metadata">

**Author:** ![ocramz](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ocramz/32/3713_2.png) [@ocramz](https://discourse.haskell.org/u/ocramz)\
**Post date:** [May 25, 2024, 2:46am UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603/27 "2024-05-25T02:46:28Z")

</div>

also from an ML angle, if the strategy belongs to a parametric family of functions (e.g. it’s a polynomial of a fixed degree), you only need to serialize the coefficients so the closure only effectively needs to exist at runtime:

```haskell
eval :: DSLv3 -> (UTCTime -> Double)
eval (S3 i1 i2 i3) t = i3 + i2 * t + i1 * t**2

```

---

<div class="post-metadata">

**Author:** ![brandonchinn178](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/brandonchinn178/32/5103_2.png) [@brandonchinn178](https://discourse.haskell.org/u/brandonchinn178)\
**Post date:** [May 31, 2024, 1:25pm UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603/28 "2024-05-31T13:25:41Z")

</div>

Potentially of interest:

[![](https://img.youtube.com/vi/wppzFzzD4b8/maxresdefault.jpg "Bartosz Milewski – Replacing functions with data") ](https://www.youtube.com/watch?v=wppzFzzD4b8)

---

<div class="post-metadata">

**Author:** ![darkxero](https://avatars.discourse-cdn.com/v4/letter/d/f07891/32.png) [@darkxero](https://discourse.haskell.org/u/darkxero)\
**Post date:** [June 1, 2024, 2:57am UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603/29 "2024-06-01T02:57:12Z")

</div>

By what point do you figure you’re implementing a Lisp with Haskell?  
Might as well use Common Lisp with Coalton.

---

<div class="post-metadata">

**Author:** ![ocramz](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/ocramz/32/3713_2.png) [@ocramz](https://discourse.haskell.org/u/ocramz)\
**Post date:** [June 3, 2024, 3:25am UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603/30 "2024-06-03T03:25:30Z")

</div>

Bartos has this art school teacher vibe : "lambdas are _closures_ mmmkay?

---

<div class="post-metadata">

**Author:** ![enobayram](https://sea2.discourse-cdn.com/flex002/user_avatar/discourse.haskell.org/enobayram/32/1815_2.png) [@enobayram](https://discourse.haskell.org/u/enobayram)\
**Post date:** [June 3, 2024, 7:14am UTC](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603/31 "2024-06-03T07:14:48Z")

</div>

I think that’s the C++ programmer in Bartosz speaking. When he says “lambdas are not named functions”, he means named functions in the C++ sense, where they can only be top-level. And when he says lambdas are closures, he means it in the sense that C++ lambdas get turned into a closure object if they have a non-empty capture clause. So I think that bit is him talking to his inner C++ that he can’t just pass around function pointers (which are effectively `StaticPtr (a -> b)` in Haskell land)

[Previous page](https://discourse.haskell.org/t/serializing-haskell-functions-to-disk/9603.md?page=1)
