rerefined: Refinement types, again

[ data, library, mit, types ] [ Propose Tags ]
Versions [RSS] 0.1.0, 0.2.0, 0.3.0, 0.4.0
Change log CHANGELOG.md
Dependencies base (>=4.16 && <5), mono-traversable (>=1.0.17.0 && <1.1), template-haskell, typeably (>=0.1.0 && <0.2) [details]
License MIT
Author Ben Orchard
Maintainer Ben Orchard <thefirstmuffinman@gmail.com>
Category Types, Data
Home page https://github.com/raehik/rerefined#readme
Bug tracker https://github.com/raehik/rerefined/issues
Source repo head: git clone https://github.com/raehik/rerefined
Uploaded by raehik at 2024-04-30T15:57:33Z
Distributions
Reverse Dependencies 1 direct, 2 indirect [details]
Downloads 46 total (46 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs uploaded by user
Build status unknown [no reports yet]

Readme for rerefined-0.1.0

[back to package description]

rerefined

Rewrite of Nikita Volkov's refined library.

  • same concept
  • same performance
  • more instances
  • better ergonomics (no insidious Typeable constraints)
  • internals: fewer dependencies (no aeson), better errors, more concise

Why?

I used the original refined library fairly extensively to power other libraries (see strongweak, binrep), though I moved to a fork refined1 some time ago to provide a feature I needed. I think the library has some flaws and I want to contribute, but my tiny tweaks are still pending after a few years. A good excuse to rewrite from the ground up.

All source code is original.

Major changes from original refined

Simplified errors

refined encoded the logical predicates in its error type. This doesn't enable any further analysis, just turns a non-sum type into a sum type and complicates consumption. Furthermore, this error type is first transformed into another recursive ADT, which is then pretty printed. This is unnecessary (even mentioned in the code).

rerefined has a single-constructor error type which can be easily and efficiently turned into a String in a single pass.

No insidious Typeable contexts

See refined#101. Typeable is useful, but the way it is used brings lots of Typeable contexts.

rerefined has predicates declare their "predicate name" explicitly. You can still use Typeable for non-combinator predicates, where no Typeable contexts are incurred, but combinator predicates such as binary logical predicates require more work. However, you can use all the existing ShowS helpers (that's how typeReps are printed anyway), so it's just like writing a manual Show instance! Plus, combinator predicates are fairly unusual, so library users will probably never see this.

Note that this change also improves predicate name display, since typeRep tries to display inferred/hidden kinds for wrapped predicates in combinator predicates, which are uninteresting. We can ignore these in our manual instances!

Cleaner design

What do LessThan, GreaterThan, EqualTo etc. have in common? They're all relational binary operators where one value is a pre-filled Natural. rerefined packs all of these into a single predicate that takes a type-level relational operator. Only one instance for the same amount of code, and much easier to reason about.

We take this even further and allow passing a type-level sign, to enable comparing negative values.

We take this even further and use the same relational operator definitions to define length comparisons, where the other value is taken from the input's length (rather than its numeric value). This does not take a sign, since length must be non-negative.

More instances

You know that length comparison predicate above? It has a single instance for each of Refined1 and Refined:

-- | Compare the length of a 'Foldable' to a type-level 'Natural' using the
--   given 'RelOp'.
instance (KnownNat n, Foldable f, ReifyRelOp op)
  => Refine1 (CompareLength op n) f where
    validate1 p = validateCompareLength p . length

-- | Compare the length of a 'MonoFoldable' to a type-level 'Natural' using the
--   given 'RelOp'.
instance (KnownNat n, MonoFoldable a, ReifyRelOp op)
  => Refine (CompareLength op n) a where
    validate p = validateCompareLength p . olength

We get a ton more instances for a ton less code. (Note that mono-foldable has a surprisingly small footprint, as most of its transitive dependencies are core libraries.)

License

Provided under the MIT license. See LICENSE for license text.