{"id":16661250,"url":"https://github.com/snowleopard/selective","last_synced_at":"2025-04-04T10:09:18.284Z","repository":{"id":37734515,"uuid":"135861997","full_name":"snowleopard/selective","owner":"snowleopard","description":"Selective Applicative Functors: Declare Your Effects Statically, Select Which to Execute Dynamically","archived":false,"fork":false,"pushed_at":"2024-04-20T23:07:28.000Z","size":846,"stargazers_count":205,"open_issues_count":11,"forks_count":21,"subscribers_count":10,"default_branch":"main","last_synced_at":"2025-03-28T09:08:26.357Z","etag":null,"topics":["haskell"],"latest_commit_sha":null,"homepage":"","language":"TeX","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/snowleopard.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGES.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-06-03T00:35:34.000Z","updated_at":"2025-02-08T00:43:44.000Z","dependencies_parsed_at":"2024-01-25T00:24:36.900Z","dependency_job_id":"25b7585b-5bcd-4be8-b295-6e315a24bc0e","html_url":"https://github.com/snowleopard/selective","commit_stats":{"total_commits":402,"total_committers":15,"mean_commits":26.8,"dds":"0.15671641791044777","last_synced_commit":"3134405e22d325b53014234b910e69db04825942"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snowleopard%2Fselective","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snowleopard%2Fselective/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snowleopard%2Fselective/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/snowleopard%2Fselective/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/snowleopard","download_url":"https://codeload.github.com/snowleopard/selective/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247157283,"owners_count":20893220,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["haskell"],"created_at":"2024-10-12T10:34:19.039Z","updated_at":"2025-04-04T10:09:18.265Z","avatar_url":"https://github.com/snowleopard.png","language":"TeX","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Selective applicative functors\n\n[![Hackage version](https://img.shields.io/hackage/v/selective.svg?label=Hackage)](https://hackage.haskell.org/package/selective) [![Build status](https://img.shields.io/github/actions/workflow/status/snowleopard/selective/ci.yml?branch=main)](https://github.com/snowleopard/selective/actions)\n\nThis is a library for *selective applicative functors*, or just *selective functors*\nfor short, an abstraction between applicative functors and monads, introduced in\n[this paper](https://dl.acm.org/doi/10.1145/3341694).\n\n## What are selective functors?\n\nWhile you're encouraged to read the paper, here is a brief description of\nthe main idea. Consider the following new type class introduced between\n`Applicative` and `Monad`:\n\n```haskell\nclass Applicative f =\u003e Selective f where\n    select :: f (Either a b) -\u003e f (a -\u003e b) -\u003e f b\n\n-- | An operator alias for 'select'.\n(\u003c*?) :: Selective f =\u003e f (Either a b) -\u003e f (a -\u003e b) -\u003e f b\n(\u003c*?) = select\n\ninfixl 4 \u003c*?\n```\n\nThink of `select` as a *selective function application*: you **must apply** the function\nof type `a -\u003e b` when given a value of type `Left a`, but you **may skip** the\nfunction and associated effects, and simply return `b` when given `Right b`.\n\nNote that you can write a function with this type signature using\n`Applicative` functors, but it will always execute the effects associated\nwith the second argument, hence being potentially less efficient:\n\n```haskell\nselectA :: Applicative f =\u003e f (Either a b) -\u003e f (a -\u003e b) -\u003e f b\nselectA x f = (\\e f -\u003e either f id e) \u003c$\u003e x \u003c*\u003e f\n```\n\nAny `Applicative` instance can thus be given a corresponding `Selective`\ninstance simply by defining `select = selectA`. The opposite is also true\nin the sense that one can recover the operator `\u003c*\u003e` from `select` as\nfollows (I'll use the suffix `S` to denote `Selective` equivalents of\ncommonly known functions).\n\n```haskell\napS :: Selective f =\u003e f (a -\u003e b) -\u003e f a -\u003e f b\napS f x = select (Left \u003c$\u003e f) ((\u0026) \u003c$\u003e x)\n```\n\nHere we wrap a given function `a -\u003e b` into `Left` and turn the value `a`\ninto a function `($a)`, which simply feeds itself to the function `a -\u003e b`\nyielding `b` as desired. Note: `apS` is a perfectly legal\napplication operator `\u003c*\u003e`, i.e. it satisfies the laws dictated by the\n`Applicative` type class as long as [the laws](#laws) of the `Selective`\ntype class hold.\n\nThe `branch` function is a natural generalisation of `select`: instead of\nskipping an unnecessary effect, it chooses which of the two given effectful\nfunctions to apply to a given argument; the other effect is unnecessary. It\nis possible to implement `branch` in terms of `select`, which is a good\npuzzle (give it a try!).\n\n```haskell\nbranch :: Selective f =\u003e f (Either a b) -\u003e f (a -\u003e c) -\u003e f (b -\u003e c) -\u003e f c\nbranch = ... -- Try to figure out the implementation!\n```\n\nFinally, any `Monad` is `Selective`:\n\n```haskell\nselectM :: Monad f =\u003e f (Either a b) -\u003e f (a -\u003e b) -\u003e f b\nselectM mx mf = do\n    x \u003c- mx\n    case x of\n        Left  a -\u003e fmap ($a) mf\n        Right b -\u003e pure b\n```\n\nSelective functors are sufficient for implementing many conditional constructs,\nwhich traditionally require the (more powerful) `Monad` type class. For example:\n\n```haskell\n-- | Branch on a Boolean value, skipping unnecessary effects.\nifS :: Selective f =\u003e f Bool -\u003e f a -\u003e f a -\u003e f a\nifS i t e = branch (bool (Right ()) (Left ()) \u003c$\u003e i) (const \u003c$\u003e t) (const \u003c$\u003e e)\n\n-- | Conditionally perform an effect.\nwhenS :: Selective f =\u003e f Bool -\u003e f () -\u003e f ()\nwhenS x act = ifS x act (pure ())\n\n-- | Keep checking an effectful condition while it holds.\nwhileS :: Selective f =\u003e f Bool -\u003e f ()\nwhileS act = whenS act (whileS act)\n\n-- | A lifted version of lazy Boolean OR.\n(\u003c||\u003e) :: Selective f =\u003e f Bool -\u003e f Bool -\u003e f Bool\n(\u003c||\u003e) a b = ifS a (pure True) b\n\n-- | A lifted version of 'any'. Retains the short-circuiting behaviour.\nanyS :: Selective f =\u003e (a -\u003e f Bool) -\u003e [a] -\u003e f Bool\nanyS p = foldr ((\u003c||\u003e) . p) (pure False)\n\n-- | Return the first @Right@ value. If both are @Left@'s, accumulate errors.\norElse :: (Selective f, Semigroup e) =\u003e f (Either e a) -\u003e f (Either e a) -\u003e f (Either e a)\norElse x = select (Right \u003c$\u003e x) . fmap (\\y e -\u003e first (e \u003c\u003e) y)\n```\n\nSee more examples in [src/Control/Selective.hs](src/Control/Selective.hs).\n\nCode written using selective combinators can be both statically analysed\n(by reporting all possible effects of a computation) and efficiently\nexecuted (by skipping unnecessary effects).\n\n## Laws\n\nInstances of the `Selective` type class must satisfy a few laws to make\nit possible to refactor selective computations. These laws also allow us\nto establish a formal relation with the `Applicative` and `Monad` type\nclasses.\n\n* Identity:\n    ```haskell\n    x \u003c*? pure id = either id id \u003c$\u003e x\n    ```\n\n* Distributivity (note that `y` and `z` have the same type `f (a -\u003e b)`):\n    ```haskell\n    pure x \u003c*? (y *\u003e z) = (pure x \u003c*? y) *\u003e (pure x \u003c*? z)\n    ```\n* Associativity:\n    ```haskell\n    x \u003c*? (y \u003c*? z) = (f \u003c$\u003e x) \u003c*? (g \u003c$\u003e y) \u003c*? (h \u003c$\u003e z)\n      where\n        f x = Right \u003c$\u003e x\n        g y = \\a -\u003e bimap (,a) ($a) y\n        h z = uncurry z\n    ```\n* Monadic select (for selective functors that are also monads):\n    ```haskell\n    select = selectM\n    ```\n\nThere are also a few useful theorems:\n\n* Apply a pure function to the result:\n    ```haskell\n    f \u003c$\u003e select x y = select (fmap f \u003c$\u003e x) (fmap f \u003c$\u003e y)\n    ```\n\n* Apply a pure function to the `Left` case of the first argument:\n    ```haskell\n    select (first f \u003c$\u003e x) y = select x ((. f) \u003c$\u003e y)\n    ```\n\n* Apply a pure function to the second argument:\n    ```haskell\n    select x (f \u003c$\u003e y) = select (first (flip f) \u003c$\u003e x) ((\u0026) \u003c$\u003e y)\n    ```\n\n* Generalised identity:\n    ```haskell\n    x \u003c*? pure y = either y id \u003c$\u003e x\n    ```\n\n* A selective functor is *rigid* if it satisfies `\u003c*\u003e = apS`. The following\n*interchange* law holds for rigid selective functors:\n    ```haskell\n    x *\u003e (y \u003c*? z) = (x *\u003e y) \u003c*? z\n    ```\n\nNote that there are no laws for selective application of a function to a pure\n`Left` or `Right` value, i.e. we do not require that the following laws hold:\n\n```haskell\nselect (pure (Left  x)) y = ($x) \u003c$\u003e y -- Pure-Left\nselect (pure (Right x)) y = pure x     -- Pure-Right\n```\n\nIn particular, the following is allowed too:\n\n```haskell\nselect (pure (Left  x)) y = pure ()       -- when y :: f (a -\u003e ())\nselect (pure (Right x)) y = const x \u003c$\u003e y\n```\n\nWe therefore allow `select` to be selective about effects in these cases, which\nin practice allows to under- or over-approximate possible effects in static\nanalysis using instances like `Under` and `Over`.\n\nIf `f` is also a `Monad`, we require that `select = selectM`, from which one\ncan prove `apS = \u003c*\u003e`, and furthermore the above `Pure-Left` and `Pure-Right`\nproperties now hold.\n\n## Static analysis of selective functors\n\nLike applicative functors, selective functors can be analysed statically.\nWe can make the `Const` functor an instance of `Selective` as follows.\n\n```haskell\ninstance Monoid m =\u003e Selective (Const m) where\n    select = selectA\n```\n\nAlthough we don't need the function `Const m (a -\u003e b)` (note that\n`Const m (Either a b)` holds no values of type `a`), we choose to\naccumulate the effects associated with it. This allows us to extract\nthe static structure of any selective computation very similarly\nto how this is done with applicative computations.\n\nThe `Validation` instance is perhaps a bit more interesting.\n\n```haskell\ndata Validation e a = Failure e | Success a deriving (Functor, Show)\n\ninstance Semigroup e =\u003e Applicative (Validation e) where\n    pure = Success\n    Failure e1 \u003c*\u003e Failure e2 = Failure (e1 \u003c\u003e e2)\n    Failure e1 \u003c*\u003e Success _  = Failure e1\n    Success _  \u003c*\u003e Failure e2 = Failure e2\n    Success f  \u003c*\u003e Success a  = Success (f a)\n\ninstance Semigroup e =\u003e Selective (Validation e) where\n    select (Success (Right b)) _ = Success b\n    select (Success (Left  a)) f = Success ($a) \u003c*\u003e f\n    select (Failure e        ) _ = Failure e\n```\n\nHere, the last line is particularly interesting: unlike the `Const`\ninstance, we choose to actually skip the function effect in case of\n`Failure`. This allows us not to report any validation errors which\nare hidden behind a failed conditional.\n\nLet's clarify this with an example. Here we define a function to\nconstruct a `Shape` (a circle or a rectangle) given a choice of the\nshape `s` and the shape's parameters (`r`, `w`, `h`) in a selective\ncontext `f`.\n\n```haskell\ntype Radius = Int\ntype Width  = Int\ntype Height = Int\n\ndata Shape = Circle Radius | Rectangle Width Height deriving Show\n\nshape :: Selective f =\u003e f Bool -\u003e f Radius -\u003e f Width -\u003e f Height -\u003e f Shape\nshape s r w h = ifS s (Circle \u003c$\u003e r) (Rectangle \u003c$\u003e w \u003c*\u003e h)\n```\n\nWe choose `f = Validation [String]` to report the errors that occurred\nwhen parsing a value. Let's see how it works.\n\n```haskell\n\u003e shape (Success True) (Success 10) (Failure [\"no width\"]) (Failure [\"no height\"])\nSuccess (Circle 10)\n\n\u003e shape (Success False) (Failure [\"no radius\"]) (Success 20) (Success 30)\nSuccess (Rectangle 20 30)\n\n\u003e shape (Success False) (Failure [\"no radius\"]) (Success 20) (Failure [\"no height\"])\nFailure [\"no height\"]\n\n\u003e shape (Success False) (Failure [\"no radius\"]) (Failure [\"no width\"]) (Failure [\"no height\"])\nFailure [\"no width\",\"no height\"]\n\n\u003e shape (Failure [\"no choice\"]) (Failure [\"no radius\"]) (Success 20) (Failure [\"no height\"])\nFailure [\"no choice\"]\n```\n\nIn the last example, since we failed to parse which shape has been chosen,\nwe do not report any subsequent errors. But it doesn't mean we are short-circuiting\nthe validation. We will continue accumulating errors as soon as we get out of the\nopaque conditional, as demonstrated below.\n\n```haskell\ntwoShapes :: Selective f =\u003e f Shape -\u003e f Shape -\u003e f (Shape, Shape)\ntwoShapes s1 s2 = (,) \u003c$\u003e s1 \u003c*\u003e s2\n\n\u003e s1 = shape (Failure [\"no choice 1\"]) (Failure [\"no radius 1\"]) (Success 20) (Failure [\"no height 1\"])\n\u003e s2 = shape (Success False) (Failure [\"no radius 2\"]) (Success 20) (Failure [\"no height 2\"])\n\u003e twoShapes s1 s2\nFailure [\"no choice 1\",\"no height 2\"]\n```\n\n## Do we still need monads?\n\nYes! Here is what selective functors cannot do: `join :: Selective f =\u003e f (f a) -\u003e f a`.\n\n## Further reading\n\n* An ICFP'19 paper introducing selective functors: https://doi.org/10.1145/3341694.\n* An older blog post introducing selective functors: https://blogs.ncl.ac.uk/andreymokhov/selective.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnowleopard%2Fselective","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsnowleopard%2Fselective","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsnowleopard%2Fselective/lists"}