lue-bird/elm-keysset/4.0.2

NOTE: the latest version is 5.0.2

Order

Comparing things

Build tagged compare functions for basic types, records, type choices. All without comparable, number, ...

import Order exposing (Ordering)
import Int.Order
import Char.Order
import String.Order
import Map exposing (Mapping)


type FirstName
    -- no exposing (..)
    = FirstName


firstName : Mapping User FirstName String
firstName =
    Typed.tag FirstName (\(User user) -> user.firstName)


type LastName
    -- no exposing (..)
    = LastName


lastName : Mapping User LastName String
lastName =
    Typed.tag LastName (\(User user) -> user.lastName)


type Age
    -- no exposing (..)
    = Age


age : Mapping User Age String
age =
    Typed.tag Age (\(User user) -> user.age)


type User
    = User
        { firstName : String
        , lastName : String
        , age : Int
        }


type alias NameOrder =
    String.Order.Earlier
        (Char.Order.AToZ Char.Order.LowerUpper)


type alias Order =
    Order.NextOnTie
        (Order.By LastName NameOrder)
        (Order.NextOnTie
            (Order.By FirstName NameOrder)
            (Order.By Age Order.Int.Up)
        )


order : Ordering User Order
order =
    Order.by lastName
        (String.Order.earlier
            (Char.Order.aToZ Char.Order.lowerUpper)
        )
        |> Order.onTie
            (Order.by .firstName
                (String.Order.earlier
                    (Char.Order.aToZ Char.Order.lowerUpper)
                )
            )
        |> Order.onTie
            (Order.by .age Int.Order.up)


[ User { firstName = "Andy", lastName = "Baldwin", age = 90 }
, User { firstName = "Bert", lastName = "Anderson", age = 23 }
, User { firstName = "Alec", lastName = "Anderson", age = 8 }
, User { firstName = "Alec", lastName = "Anderson", age = 100 }
]
    |> List.sortWith (Order.with userOrder)
--> [ { firstName = "Alec", lastName = "Anderson", age = 8 }
--> , { firstName = "Alec", lastName = "Anderson", age = 100 }
--> , { firstName = "Bert", lastName = "Anderson", age = 23 }
--> , { firstName = "Andy", lastName = "Baldwin", age = 90 }
--> ]

↑ another example

type alias Ordering complete tag =
Mapping ( complete, complete ) tag Order

How 2 thing compare to each other. An Ordering can be used to sortWith, make comparisons, ...

cardsSort : List Card -> List Card
cardsSort =
    List.sortWith (Order.with cardOrder)


cardOrder : Ordering Card

create

tie : Ordering complete_ Tie

Always EQ for any 2 things

Order.with tie 5555 -12345 --> EQ
type Tie

Tag for tie

reverse : Ordering complete tag -> Ordering complete (Reverse tag)

a < b ⇆ a > b

import Char.Order
import String.Order


[ "b", "c", "a" ]
    |> (List.sortWith << Order.with)
        (String.Order.earlier
            (Char.Order.aToZ Order.tie)
        )
--> [ "a", "b", "c" ]


[ "b", "c", "a" ]
    |> (List.sortWith << Order.with)
        (String.Order.earlier
            (Char.Order.aToZ Order.tie)
            |> Order.reverse
        )
--> [ "c", "b", "a" ]
type alias Reverse orderTag =
( ReverseTag, orderTag )

Tag for reverse

Tag wrapper for Reverse

combine

by :
Mapping complete mapTag mapped
-> Ordering mapped mappedOrderTag
-> Ordering complete (By mapTag mappedOrderTag)

Order by a transformed value

access a part

import Typed
import String.Order
import Char.Order
import Map exposing (Mapping)


type Name
    = Name


name : Mapping { name : String } Name String
name =
    Typed.tag Name .name


[ { name = "Bo" }, { name = "Amy" }, { name = "Cam" } ]
    |> (List.sortWith << Order.with)
        (Order.by name
            (String.Order.earlier (Char.Order.aToZ Order.tie))
        )
--> [ { name = "Amy" }, { name = "Bo" }, { name = "Cam" } ]


{-| `Order` `Tuple.first`, then on tie `Tuple.second`


    Order.tuple ( Int.Order.up, Int.Order.up ) ( 0, 2 ) ( 0, -2 )
    --→ GT
-}
tuple :
    ( Ordering part0 part0OrderTag
    , Ordering part1 part1OrderTag
    )
    -> Ordering
            ( part0, part1 )
            (Order.OnTieNext
                (Order.By Tuple.Map.Part0 part0OrderTag)
                (Order.By Tuple.Map.Part1 part1OrderTag)
            )
tuple ( part0Order, part1Order ) =
    Order.by Tuple.Map.part0 part0Order
        |> Order.onTie
            (Order.by Tuple.Map.part1 part1Order)

unwrap a type union constructor

import Char.Order
import Order exposing (Ordering)
import String.Order


type User
    = User { name : String }


userNameOrder : Ordering Username
userNameOrder =
    Order.by (\(User user) -> user)
        (Order.by .name
            (String.Order.earlier
                (Char.Order.aToZ Char.Order.lowerUpper)
            )
        )

rank a simple choice

type Case
    = Lower
    | Upper


{-| `'A' < 'a'`
-}
upperLower : Ordering Case
upperLower =
    Order.by
        (\case_ ->
            case case_ of
                Upper ->
                    0


                Lower ->
                    1
        )
        Int.Order.up

neat!

type alias By mapTag mappedOrderTag =
( ByTag, ( mapTag, mappedOrderTag ) )

Tag for by

type ByTag

Wrapper tag for By

