Add Invariant Abstraction
See original GitHub issueDescription
Add support for an abstraction that combines a Functor
and a Contravariant
in a way similar to a Profunctor
, but where the output parameter is constrained to the input parameter.
From Haskell:
class Invariant f where
invmap :: (a -> b) -> (b -> a) -> f a -> f b
This is a useful abstraction when dealing with bidirectional programming, for example:
type Decoder<'a> = (string -> Result<'a, string>)
type Encoder<'a> = ('a -> string)
type Codec<'a> =
{ decoder : Decoder<'a>
encoder : Encoder<'a> }
type Decoder<'a> with
static member Map(d : Decoder<'a>, f : 'a -> 'b) : Decoder<'b> = map f << d
type Encoder<'a> with
static member Contramap(e : Encoder<'a>, f : 'b -> 'a) : Decoder<'b> = f >> e
type Codec<'a> with
static member Invmap({ decoder = d; encoder = e } : Codec<'a>, f : 'a -> 'b, g : 'b -> 'a) : Codec<'b> =
{ decoder = map f d
encoder = contramap g e }
A similar related abstraction is an Invariant2
, a sort of Bifunctor
analog of Invariant
class Invariant2 f where
invmap2 :: (a -> c) -> (c -> a) -> (b -> d) -> (d -> b) -> f a b -> f c d
Which has a similar application, except that it allows the definition of types like Codec<'a, 'b>
, where the encoded type is parametric in addition to the decoded type.
If you like the suggested additions, I would be happy to submit a PR with these abstractions added.
Issue Analytics
- State:
- Created 5 years ago
- Comments:18 (14 by maintainers)
Top Results From Across the Web
Reading 13: Abstraction Functions & Rep Invariants
An invariant is a property of a program that is always true, for every possible runtime state of the program. Immutability is one...
Read more >CSE 331 / Writing Abstraction Functions and Rep Invariants
Abstraction functions and representation invariants are internal documentation of a class's implementation details. A client should not need any of this ...
Read more >Abstraction Functions and Representation Invariants
In the case of an ADT, the rep invariant is a module invariant. Module invariants are useful for understanding how the code works,...
Read more >Rep Invariants and Abstraction Functions
Representation Invariant: A condition that must be true over all valid concrete representations of a class. The representation invariant also defines the domain ......
Read more >Representation Invariants and Abstraction Functions
The abstraction function maps valid concrete data representation to the abstract value it represents. • I.e., domain is all reps that satisfy the...
Read more >Top Related Medium Post
No results found
Top Related StackOverflow Question
No results found
Troubleshoot Live Code
Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start FreeTop Related Reddit Thread
No results found
Top Related Hackernoon Post
No results found
Top Related Tweet
No results found
Top Related Dev.to Post
No results found
Top Related Hashnode Post
No results found
Top GitHub Comments
I know what you mean when you say it’s hard to immediately see the real-world value of these abstractions. I find this is often the case with functional abstractions, until you actually encounter a situation where they’re useful.
I have encountered a situation recently in one of my personal projects Data Blocks where this abstraction would be useful.
It is a more specific example of the sample code I gave above:
The basis of the API is that for every operation you define for the
JsonDecoder
, there should be a corresponding operation defined for theJsonEncoder
. e.g.:given that the two sets of APIs are opposites, this allows a single
codec
API to be written that combines these:invmap
is therefore required by the API, because it’s the only way to combine a functor and a contravariant functor into a single unit that still allows mapping over the type parameter.As a result of this, you only need to specify your JSON schema one time, when creating the codec:
| I really like the example you wrote, I wish we had more sample code and docs like that.
Could always look into adding some! I’ve started using this library in a number of projects, I’d be happy to contribute code examples. I agree, examples are by far the most effective way to communicated the purpose / application of an abstraction. I think I’ve even got a few potential applications of
Arrow
…This is not a port of a Haskell project, in fact it’s quite heavily inspired by Elm’s Json.Decode and elm-decode-pipeline. The idea behind the
DataBlocks
project is to provide a similar interface, but with bidirectional capabilities (can encode or decode from the same spec).In addition, the intent was not for this to be restricted to JSON. I’m working on genericizing it so that the building block is an Epimorphism that bidirectionally converts from any type to any other type (where the decode direction may fail). This means it can be combined like this:
It’s related! An
Invariant
is a functor over which an isomorphism can be mapped.I suspect Haskell and PureScript docs would be a good place to start for this.
There is another Aeson-based F# project called Chiron which seems to be quite similar in terms of API and implementation.
I do really like the fact that the typing is implicit! That makes the code a lot more terse. I’ll have to look over it in more detail 😃