threepenny-editors: Composable algebraic editors

[ bsd3, library, web ] [ Propose Tags ]

This package provides a type class Editable and combinators to easily put together form-like editors for algebraic datatypes.

NOTE: This library contains examples, but they are not built by default. To build and install the example, use the buildExamples flag like this

cabal install threepenny-editors -fbuildExamples

[Skip to Readme]
Versions [faq],,,,,,,,,,,,,,,,,,, 0.3.0, 0.4.0, 0.4.1, 0.5.0, 0.5.1, 0.5.2, 0.5.3, 0.5.4,, 0.5.5, 0.5.6,
Change log
Dependencies base (>=4.7 && <5), bifunctors, casing, containers, data-default, generics-sop, lens, profunctors, threepenny-gui (>0.7) [details]
License BSD-3-Clause
Copyright All Rights Reserved
Author Jose Iborra
Category Web
Home page
Uploaded by PepeIborra at Sun Aug 13 09:32:48 UTC 2017
Distributions NixOS:
Executables person, parser
Downloads 7976 total (272 in the last 30 days)
Rating (no votes yet) [estimated by rule of succession]
Your Rating
  • λ
  • λ
  • λ
Status Hackage Matrix CI
Docs available [build log]
Last success reported on 2017-08-13 [all 1 reports]





build the examples


Use -f <flag> to enable a flag, or -f -<flag> to disable that flag. More info


Maintainer's Corner

For package maintainers and hackage trustees

Readme for threepenny-editors-0.5.2

[back to package description]

Travis Build Status Hackage Stackage Nightly



A library allowing to easily create threepenny-gui widgets for editing algebraic datatypes. The library provides a set of editors for primitive and base types, a set of editor constructors for building editors for type constructors, and a set of combinators for composing editors - the EditorFactory type has an Applicative-like structure, with two combinators for horizontal and vertical composition, as well as a Profunctor instance. Don't worry if you are not familiar with these concepts as they are not required to perform simple tasks with this library.

newtype EditorFactory a b
instance Profunctor EditorFactory

(|*|) :: EditorFactory s (b->a) -> EditorFactory s b -> EditorFactory s a
(-*-) :: EditorFactory s (b->a) -> EditorFactory s b -> EditorFactory s a

The library also provides an Editable type class to associate a default EditorFactoy with a type:

class Editable a where
  editor :: EditorFactory a a


Let's start with something simple, obtaining an EditorFactory for a newtype:

newtype Brexiteer = Brexiteer {unBrexiteer::Bool} deriving (Bounded, Enum, Eq, Read, Show, Ord, Generic)

Since we already have an Editable instance for Bool that displays a checkbox, we can obtain an Editable instance for Brexiteer for free:

deriving instance Editable Brexiteer

We can also wrap the existing Bool editor manually if we want to using dimap:

editorBrexiteer = dimap unBrexiteer Brexiteer (editor :: Editor Bool Bool)

The type annotation above is only for illustrative purposes.

Perhaps we are not happy with the default checkbox editor and want to have a different UI? The code below shows how to use a textbox instead:

editorBrexiteerText :: EditorFactory Brexiteer Brexiteer
editorBrexiteerText = editorReadShow

Or a combo box:

editorBrexiteerChoice :: EditorFactoy Brexiteer Brexiteer
editorBrexiteerChoice = editorEnumBounded

Let's move on to a union type now:

data Education
  = Basic
  | Intermediate
  | Other_ String
  deriving (Eq, Read, Show)

We could define an editor for Education with editorReadShow, but maybe we want a more user friendly UI that displays a choice of education type, and only in the Other case a free form text input. The editorSum combinator takes a list of choices and an editor for each choice:

editorEducation :: EditorFactory Education Education
editorEducation = do
    let selector x = case x of
            Other _ -> "Other"
            _       -> show x
      [ ("Basic", const Basic <$> editorUnit)
      , ("Intermediate", const Intermediate <$> editorUnit)
      , ("Other", dimap (fromMaybe "" . getOther) Other editor)

getOther :: Education -> Maybe String
getOther (Other s) = Just s
getOther _         = Nothing

Or more simply, we could just use editorGeneric to achieve the same effect, provided that Education has got SOP.Generic and SOP.HasDatatypeInfo instances

import           GHC.Generics
import qualified Generics.SOP as SOP

deriving instance Generic Education
instance SOP.HasDatatypeInfo Education
instance SOP.Generic Education

-- Derive an Editable instance that uses editorGeneric
instance Editable Education

-- Explicitly call editorGeneric
editorEducation :: EditorFactory Education Education
editorEducation = editorGeneric

Moving on to a record type, let's look at how to compose multiple editors together:

data Person = Person
  { education           :: Education
  , firstName, lastName :: String
  , age                 :: Maybe Int
  , brexiteer           :: Brexiteer
  , status              :: LegalStatus
  deriving (Generic, Show)

The field combinator encapsulates the common pattern of pairing a label and a base editor to build the editor for a record field:

field :: String -> (out -> inn) -> EditorFactory inn a -> EditorFactory out a
field name f e = string name *| lmap f e

Where *| prepends a UI Element to an Editor horizontally:

(*|) :: UI Element -> EditorFactory s a -> EditorFactory s a

Armed with field and applicative composition (vertical '--' and horizontal '||'), we define the editor for Person almost mechanically:

editorPerson :: EditorFactory Person Person
editorPerson =
    (\fn ln a e ls b -> Person e fn ln a b ls)
      <$> field "First:"     firstName editor
      -*- field "Last:"      lastName editor
      -*- field "Age:"       age editor
      -*- field "Education:" education editorEducation
      -*- field "Status"     status (editorJust $ editorSelection (pure [minBound..]) (pure (
      -*- field "Brexiter"   brexiteer editor

The only bit of ingenuity in the code above is the deliberate reordering of the fields.

It is also possible to generically derive the editor for person in the same way as before, in which case the labels are taken from the field names, and the order from the declaration order.