Home

Awesome

SelectionFoldable

The SelectionFoldable type represents a Foldable structure (eg. Array, List) of items where zero or one of the items is selected.

Also provided is the SelectionFoldableWithData type, which also keeps some user-provided data along with the selected item (the SelectionFoldable type is actually just a type alias for a SelectionFoldableWithData where there is no data).

Inspired by the excellent Elm list-selection package written by NoRedInk.

Usage examples

Note 1: All of the code examples in this README assume the following imports (common imports like Prelude are implicit):

import Data.SelectionFoldable as SF
import Data.SelectionFoldableWithData as SFWD

Note 2: These examples use the # operator, which is left-to-right function application:

1 # (\n -> n + 2) -- Add 2
-- 3

[1,2,3] # map (\n -> n + 1) -- Add one to each number
-- [2,3,4]

[1,2,3]
    # map (\n -> n + 1) -- 1. Add one to each number, then
    # map (\n -> n < 4) -- 2. Map the numbers to bools
-- [true,true,false]

Creating:

Any Foldable structure can be plugged in. Items and associated data can be of any type:

-- Using an Array:
x1 :: SF.SelectionFoldable Array Int
x1 = SF.fromFoldable [1,2,3]

-- Using a List:
x2 :: SF.SelectionFoldable List String
x2 = SF.fromFoldable (Cons "a" Nil)

-- This seems a bit silly, but it's possible!
x3 :: SF.SelectionFoldable Maybe Boolean
x3 = SF.fromFoldable (Just true)

-- Here `Bool` is the type of user-provided data associated with selected items.
-- The compiler will often require you to provide this type annotation.
x4 :: SFWD.SelectionFoldableWithData Array Bool Int
x4 = SFWD.fromFoldable [1,2,3]

-- Any types will work!
data Foo = A | B
x5 :: SFWD.SelectionFoldableWithData Array Foo Foo
x5 = SFWD.fromFoldable [A,B]

Selecting by equality:

Using select or selectIndex will select items by equality to the provided item (this obviously requires an Eq instance for the item type):

x1 :: SF.SelectionFoldable Array Int
x1 = SF.fromFoldable [1,3,9] # SF.select 3 -- Selects the second element
-- SF.selected x1 = Just 3 :: Maybe Int

x2 :: SF.SelectionFoldable Array Int
x2 = SF.fromFoldable [1,3,9] # SF.select 2 -- Selects nothing
-- SF.selected x2 = Nothing :: Maybe Int

x3 :: SF.SelectionFoldable Array String
x3 = SF.fromFoldable ["a","b","c"] # SF.selectIndex 0 -- Selects the 'a'
-- SF.selected x3 = Just "a" :: Maybe String

x4 :: SF.SelectionFoldable Array Int
x4 = SF.fromFoldable [1,3,9] # SF.select 10 -- Selects nothing (out of bounds)
-- SF.selected x4 = Nothing :: Maybe Int

x4 :: SF.SelectionFoldableWithData Array String Int
x4 = SF.fromFoldable [1, 0, 2]
    # SF.select "woo!" 2 -- Selects the 2, and stores the string "woo!" with it
-- SF.selected x4 = Just (Tuple "woo!" 2) :: Maybe (Tuple String Int)

Selecting with a function:

Using selectWith or selectWithIndex will select items using the provided function:

x1 :: SF.SelectionFoldable Array Int
x1 = SF.fromFoldable [1,3,9]
    # SF.selectWith (_ > 1) -- Selects the second element
-- SF.selected x1 = Just 3 :: Maybe Int

x2 :: SF.SelectionFoldable Array Int
x2 = SF.fromFoldable [1,3,9]
    # SF.selectWith (_ < 1) -- Selects nothing
-- SF.selected x2 = Nothing :: Maybe Int

x3 :: SF.SelectionFoldable Array Int
x3 = SF.fromFoldable [1, 0, 2]
    # SF.selectWithIndex (\i s -> i == s) -- Selects the 2
-- SF.selected x3 = Just 2 :: Maybe Int

This does not require an Eq instance for the item type to work:

data Foo = A | B -- no Eq instance for Foo

x1 :: SF.SelectionFoldable Array String
x1 = SF.fromFoldable [A, B]
    # SF.selectWith (\x -> case x of
        A -> false
        B -> true
    ) -- selects the B

x2 :: Maybe Int
x2 = SF.selected x1
    # map (\x -> case x of
        A -> 1
        B -> 2
    ) -- Just 2 :: Maybe Int

Mapping:

The selected item (if it exists) gets mapped as well as the items in the structure:

x1 :: SF.SelectionFoldable Array Int
x1 = SF.fromFoldable [1,3,9]
    # SF.selectIndex 0 -- Selects the '1'
    # map (\n -> n + 1)
-- SF.toFoldable x1 = [2,4,10] :: Array Int
-- SF.selected x1 = Just 2 :: Maybe Int

x2 :: SF.SelectionFoldable Array Bool
x2 = SF.fromFoldable [1,3,9]
    # SF.select 10 -- Selects nothing (out of bounds)
    # map (\n -> n + 1)
-- SF.toFoldable x1 = [2,4,10] :: Array Int
-- SF.selected x1 = Nothing :: Maybe Int

Folding:

SF.fromFoldable [1,2,3]
    # SF.select 1
    # SF.foldrSelected (\isSelected x z ->
        if isSelected then
            (show x <> "!") : z
        else
            (show x) : z
    ) []
-- ["1!","2","3"] :: Array String

SFWD.fromFoldable [1,2,3]
    # SFWD.select "!" 1
    # SFWD.foldrSelected
        { sel: \(Tuple s x) z -> (show x <> s) : z
        , rest: \x z -> (show x) : z
        } []
-- ["1!","2","3"] :: Array String

See test/Test/Main.purs for further examples.

Invariants

It's impossible to select an item that isn't found in the Foldable structure.

This guarantee comes out of the fact that the data constructor is private, and the exposed functions maintain the invariant.

Regarding uniqueness of items

No guarantees are made with regards to the uniqueness of items in the structure.

Using the select function (or selectWith, etc) selects the first matching item as expected:

xs = SF.fromFoldable [1,2,3] # SF.selectWith (\x -> x < 3)

SF.selected xs -- Just 1

However, when using mapSelected (or foldrSelected) to map a function f over a SelectionFoldable where the selected item appears more than once in the list, true will be passed as the IsSelected argument for each instance of the selected item:

-- Here, both the first and last elements are equal to the selected item.
xs = SF.fromFoldable [1,2,1] # SF.select 1

mapSelected (\isSelected x -> if isSelected then "a" else "b") xs
-- (SelectionFoldableWithData ["a","b","a"] Just (Tuple unit "a")))
-- Note how both the first and last items were treated as if they were selected

Developing

  1. Install purescript: npm install -g purescript
  2. Install bower: npm install -g bower
  3. Install pulp: npm install -g pulp
  4. Install dependencies: npm install && bower install
  5. Run tests: pulp test

License

Licensed under a BSD 3-Clause license

Documentation