Ordering complete tieBreakingTag
-> Ordering complete tag
-> Ordering complete (OnTieNext tag tieBreakingTag)

Prioritize the Ordering by one aspect and break ties with the following

import Order exposing (Ordering)
import RecordWithoutConstructorFunction exposing (RecordWithoutConstructorFunction)


type alias Card =
    RecordWithoutConstructorFunction
        { suite : Suite, value : Value }

(RecordWithoutConstructorFunction tricks elm into not creating a Card function)

type Suite
    = Clubs
    | Hearts
    | Diamonds
    | Spades


type Value
    = Two
    | Three
    | Four
    | Five
    | Six
    | Seven
    | Eight
    | Nine
    | Ten
    | Jack
    | Queen
    | King
    | Ace


cardOrder : Ordering Card (Order.OnTieNext (Order.By Suite SuiteOrder) (Order.By Value ValueOrder))
cardOrder =
    Order.by suite suiteOrder
        |> Order.onTieNext
            (Order.by value valueOrder)


type SuiteToInt
    = SuiteToInt


suiteToInt : Mapping Suite SuiteToInt Int
suiteToInt =
    Typed.tag SuiteOrder
        (\suite ->
            case suite of
                Clubs ->
                    0


                Hearts ->
                    1


                Diamonds ->
                    2


                Spades ->
                    3
        )


type SuiteOrder =
    Order.By SuiteToInt Int.Order.Up


suiteOrder : Ordering Suite SuiteOrder
suiteOrder =
    Order.by suiteToInt Int.Order.up

onTie can also be used to order different variants and values → see on

type alias OnTieNext orderTag tieBreakingOrderTag =
( OnTieNextTag, ( orderTag, tieBreakingOrderTag ) )

Tag for onTie

Tag wrapper for OnTieNext

choose

on :
Mapping choice toPossibilityTag (Maybe possibilityAttachment)
-> Ordering possibilityAttachment possibilityOrderTag
-> Ordering choice (On toPossibilityTag possibilityOrderTag)

Try ordering by a given parsed value. In comparison to Order.by, mapping can produce Nothing in which case any Just value is greater.

Continue by adding all possibilities with Order.onTie

-- module Card exposing (Card(..), CardNormal, order)




import Order exposing (Ordering)
import RecordWithoutConstructorFunction exposing (RecordWithoutConstructorFunction)


type Card
    = Normal CardNormal
    | Joker


type alias CardNormal =
    RecordWithoutConstructorFunction
        { value : Value, suite : Suite }


type ToNormal
    = ToNormal


normal : Mapping Card ToNormal (Maybe CardNormal)
normal =
    Typed.tag ToNormal
        (\card ->
            case card of
                Normal normalCard ->
                    Just normalCard


                Joker ->
                    Nothing
        )


type alias NormalOrder =
    Order.OnTieNext
        (Order.By Suite SuiteOrder)
        (Order.By Value ValueOrder)


normalOrder : Ordering CardNormal NormalOrder
normalOrder =
    Order.by suite suiteOrder
        |> Order.onTie
            (Order.by value valueOrder)


order : Ordering Card (Order.On ToNormal NormalOrder)
order =
    Order.on toNormal normalOrder


type Normal
    = Normal


toNormal : Mapping Card Normal (Maybe CardNormal)
toNormal =
    \card ->
        case card of
            Normal normal ->
                normal |> Just


            Joker ->
                Nothing

-- Advanced territory --

Recursive types need special treatment so we don't run into recursive types: Recursive Orderings need a separate tag, e.g.

import Map
import Order
import Typed


type alias Earlier elementOrder =
    Order.OnTieNext
        (Order.On Empty Order.Tie)
        (Order.On
            Cons
            (Order.OnTieNext
                (Order.By ConsElement elementOrder)
                (Order.By ConsList ( EarlierRecursive, elementOrder ))
            )
        )


type Empty
    = Empty


toEmpty : Mapping (List element) Empty (Maybe ())
toEmpty =
    Typed.tag Empty
        (\list ->
            case list of
                [] ->
                    Just ()


                _ :: _ ->
                    Nothing
        )


type Cons
    = Cons


toCons : Mapping (List element) Cons (Maybe { element : element, list : List element })
toCons =
    Typed.tag Cons
        (\list ->
            case list of
                [] ->
                    Nothing


                element :: list ->
                    Just { element = element, list = list }
        )


type ConsElement
    = ConsElement


consElement : Mapping { element : element, list : List element } ConsList (List element)
consElement =
    Typed.tag ConsList .list


type ConsList
    = ConsList


consList : Mapping { element : element, list : List element } ConsList (List element)
consList =
    Typed.tag ConsList .list


type EarlierRecursive
    = EarlierRecursive


order : Ordering element elementOrder -> Ordering (List element) (Earlier elementOrder)
order elementOrder =
    let
        listRecursiveOrder : () -> Ordering (List element) ( EarlierRecursive, elementOrder )
        listRecursiveOrder () =
            Typed.mapToWrap EarlierRecursive order
    in
    Order.on toEmpty Order.tie
        |> Order.onTie (Order.on toCons Order.by)

a bit much boilerplate.

type alias On toPossibility order =
( OnTag, ( toPossibility, order ) )

Tags an attached on possibility Mapping-Ordering pair

type OnTag

Tag wrapper for On

transform

with : Ordering complete tag_ -> complete -> complete -> Order

Convert to a function in the form a -> a -> Order that can be used to sortWith, make comparisons, ...

cardsSort : List Card -> List Card
cardsSort =
    List.sortWith (Order.with cardOrder)


cardOrder : Ordering Card

prior art