one type for emptiable and safe non-empty
There are many types that promise non-emptiness. One example: MartinSStewart's NonemptyString.
There cool thing is that fromInt, fromChar, etc. keep these compile-time promises, so that head, tail, etc. are guaranteed to succeed and you don't have to carry Maybes throughout your program.
How about this: A string type that allows the same operations for non-empty and emptiable values:
toUpper : String emptyOrNot -> String emptyOrNot
length : String emptyOrNot -> Int
...or even allows passing the (im)possibility of a state from one data structure to another?
toCharList : String emptyOrNot -> List emptyOrNot -- crazy!All this is very much possible!
Let's try stuff out and see how where we end up:
type String emptyOrNot
= StringEmpty emptyOrNot
| StringNotEmpty Char String
char : Char -> StringIs notEmpty_
char onlyChar =
StringNotEmpty onlyChar ""
head : StringIs Never -> Char
head string =
case string of
StringEmpty empty ->
never empty --! neat
StringNotEmpty headChar _ ->
headChar
head (char 'E') --> 'E'
head (StringEmpty ()) --> errorthe type StringIs Never limits arguments to just StringNotEmpty.
to make the type argument name more descriptive, we could define
type alias NotEmpty =
Never
type alias Emptiable =
()not a good idea:
Html NotEmptyNext try:
type alias NotEmpty =
{ canBeEmpty : Never }almost there!
StringIs () would then refer to an emptiable. Nice!
Now let's create toCharList that carries the emptiness-information over:
toCharList : StringIs WAIT -> ListIs HOW_CAN_I_DO_THIS Charseems like we need
type alias Emptiable = { canBeEmpty : () }
type alias NotEmpty = { canBeEmpty : Never }
toCharList :
StringIs { canBeEmpty : unitOrNever }
-> ListIs { ... : unitOrNever } CharCanBe 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 : () } NeverAlso, what needed to be written as
type alias Emptiable =
{ canBeEmpty : () }
emptyString : StringIs Emptiable
emptyString =
StringEmpty { canBeEmpty = () }becomes simply
emptyString : StringIs (CanBe empty_ ())
emptyString =
StringEmpty (CanBe ())Now the fun part:
toCharList :
StringIs (CanBe emptyString_ yesOrNever) a
-> ListIs (CanBe emptyList_ yesOrNever) a
toCharList string =
case string of
StringEmpty (CanBe yesOrNever) ->
IsNothing
--↓ carries over the `yesOrNever` type,
--↓ while allowing a new tag
(CanBe yesOrNever)
StringNotEmpty headChar tailString ->
ListIs.fromCons headChar (tailString |> String.toList)the type information gets carried over, so
StringIs.NotEmpty -> ListIs.NotEmpty CanBe emptyString_ () -> CanBe emptyList_ ()
MaybeIs is just a convenience layer for an optional-able value
where a CanBe value is attached to its nothing variant.
type alias StringIs emptyOrNot =
MaybeIs emptyOrNot ( Char, String )
MaybeIs.map StringIs.head
--: StringIs (CanBe empty_ yesOrNever)
--: -> MaybeIs (CanBe nothing_ yesOrNever) CharA StringIs acts like a type-safe Maybe NonEmptyString!
Let's create some data structures!
ListIsHandle Emptiable and NotEmpty lists at once.
MaybeIs emptyOrNot ( a, List b ) is a ListIs emptyOrNot a.
More correctly, it's a ListWithHeadType a emptyOrNot b.
NotEmpty allows safe Maybe-free head, tail, fold (useful for finding the maximum, etc. some call it "fold1"), ...
import ListIs
ListIs.empty -- ListIs Emptiable a_
|> ListIs.appendNotEmpty
(ListIs.fromCons 1 [ 2, 3 ])
-- ListIs notEmpty_ Int
|> ListIs.cons 5 -- ListIs notEmpty_ Int
|> ListIs.unCons
--> ( 5, [ 1, 2, 3 ] )HoleyFocusListA list zipper that can also focus before and after every item.
import HoleyFocusList
HoleyFocusList.empty -- HoleyFocusList (CanBe hole_ ()) a_
|> HoleyFocusList.plug 5 -- HoleyFocusList item_ Int
|> HoleyFocusList.append [ 1, 2, 3 ]
-- HoleyFocusList item_ Int
|> HoleyFocusList.nextHole -- HoleyFocusList (CanBe hole_ ()) Int
|> HoleyFocusList.toList
--> [ 5, 1, 2, 3 ]→ zwilias's holey-zipper with a type-safe implementation using MaybeIs and other minor tweaks.
→ See contributing.md