FSharp.Uniplate
Port of the Haskell Uniplate library to F#. Simplifies transformations over recursive algebraic data types.
Install / Use
/learn @jobjo/FSharp.UniplateREADME
FSharp.Uniplate
Fsharp.Uniplate is a port of the Haskell Uniplate library by Neil Mitchell. Uniplate enabels transformations over recursive algebraic data types to be expressed in a concise way.
The library relies on reflection to automatically derive uniplate instances for arbitrary algebraic data types.
Basic usage
Consider the following type for representing boolean expressions:
type Exp =
| True
| False
| Var of string
| Not of Exp
| And of Exp * Exp
| Or of Exp * Exp
A uniplate instance for the Exp data typed can be created using the derive function:
open Fsharp.Data.Generics.Uniplate
let UP = derive<Exp>
A function for collecting all names of variables of a given expression is simple to implement using UP.Universe for collecting sub expressions:
let variableNames exp =
[
for exp in UP.Universe exp do
match exp with
| Var x -> yield x
| _ -> ()
]
We can also define a function that maps over all variable names of an expression, using the Tranform function:
let toUpperVarNames = UP.Transform <| function
| Var name -> Var <| name.ToUpper()
| e -> e
Note that we only need to pattern match on the case we are interested in, the Transform function will apply the function on all sub-expressions, rebuilding the expression tree from bottom up.
Additionally, there is a more powerful alternative Rewrite that applies a tranformation recursively until a fixed point is reached, i.e there are no sub-expressions matching the rewrite rules. Here's an example of using Rewrite for defining a function that normalizes boolean formulas:
let normalize = UP.Rewrite <| function
| Not True -> Some False
| Not False -> Some True
| And(True,e)
| And (e,True) -> Some e
| And(False,_)
| And(_, False) -> Some False
| Or(True,_)
| Or(_,True) -> Some True
| Or(False,e)
| Or(e,False) -> Some e
| Not (Not x) -> Some x
| Not (And (e1,e2)) -> Some <| Or (Not e1, Not e2)
| Not (Or (e1,e2)) -> Some <| And (Not e1, Not e2)
| _ -> None
And a few examples of applying the normalize function:
> normalize <| Not (Not (Not False));;
val it : Exp = True
> normalize <| Not (And (Not (Or (Var "a",False)),Not (Var "z")))
val it : Exp = Or (Var "a",Var "z")
> normalize <| Not (Or (Not True, Var "X"));;
val it : Exp = Not (Var "X")
Defining uniplate instances manually
Rather than using the derive function for creating a unipate instance, you may also define it manually with mkUniplate by specifying a function for how to decompose values. Its signature is:
type Decomposition<'T> = list<'T> * (list<'T> -> 'T)
val mkUniplate<'T> (uniplate: 'T -> Decomposition<'T>) : Uniplate<'T>
The function argument maps an value to a tuple of a list of sub-expressions (children) and a constructor function for composing children to create an expression of the same type.
Here is an equivalent version of the automatically derived uniplate instance from above:
let UP =
mkUniplate <| function
| True -> [], fun _ -> True
| False -> [], fun _ -> False
| Var n -> [], List.head
| Not e -> [e], List.head >> Not
| And (e1,e2) -> [e1;e2], fun xs -> And (xs.[0], xs.[1])
| Or (e1,e2) -> [e1;e2], fun xs -> Or (xs.[0], xs.[1])
The manually created instance is faster since it doesn't rely on reflectoin for decmposing the values.
Related Skills
node-connect
343.3kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
92.1kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
343.3kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
343.3kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
