lue-bird/elm-emptiness-typed/2.0.1

NOTE: the latest version is 9.2.1

MaybeIs

Maybe with the ability to know at the type level whether it exists.

import MaybeIs exposing (just)


[ just 1, just 7 ]
    --: List (MaybeIs just_ number_)
    |> List.map MaybeIs.value
--> [ 1, 7 ]

MaybeIs alone will probably not proof any useful, but we can build cool type-safe data structures with it:

type alias ListIs emptyOrNot a =
    MaybeIs emptyOrNot ( a, List a )


type alias NotEmpty =
    MaybeIs.CanBe { empty : () } Never


type alias Emptiable =
    MaybeIs.CanBe { empty : () } ()


empty : ListIs Emptiable a_


cons : ListIs emptyOrNot_ a -> a -> ListIs notEmpty_ a


head : ListIs NotEmpty a -> a

This is exactly how ListIs is implemented.

types

type CanBe stateTag neverOrValue
= CanBe neverOrValue

CanBe is just a cleaner version of this. It has a simple type tag to make Never values distinct:

type alias NotEmpty =
    CanBe { empty : () } Never


type alias Item =
    CanBe { hole : () } Never

Remember to update the tag after renaming an alias.

Now the fun part:

joinParts :
    HoleyFocusList (CanBe hole_ yesOrNever) a
    -> ListIs (CanBe empty_ yesOrNever) a
joinParts ... =
    case ( before, focus, after ) of
        ( [], StringEmpty (CanBe yesOrNever), [] ) ->
            IsNothing
                --↓ carries over the `yesOrNever` type,
                --↓ while allowing a new tag
                (CanBe yesOrNever)


        ... -> ...

the type information gets carried over, so

HoleyFocusList.Item -> ListIs.NotEmpty
CanBe hole_ () -> CanBe empty_ ()

Read more in the readme!

type MaybeIs justOrNothing a
= IsNothing justOrNothing
| IsJust a

Like Maybe with type level information about whether it exists. See CanBe.

create

just : value -> MaybeIs just_ value

A MaybeIs that certainly exists.

MaybeIs.just "you" |> MaybeIs.value
--> "you"
nothing : MaybeIs (CanBe possibleStateTag_ ()) a_

Nothing here.

fromMaybe : Maybe a -> MaybeIs (CanBe possibleStateTag_ ()) a

Convert a Maybe to a MaybeIs.

transform

map :
(a -> b)
-> MaybeIs (CanBe nothingTag_ yesOrNever) a
-> MaybeIs (CanBe mappedNothingTag_ yesOrNever) b

Transform the value in the MaybeIs using a given function:

import MaybeIs exposing (just, nothing)


MaybeIs.map abs (just -3) --> just 3
MaybeIs.map abs nothing --> nothing
(a -> b -> combined)
-> MaybeIs (CanBe aNothingTag_ yesOrNever) a
-> MaybeIs (CanBe bNothingTag_ yesOrNever) b
-> MaybeIs (CanBe combinedNothingTag_ yesOrNever) combined

If all the arguments exist, combine them using a given function.

import MaybeIs exposing (just, nothing)


MaybeIs.map2 (+) (just 3) (just 4) --> just 7
MaybeIs.map2 (+) (just 3) nothing --> nothing
MaybeIs.map2 (+) nothing (just 4) --> nothing
toMaybe : MaybeIs justOrNothing_ a -> Maybe a

Convert a MaybeIs to a Maybe.

value : MaybeIs (CanBe impossibleStateTag_ Never) value -> value

Safely extracts the value from a MaybeIs (CanBe nothingTag_ Never) value.

import MaybeIs exposing (just, MaybeIs)


just (just (just "you"))
    |> MaybeIs.value
    |> MaybeIs.value
    |> MaybeIs.value
--> "you"


head : MaybeIs (CanBe empty_ Never) ( a, List a ) -> a
head =
    MaybeIs.value >> Tuple.first

See CanBe and ListIs.

(a -> MaybeIs (CanBe thenNothingTag yesOrNever) b)
-> MaybeIs (CanBe nothingTag_ yesOrNever) a
-> MaybeIs (CanBe thenNothingTag yesOrNever) b

Chain together many computations that may fail.

maybeString
    |> MaybeIs.andThen parse
    |> MaybeIs.andThen extraValidation


parse : String -> MaybeIs CanBe Parsed
extraValidation : Parsed -> MaybeIs CanBe Validated
(unitOrNever -> value)
-> MaybeIs (CanBe stateTag_ unitOrNever) value
-> value

Lazily use a fallback value if the MaybeIs is nothing.

import Dict


Dict.empty
    |> Dict.get "Tom"
    |> MaybeIs.fromMaybe
    |> MaybeIs.withFallback (\() -> "unknown")
--> "unknown"

Hint: MaybeIs.withFallback never is equivalent to MaybeIs.value.

type-level

branchableType : MaybeIs (CanBe impossibleStateTag_ Never) a -> MaybeIs just_ a

When using CanBe ... Never for an argument:

theShorter :
    ListIs (CanBe { empty : () } Never) a
    -> ListIs emptyOrNot a
    -> ListIs emptyOrNot a
theShorter aList bList =
    if ListIs.length bList > ListIs.length aList then
        bList


    else
        --↓ `NotEmpty` but we need `emptyOrNot`
        aList

to make both branches return emptyOrNot, we could use

aList |> ListType.unCons |> ListType.fromUnConsed

also known as: necessary code that nobody will understand.

aList |> MaybeIs.branchableType

is a bit better.

💙 Found a better name? → open an issue.