lue-bird/elm-typesafe-array

typed array that enables safe access


Keywords
elm, vector, array, type-safe
License
MIT
Install
elm-package install lue-bird/elm-typesafe-array 10.0.0

Documentation

elm-typesafe-array

Knowing more about the length of an Array at compile-time to help you access elements safely.

ticTacToeBoard
    |> Arr.at nat2 FirstToLast
    |> Arr.at nat1 FirstToLast

returns a value, not a Maybe if ticTacToeBoard's type can promise that it contains enough elements.

A type that can hold that promise could be:

{-| 3 by 3 -}
type alias TicTacToeBoard =
    Arr (Only Nat3) (Arr (Only Nat3) Field)

type Field =
    FieldEmpty
    | X
    | O

initialTicTacToeBoard : TicTacToeBoard
initialTicTacToeBoard =
    Arr.repeat nat3
        (Arr.repeat nat3 FieldEmpty)

We & the compiler know there are enough elements in initialTicTacToeBoard:

initialTicTacToeBoard
    |> Arr.at nat2 FirstToLast
    |> Arr.at nat1 FirstToLast
--> FieldEmpty

Setup

If you want, take a 👀 to get a feel why the used packages are useful.

elm install lue-bird/elm-linear-direction
elm install lue-bird/elm-typed-value
elm install lue-bird/elm-bounded-nat
elm install lue-bird/elm-typesafe-array
import LinearDirection exposing (LinearDirection(..))

import Nat exposing (Nat, Only, In, Min)
import InNat
import NNats exposing (..)
    -- (..) is nat0 to nat160

import Typed

import TypeNats exposing (..)
    -- (..) is Nat0 to Nat160 & Nat1Plus to Nat160Plus

import Arr exposing (Arr)
import InArr
import MinArr

You can define & use operations for Arrs with a certain amount.

a minimum length?

first :
    Arr (In (Nat1Plus orMore_) max_) element
    -> element
first =
    Arr.at nat0 FirstToLast

biggest :
    Arr (In (Nat1Plus orMore_) max_) comparable
    -> comparable
biggest =
    Arr.foldWith FirstToLast max

first Arr.empty --> compile-time error
biggest Arr.empty --> compile-time error

Arr (In (Nat1Plus orMore_) max_) means what exactly? → It constrains the length of possible Arrs.

The types are explained in more detail in bounded-nat (only In, Min & Only is used for Arrs). In this example:

  • Arr: Array with additional type info about its length
    • In: length is within a minimum (& maximum)
      • Nat1Plus orMore_: the minimum length is >= 1
      • max_: no maximum length or any maximum length

an exact length?

As we've seen in the tic-tac-toe example.

{-| 8 by 8 -}
type alias ChessBoard =
    Arr (Only Nat8) (Arr (Only Nat8) Field)

type Field
    = Empty
    | Piece PieceKind Color

type PieceKind = Pawn | --...
type Color = Black | White

initialChessBoard : ChessBoard
initialChessBoard =
    let
        pawnRow color =
            Arr.repeat nat8 (Piece Pawn color)

        firstRow color =
            Arr.repeat nat8 (Piece Other color)
    in
    Arr.empty
        |> InArr.push (firstRow White)
        |> InArr.push (pawnRow White)
        |> InArr.extend nat4
            (Arr.repeat nat4 (Arr.repeat nat8 Empty))
        |> InArr.push (pawnRow Black)
        |> InArr.push (firstRow Black)

initialChessBoard
    |> Arr.at nat1 FirstToLast
    |> Arr.at nat6 FirstToLast
--> Piece Pawn White

a maximum length?

-- the max tag count should be 53
tag : Arr (In min_ Nat53) Tag -> a -> Tagged a
tag tags toTag = --...

tag (Arr.from3 "fun" "easy" "fresh")
--> valid

tag (Arr.repeat nat100 EmptyTag)
--> compile-time error

Now take a look at modules like Arr to get started!

comparison to Orasund's static-array

I started creating my package before this one so I didn't take inspiration from this package.

creation

six = StaticArray.Length.five |> StaticArray.Length.plus1

StaticArray.fromList six 0[1,2,3,4,5]

vs

Arr.from6 0 1 2 3 4 5

appending

staticArray1, staticArray2 : StaticArray Six ...

let
    array1 =
        staticArray1 |> StaticArray.toRecord
    
    array2 =
        staticArray2 |> StaticArray.toRecord
in
StaticArray.fromRecord
    { length = StaticArray.Length.twelve
    , head = array1.head
    , tail = Array.append (array1.tail |> Array.push array2.head) array2.tail
    }

important note from static-array:

Notice that we can NOT do addition in compile time, therefore we need to construct 6+6 manually.

→ You could easily do

wrong =
    StaticArray.fromRecord
        { length = StaticArray.Length.eight
        , head = array1.head
        , tail = Array.empty
        }

→ length in the type doesn't match the actual length

wrong
    |> StaticArray.get
        (StaticArray.Index.last StaticArray.Length.eight)

It silently gave us back an element at the wrong (first) index!

vs

arr1, arr2 : Arr (In Nat6 (Nat6Plus a_)) ...

arr1 |> InArr.extend nat6 arr2
-- : Arr (In Nat12 (Nat12Plus a_)) ...

type-safe.

a length in a range

maybePush : Maybe a -> StaticArray n a -> -- what is the result?!

type MaybePushResult lengthBefore
    = Pushed
        (StaticArray    
            (StaticArray.Index.OnePlus lengthBefore)
        )
    | DidntPush (StaticArray lengthBefore)

maybePush : Maybe a -> StaticArray n a -> MaybePushResult n
-- unconvenient

vs

maybePush :
    Maybe a
    -> Arr (In min max) a
    -> Arr (In min (Nat1Plus max)) a