lue-bird/elm-emptiness-typed/1.0.0

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.Just { notEmpty : () }


type alias Emptiable =
    MaybeIs.Nothingable { emptiable : () }


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 MaybeIs justOrNothing a
= IsNothing justOrNothing
| IsJust a

Like Maybe with type level information about whether it exists.

See Just and Nothingable.

type alias Just tag =
CanBeNothing Never tag

Only allow MaybeIss that certainly exist as arguments.

import MaybeIs exposing (Just, MaybeIs)


head : MaybeIs (Just tag_) ( a, List a ) -> a
head =
    MaybeIs.value >> Tuple.first

See CanBeNothing and ListIs.

type alias Nothingable tag =
CanBeNothing () tag

MaybeIs (Nothingable tag): The value could exist, could also not exist.

See CanBeNothing.

type CanBeNothing valueIfNothing tag
= CanBeNothing valueIfNothing

The value attached to a IsNothing:

It also has a simple type tag to make MaybeIs values distinct:

type alias NotEmpty =
    MaybeIs.Just { notEmpty : () }


type alias ItemFocussed =
    MaybeIs.Just { itemFocussed : () }

Remember to update the tag after renaming an alias.

create

just : value -> MaybeIs just_ value

A MaybeIs that certainly exists.

MaybeIs.just "you" |> MaybeIs.value
--> "you"
nothing : MaybeIs (Nothingable tag_) a_

Nothing here.

fromMaybe : Maybe a -> MaybeIs (Nothingable tag_) a

Convert a Maybe to a MaybeIs.

transform

map : (a -> b) -> MaybeIs justOrNothing a -> MaybeIs justOrNothing 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 justOrNothing a
-> MaybeIs justOrNothing b
-> MaybeIs justOrNothing 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 (Just tag_) value -> value

Safely extracts the value from a MaybeIs Just value.

import MaybeIs exposing (just)


just (just (just "you"))
    |> MaybeIs.value
    |> MaybeIs.value
    |> MaybeIs.value
--> "you"
(a -> MaybeIs justOrNothing b)
-> MaybeIs justOrNothing a
-> MaybeIs justOrNothing b

Chain together many computations that may fail.

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


parse : String -> MaybeIs Nothingable Parsed
extraValidation : Parsed -> MaybeIs Nothingable Validated
(canBeNothing -> value)
-> MaybeIs (CanBeNothing canBeNothing tag_) 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 (Just tag_) a -> MaybeIs just_ a

When using Just/NotEmpty/... for an argument:

theShorter :
    ListIs NotEmpty 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

else
    aList |> ListType.toTuple |> ListType.fromTuple

also known as: necessary code that nobody will understand.

else
    aList |> MaybeIs.branchableType

is a bit better.

💙 Found a better name? → open an issue.