uniplate

Haskell library for simple, concise and fast generic operations.

https://github.com/ndmitchell/uniplate

Science Score: 67.0%

This score indicates how likely this project is to be science-related based on various indicators:

  • CITATION.cff file
    Found CITATION.cff file
  • codemeta.json file
    Found codemeta.json file
  • .zenodo.json file
    Found .zenodo.json file
  • DOI references
    Found 1 DOI reference(s) in README
  • Academic publication links
    Links to: acm.org
  • Committers with academic emails
  • Institutional organization owner
  • JOSS paper metadata
  • Scientific vocabulary similarity
    Low similarity (11.5%) to scientific vocabulary
Last synced: 6 months ago · JSON representation ·

Repository

Haskell library for simple, concise and fast generic operations.

Basic Info
  • Host: GitHub
  • Owner: ndmitchell
  • License: other
  • Language: Haskell
  • Default Branch: master
  • Size: 2.83 MB
Statistics
  • Stars: 81
  • Watchers: 5
  • Forks: 8
  • Open Issues: 12
  • Releases: 0
Created over 12 years ago · Last pushed over 2 years ago
Metadata Files
Readme Changelog License Citation

README.md

Boilerplate Removal with Uniplate Hackage version Stackage versionBuild status

Generic transformations and queries are often referred to as boilerplate code - they remain relatively similar as the action performed by the code changes, and can often outnumber the actual intent of the code in terms of lines. While other generic traversal schemes have shown how powerful new features can be added to compilers, and how the type system can be manipulated into accepting these operations, the Uniplate library focuses on a conceptually simpler generic concept. A more complete document on Uniplate was published at the Haskell Workshop 2007, and is available from here, along with a video presentation, and the associated thesis chapter.

Uniplate is a simple, concise and fast generics library. To expand on that sentence:

  1. A generics library is one which allows you to write functions that operate over a data structure without tying down all aspects of the data structure. In particular, when writing an operation, you don't need to give a case for each constructor, and you don't have to state which fields are recursive.
  2. Uniplate is the simplest generics library. Using Uniplate is within the reach of all Haskell programmers.
  3. Uniplate is more concise than any other generics library.
  4. Uniplate is fast, not always the absolute fastest, but massively faster than many generics libraries.
  5. Uniplate is also less powerful than some other generics libraries, but if it does the job, you should use it.

The Uniplate library can be installed with the standard sequence of cabal commands:

cabal update
cabal install uniplate

This document proceeds as follows:

  1. Using Uniplate
  2. Using Biplate
  3. Making Uniplate Faster

Acknowledgements

Thanks to Björn Bringert for feedback on an earlier version of this document, Eric Mertens for various ideas and code snippets, and to Matt Naylor and Tom Shackell for helpful discussions.

Using Uniplate

To demonstrate the facilities of Uniplate, we use a simple arithmetic type:

