strongweak
Convert between strong and weak representations of types
https://github.com/raehik/strongweak#readme
Version on this page: | 0.3.2 |
LTS Haskell 20.26: | 0.3.2 |
Stackage Nightly 2024-10-03: | 0.9.1 |
Latest on Hackage: | 0.9.1 |
strongweak-0.3.2@sha256:70ac7d942bb8938b5b306c31256b4f63fb926d8a0c25d1c87cb1c0f58991e089,2405
Module documentation for 0.3.2
strongweak
Purely convert between pairs of “weak” and “strong”/“validated” types, with good errors and generic derivers.
Definition of strong and weak types
Take a pair of types (strong, weak)
. We state the following:
- You may safely convert (“weaken”) any
strong
value to aweak
value. - You can try to convert (“strengthen”) any
weak
value to astrong
value, but it may fail.
As a rule, a weak type should be easier to use than its related strong type.
That is, it should have fewer invariants to consider or maintain. You could
weaken an a
to a Maybe a
, but since a Maybe a
is harder to use, it’s not a
candidate for this library.
As an arbitrary limitation for ease of use, a strong type has only one associated weak type. The same weak type may be used for multiple strong types. This restriction guides the design of “good” strong-weak type pairs & keeps them synchronized, plus helps type inference.
Examples
The refined library defines a newtype Refined p a = Refined a
. To get a Refined
, you must test its associated predicate. You may
recover the unrefined value by removing the newtype wrapper. Thus, you may
strengthen a
s into Refined p a
s, and weaken vice versa.
The WordX
family are like bounded Natural
s. We can consider Natural
as a
weak type, which can be strengthened into e.g. Word8
by asserting
well-boundedness.
Cool points
Validates as much as possible
This is primarily a validation library. Thus, we don’t fail on the first error
– we attempt to validate every part of a data type, and collate the errors into
a big list. (ApplicativeDo
plus Validation
is magical.)
One definition, strong + weak views
Using a type-level Strength
switch and the SW
type family, you can write a
single datatype definition and receive both a strong and a weak representation,
which the generic derivers can work with. See the Strongweak.SW
module for
details.
Generic strengthening is extremely powerful
There are generic derivers for generating Strengthen
and Weaken
instances
between arbitrary data types. The Strengthen
instances annotate errors
extensively, telling you the datatype & record for which strengthening failed -
recursively, for nested types!
Note that the generic derivers work with any pair of matching data types. But they must match very closely: both types are traversed in tandem, so every pair of fields must be compatible. If you need to do calculation to move between your strong and weak types, consider splitting it into calculation -> strengthening and using the generic derivers. Or write your own instances.
Backdoors included
Sometimes you have can guarantee that a weak value can be safely strengthened, but the compiler doesn’t know - a common problem in parsing. In such cases, you may use efficient unsafe strengthenings, which don’t perform invariant checks.
What this library isn’t
Not a convertible
This is not a Convertible
library that enumerates transformations between
types into a dictionary. A strong type has exactly one weak representation, and
strengthening may fail while weakening cannot. For safe conversion enumeration
via typeclasses, consider Taylor Fausak’s
witch library.
Not particularly speedy
The emphasis is on safety, possibly at the detriment of performance. However, my
expectation is that you only strengthen & weaken at the “edges” of your program,
and most of the time will be spent transforming weak representations. This may
improve performance if it means invariants don’t have to be continually asserted
inline, but it also may slow things down e.g. Natural
s are slower than
Word
s.
Related projects
barbies
The barbies library is an investigation into how far the higher-kinded data pattern can be stretched. strongweak has some similar ideas:
- Both treat a type definition as a “skeleton” for further types.
- strongweak’s
SW
type family looks a lot like barbies’Wear
.
But I believe we’re irreconcilable. strongweak is concerned with validation via
types. SW
is just a convenience to reuse a definition for two otherwise
distinct types, and assist in handling common patterns. Due to the type family
approach, we can rarely be polymorphic over the strong and weak representations.
Whereas barbies wants to help you swap out functors over records, so it’s very
polymorphic over those, and makes rules for itself that then apply to its users.
You could stack barbies on top of a SW
type no problem. It would enable you to
split strengthening into two phases: strengthening each field, then gathering
via traverse (rather than doing both at once via applicative do). That thinking
helps reassure me that these ideas are separate. (Note: I would hesitate to
write such a type, because the definition would start to get mighty complex.)
Changes
0.3.2 (2022-11-28)
- support GHC 9.4
0.3.1 (2022-07-04)
- update refined (polykind predicate)
0.3.0 (2022-06-08)
- switch to associated type family for
Weak
insideWeaken
-Strengthen
now hasWeaken
as a superclass- I’m fairly confident that things make more sense this way - we get to remove an open type family, improve type inference, and prevent users from writing potentially dangerous instances. For that, a bit of asymmetry is welcome.
- better document generic derivers
- clarify instance design, provide more decomposer instances
- various refactoring
0.2.0 (2022-05-31)
Initial Hackage release (dependency issues prevented uploading).
- fix field indexing in generic errors
- add unsafe strengthening
- add property and error tests
0.1.0 (2022-05-16)
Initial release.
- basic instances (lists, numerics)
- generic derivations
- super explicit errors