Package

purescript-polyform

Repository
purescript-polyform/polyform
License
BSD-3-Clause
Uploaded by
paluh
Published on
2021-05-24

An attempt to build easy to use and composable applicative/semigroupoid based validation toolkit. As a bonus it provides a different codec type formulation than in the codecs lib which is called Dual.

Sorry for these sparse docs. I hope to extend them soon. Till then it is just better to take a look at the real world examples (json duals, urlencode validators etc.) in the purescript-polyform-batteries repo.

Core types

Validator

This type provides an Applicative instance which allow composition in the Reader spirit - all Validators "work" on the same input. It is accumulative because it is built on top of the V type from purescript-validation. There is also a Category instance which allows you to build validation chains combining this applicative steps.

newtype Validator m e i o = Validator (Star (Compose m (V e)) i o)

Exceptor

This type is nearly Star (Except e m) but there is no Semigroup e constraint in its Alt instance (consistent with Either).

newtype Exceptor m e i o = Exceptor (Star (ExceptT e m) i o)

Reporter

We can imagine that Reporter is built on top of a type like Tuple r (Maybe a) instead of V r a or Either r a (We process this type by using MaybeT over WriterT underneath).

The idea of accumulating errors from the V type (from purescript-validation) is extended here. We can think of r value as not only the error representation but as the overall validation "report". These values are accumulated by all interesting instances (Functor, Applicative) of the Reproter type.

When this type can be useful? When we consider for example HTML form rendering we can find that when form validation fails we want to present not only invalid parts of the form. In such a case we want to rerender the whole form. It is convenient to have already validated values in such a case and be able to provide some info based on this partially correct state.

Of course we want to also use our favorite type Star. We wrap it in a Reporter newtype and provide some additional instances like Alt or Category:

newtype Reporter m r i o = Reporter (Star (MaybeT (WriterT r m)) i o)

Dual

data DualD p i o o' = DualD (p i o') (o  i)

newtype Dual p i o = Dual (DualD p i o o)

The above types allow to build bidirectional validation and serialization flows. Polyform.Dual type thanks to its "diverging" nature (baked by DualD) can be used in an applicative manner. Finally it has to be combined into the finall result - a correct Dual.

  profile = Dual $
    ( { email1: _, email2: _, age: _}
    <$> _.email1 ~ emailDual
    <*> _.email2 ~ emailDual
    <*> _.age ~ ageDual
    )

Composibility of this type - I mean Applicative, Alt, Semigroupoid, Category instances are based on the assumption that i (which we can consider as a tokens stream or serialization result) has defined Semigroup instance. It can be a bit surprising but it is often the case and not a blocker - even for serialization and parsing a Json because Object can be combined or build in a monoidal way. Here we have some examples of Dual values definition which work with Json taken from polyform-validators test suite. We are using generic utils provided by the validators lib:

import Polyform.Dual.Record (build) as Dual.Record
import Polyform.Dual.Variant (case_)
import Polyform.Duals.Validators.Json (arrayOf, boolean, field, int, json, noArgs, number, object, on, string, sum, unit, (:=))

aRecordDual = Dual.Record.build
  $ (SProxy  SProxy "foo") := int
  <<< (SProxy  SProxy "bar") := string
  <<< (SProxy  SProxy "baz") := number

aVariantDual = case_
  # on (SProxy  SProxy "s") string
  # on (SProxy  SProxy "u") unit
  # on (SProxy  SProxy "i") int

-- In a polymorphic case (where validation monad stays unkown) we have to wrap fields in `indentity` because of the "record impredicativity".
aSumDual = sum
  { "S": identity string
  , "I": identity int
  , "B": identity boolean
  , "N": identity number
  , "E": identity noArgs
  , "U": identity unit
  }

Name

Initialy polyform was intended to provide some basic utils for HTTP form validation. It was polyomrphic on the form type hence the name. After the last rewrite this name has probably no sens any more can be interpreted in the conext of fully polymorphic bidirectional flows provided by Dual. We have serialization and deserialization of data... so you know, you can have your data represented in different formats and convert between them... :-P