{-# LANGUAGE DeriveDataTypeable #-}
module Expr where
import Data.Data
import Data.Generics.Uniplate.Data

data Expr = Val Int
          | Add Expr Expr
          | Sub Expr Expr
          | Div Expr Expr
          | Mul Expr Expr
          | Neg Expr
          deriving (Show, Eq, Data, Typeable)

In this definition, the Uniplate specific bits are bolded. The three extra parts are:

  • import Data.Generics.Uniplate.Data, this module contains all the Uniplate functions and definitions.
  • deriving (Data,Typeable), this deriving clause automatically adds the necessary instances for Uniplate.
  • {-# LANGUAGE DeriveDataTypeable #-}, this pragma turns on language support for the deriving line.

This definition makes use of the Scrap Your Boilerplate (SYB) based Uniplate implementation. The SYB implementation is compatible with the other implementations, but is slower (between 2 and 8 times) and requires some modest compiler extensions (implemented in GHC for many years). The alternative definition scheme is described towards the end of this document, in "Making Uniplate Faster". I recommend using the SYB implementation to start with, as it requires least work to use.

The Uniplate library defines two classes, Uniplate and Biplate, along with a number of functions. After importing Data.Generics.Uniplate.Data all types which have Datainstances automatically have the necessary Uniplate instances. In the following subsections we introduce the Uniplate functions, along with examples of using them. The two most commonly used functions are universe (used for queries) and transform (used for transformations).

Finding the constant values

haskell universe :: Uniplate on => on -> [on]

When manipulating our little language it may be useful to know which constants have been used. This can be done with the following code:

haskell constants :: Expr -> [Int] constants x = nub [y | Val y <- universe x]

Here the only Uniplate method being used is universe, which when given a tree returns the root of the tree, and all its subtrees at all levels. This can be used to quickly flatten a tree structure into a list, for quick analysis via list comprehensions, as is done above.

Exercise: Write a function to test if an expression performs a division by the literal zero.

Basic optimisation

haskell transform :: Uniplate on => (on -> on) -> on -> on

If we are negating a literal value, this computation can be performed in advance, so let's write a function to do this.

haskell optimise :: Expr -> Expr optimise = transform f where f (Neg (Val i)) = Val (negate i) f x = x

Here the Uniplate method being used is transform, which applies the given function to all the children of an expression, before applying it to the parent. This function can be thought of as bottom-up traversal of the data structure. The optimise code merely pattern matches on the negation of a literal, and replaces it with the literal.

Now let's add another optimisation into the same pass, just before the f x = x line insert:

haskell f (Add x y) | x == y = Mul x (Val 2)

This takes an addition where two terms are equal and changes it into a multiplication, causing the nested expression to be executed only once.

Exercise: Extend the optimisation so that adding x to Mul x (Val 2) produces a multiplication by 3.

Depth of an expression

haskell para :: Uniplate on => (on -> [res] -> res) -> on -> res

Now let's imagine that programmers in your language are paid by the depth of expression they produce, so let's write a function that computes the maximum depth of an expression.

haskell depth :: Expr -> Int depth = para (\_ cs -> 1 + maximum (0:cs))

This function performs a paramorphism (a bit like a fold) over the data structure. The function simply says that for each iteration, add one to the previous depth.

Exercise: Write a function that counts the maximum depth of addition only.

Renumbering literals

haskell transformM :: (Monad m, Uniplate on) => (on -> m on) -> on -> m on

The literal values need to be replaced with a sequence of numbers, each unique. This is unlikely for an arithmetic expression, but consider bound variables in lambda calculus and it starts to become a bit more plausible:

haskell uniqueLits :: Expr -> Expr uniqueLits x = evalState (transformM f x) [0..] where f (Val i) = do y:ys <- get put ys return (Val y) f x = return x

Here a monadic computation is required, the program needs to keep track of what the next item in the list to use is, and replace the current item. By using the state monad, this can be done easily.

Exercise: Allow each literal to occur only once, when a second occurrence is detected, replace that literal with zero.

Generating mutants

haskell contexts :: Uniplate on => on -> [(on, on -> on)]

The person who is inputting the expression thinks they made a mistake, they suspect they got one of the values wrong by plus or minus one. Generate all the expressions they might have written.

haskell mutate :: Expr -> [Expr] mutate x = concat [[gen $ Val $ i-1, gen $ Val $ i+1] | (Val i, gen) <- contexts x]

The transform function is useful for doing an operation to all nodes in a tree, but sometimes you only want to apply a transformation once. This is less common, but is sometimes required. The idea is that the context provides the information required to recreate the original expression, but with this node altered.

Exercise: Replace one multiplication with addition, if there are no multiplications return the original expression.

Fixed point optimisation

haskell rewrite :: Uniplate on => (on -> Maybe on) -> on -> on

When slotting many transformations together, often one optimisation will enable another. For example, the the optimisation to reduce.

Descend

Do something different in the odd and even cases. Particularly useful if you have free variables and are passing state downwards.

Monadic Variants

haskell descendM :: Monad m => (on -> m on) -> on -> m on -- descend transformM :: (Monad m, Uniplate on) => (on -> m on) -> on -> m on -- transform rewriteM :: (Monad m, Uniplate on) => (on -> m (Maybe on)) -> on -> m on -- rewrite

All the transformations have both monadic and non-monadic versions.

Single Depth Varaints

haskell children :: Uniplate on => on -> [on] -- universe descend :: (on -> on) -> on -> on -- transform holes :: Uniplate on => on -> [(on, on -> on)] -- contexts

Lots of functions which operate over the entire tree also operate over just one level. Usually you want to use the multiple level version, but when needing more explicit control the others are handy.

Evaluation

If we need to evaluate an expression in our language, the answer is simple, don't use Uniplate! The reasons are that there is little boilerplate, you have to handle every case separately. For example in our language we can write:

haskell eval :: Expr -> Int eval (Val i) = i eval (Add a b) = eval a + eval b eval (Sub a b) = eval a - eval b eval (Div a b) = eval a `div` eval b eval (Mul a b) = eval a * eval b eval (Neg a) = negate a

Using Biplate

All the operations defined in Uniplate have a corresponding Biplate instance. Typically the operations are just the same as Uniplate, with Bi on the end.

haskell universeBi :: Biplate on with => on -> [with] transformBi :: Biplate on with => (with -> with) -> on -> on transformBiM :: (Monad m, Biplate on with) => (with -> m with) -> on -> m on

The biggest difference is for the functions childrenBi and descendBi. In these cases, if the starting type and the target type are the same, then the input value will be returned. For example:

haskell childrenBi (Add (Val 1) (Val 2)) == [Add (Val 1) (Val 2)] children (Add (Val 1) (Val 2)) == [Val 1, Val 2]

For example, you should never have descendBi in an inner recursive loop.

Making Uniplate Faster

To make Uniplate faster import Data.Generics.Uniplate.Direct and write your instances by hand.

Related work

Owner

  • Name: Neil Mitchell
  • Login: ndmitchell
  • Kind: user
  • Location: Cambridge, UK
  • Company: Meta

Haskell/Rust programmer. All code is open source and licensed by me, not my employer. All views are my own.

Citation (CITATION.cff)

cff-version: 1.2.0
message: "If you need to cite Uniplate, this metadata is the paper that introduces Uniplate."
repository-code: "https://github.com/ndmitchell/uniplate"
title: Uniplate
preferred-citation:
  type: proceedings
  authors: 
  - family-names: Mitchell
    given-names: Neil
    orcid: "https://orcid.org/0000-0001-5171-9726"
  - family-names: Runciman
    given-names: Colin
    orcid: "https://orcid.org/0000-0002-0151-3233"
  doi: "10.1145/1291201.1291208"
  journal: "Haskell '07: Proceedings of the ACM SIGPLAN workshop on Haskell"
  month: 9
  day: 30
  isbn: 978-1-59593-674-5
  publisher: ACM
  title: "Uniform Boilerplate and List Processing"
  year: 2007
  url: https://ndmitchell.com/downloads/paper-uniform_boilerplate_and_list_processing-30_sep_2007.pdf
  abstract: "Generic traversals over recursive data structures are often referred to as boilerplate code. The definitions of functions involving such traversals may repeat very similar patterns, but with variations for different data types and different functionality. Libraries of operations abstracting away boilerplate code typically rely on elaborate types to make operations generic. The motivating observation for this paper is that most traversals have value-specific behaviour for just one type. We present the design of a new library exploiting this assumption. Our library allows concise expression of traversals with competitive performance."

GitHub Events

Total
  • Watch event: 6
Last Year
  • Watch event: 6

Committers

Last synced: 7 months ago

All Time
  • Total Commits: 695
  • Total Committers: 6
  • Avg Commits per committer: 115.833
  • Development Distribution Score (DDS): 0.017
Past Year
  • Commits: 0
  • Committers: 0
  • Avg Commits per committer: 0.0
  • Development Distribution Score (DDS): 0.0
Top Committers
Name Email Commits
Neil Mitchell n****l@g****m 683
brandon_m_moore@yahoo.com u****n 7
Nicolas Pouillard n****d@g****m 2
Jacek Generowicz j****g@m****t 1
Felix Yan f****s@a****g 1
Dmitry Kovanikov k****v@g****m 1
Committer Domains (Top 20 + Academic)

Issues and Pull Requests

Last synced: 7 months ago

All Time
  • Total issues: 29
  • Total pull requests: 5
  • Average time to close issues: over 1 year
  • Average time to close pull requests: about 1 year
  • Total issue authors: 20
  • Total pull request authors: 5
  • Average comments per issue: 2.59
  • Average comments per pull request: 1.2
  • Merged pull requests: 3
  • Bot issues: 0
  • Bot pull requests: 0
Past Year
  • Issues: 0
  • Pull requests: 0
  • Average time to close issues: N/A
  • Average time to close pull requests: N/A
  • Issue authors: 0
  • Pull request authors: 0
  • Average comments per issue: 0
  • Average comments per pull request: 0
  • Merged pull requests: 0
  • Bot issues: 0
  • Bot pull requests: 0
Top Authors
Issue Authors
  • ndmitchell (9)
  • LeventErkok (2)
  • lspitzner (1)
  • erikd (1)
  • asr (1)
  • l29ah (1)
  • soupi (1)
  • flip111 (1)
  • minad (1)
  • phadej (1)
  • polytypic (1)
  • ghost (1)
  • expipiplus1 (1)
  • mitchellwrosen (1)
  • mbrock (1)
Pull Request Authors
  • jacg (1)
  • felixonmars (1)
  • chshersh (1)
  • Anton-Latukha (1)
  • thomie (1)
Top Labels
Issue Labels
Pull Request Labels

Packages

  • Total packages: 2
  • Total downloads:
    • hackage 163,731 total
  • Total docker downloads: 35
  • Total dependent packages: 26
    (may contain duplicates)
  • Total dependent repositories: 583
    (may contain duplicates)
  • Total versions: 25
  • Total maintainers: 1
hackage.haskell.org: uniplate

Uniplate is library for writing simple and concise generic operations. Uniplate has similar goals to the original Scrap Your Boilerplate work, but is substantially simpler and faster. To get started with Uniplate you should import one of the three following modules: Data.Generics.Uniplate.Data - to quickly start writing generic functions. Most users should start by importing this module. Data.Generics.Uniplate.Direct - a replacement for Data.Generics.Uniplate.Data with substantially higher performance (around 5 times), but requires writing instance declarations. Data.Generics.Uniplate.Operations - definitions of all the operations defined by Uniplate. Both the above two modules re-export this module. In addition, some users may want to make use of the following modules: Data.Generics.Uniplate.Zipper - a zipper built on top of Uniplate instances. Data.Generics.SYB - users transitioning from the Scrap Your Boilerplate library. Data.Generics.Compos - users transitioning from the Compos library. Data.Generics.Uniplate.DataOnly - users making use of both Data and Direct to avoid getting instance conflicts.

  • Versions: 24
  • Dependent Packages: 26
  • Dependent Repositories: 583
  • Downloads: 163,731 Total
  • Docker Downloads: 35
Rankings
Docker downloads count: 0.2%
Downloads: 0.7%
Dependent repos count: 0.7%
Dependent packages count: 1.2%
Average: 4.3%
Stargazers count: 9.9%
Forks count: 13.1%
Maintainers (1)
Last synced: 6 months ago
proxy.golang.org: github.com/ndmitchell/uniplate
  • Versions: 1
  • Dependent Packages: 0
  • Dependent Repositories: 0
Rankings
Dependent packages count: 5.4%
Average: 5.6%
Dependent repos count: 5.8%
Last synced: 6 months ago

Dependencies

uniplate.cabal hackage
  • base >=4.10 && <5
  • containers *
  • ghc-prim *
  • hashable >=1.1.2.3
  • syb *
  • unordered-containers >=0.2.1
.github/workflows/ci.yml actions
  • actions/cache v2 composite
  • actions/checkout v2 composite
  • haskell/actions/setup v2 composite
  • ndmitchell/neil master composite