Giter VIP home page Giter VIP logo

purescript-fast-vect's Introduction

purescript-fast-vect ๐Ÿ†

Fast, type-safe vector libary for Purescript inspired by Idris. A vector is list with its size encoded in the type.

TOC

Installation

spago install fast-vect

Long story

A vector is a list (or an Array in the case of Purescript) that has its size encoded in the type. For instance in Idris you can define a vector like this:

vect : Vect 3 Int 
vect = [1,2,3]

Note the value 3 in the type position, indicating that the vector has exactly three elements.

In purescript-fast-vect we can define the same three element vector like this:

vect :: Vect 3 Int
vect = 1 : 2 : 3 : empty

What is this good for you ask? Well, now that we have the size encoded in the type, the compiler can help us find errors. For instance, the compiler can tell us when we try to access the head of an empty list.

This leads to a slightly different design of the api. E.g. the head function in Data.Array has the following type signature:

head :: forall a. Array a -> Maybe a

So if you call head on an Array, you have to handle the Maybe. In contrast, head in Data.FastVect has the following type signature (conceptually - the real one is slightly more complex) :

head :: forall m elem. Vect m elem -> elem

You will get an elem back, no need to handle a Maybe. And this operation is always safe, because in the case that the vector is empty you will get a compile-time error.

Similarly, the index function has the following type signature (conceptually - the real one is slightly more complex):

index :: forall i m elem. Term i -> Vect m elem -> elem

If the index i (represented as a typelevel symbol) is in bounds, i.e. i < m, you will get an elem back, otherwise you will get a compile-time error.

Further functions that are defined differently to the Array functions are:

  • take is guaranteed to return you a vector with the number of elements requested and result in a compile-time error if you are trying to request more elements than are in the vector.
  • drop is guaranteed to drop the exact number of elements from the vector and result in a compile-time error if you are trying to drop more elements than exist in the vector.

You can find the full api on pursuit.

Example usage

import Prelude

import Data.FastVect.FastVect (Vect)
import Data.FastVect.FastVect as FV
import Typelevel.Arithmetic.Add (Term, term)

as :: Vect 300 String
as = FV.replicate (term :: Term 300) "a"
-- Note you could also leave out the Term type annotation, as PS can infer it:
-- as = FV.replicate (term :: _ 300) "a"

bs :: Vect 200 String
bs = FV.replicate (term :: Term 200) "b"

cs :: Vect 500 String
cs = FV.append as bs

ds :: Vect 2 String
ds = cs # FV.drop (term :: Term 299) # FV.take (term :: Term 2)

x :: String
x = FV.index (term :: Term 499) cs

y :: String
y = FV.head (FV.singleton "a")

big1 :: Vect 23923498230498230420 String
big1 = FV.replicate (term :: Term 23923498230498230420) "a"

big2 :: Vect 203948023984590684596840586 String
big2 = FV.replicate (term :: Term 203948023984590684596840586) "b"

big :: Vect 203948047908088915095071006 String
big = FV.append big1 big2
-- Note the big example will blow up during runtime.

Contributors

In order of contribution:

purescript-fast-vect's People

Contributors

albertprz avatar artemissystem avatar dependabot[bot] avatar jamieballingall avatar pete-murphy avatar sigma-andex avatar yukikurage avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

purescript-fast-vect's Issues

Difference between Index and Modify types

Shouldn't Index and Modify have the same constraints on Proxy?

type Index vect m m_minus_one i n elem =
  Compare m_minus_one NegOne GT
  โ‡’ Add One m_minus_one m
  โ‡’ Compare n NegOne GT
  โ‡’ Add i n m_minus_one
  โ‡’ Compare i NegOne GT
  โ‡’ Reflectable i Int
  โ‡’ Proxy i
  โ†’ vect m elem
  โ†’ elem

type Modify vect m n elem =
  Reflectable m Int
  โ‡’ Compare m NegOne GT
  โ‡’ Compare n NegOne GT
  โ‡’ Compare m n LT
  โ‡’ Proxy m
  โ†’ (elem โ†’ elem)
  โ†’ vect n elem
  โ†’ vect n elem

I feel that Index should be of the following type

type Index vect m n elem =
  Reflectable m Int
  โ‡’ Compare m NegOne GT
  โ‡’ Compare n NegOne GT
  โ‡’ Compare m n LT
  โ‡’ Proxy m
  โ†’ vect n elem
  โ†’ elem

`Vect` to nested tuple conversion

Hi, thanks a lot for building this lib! :-)

I find it useful to be able to deconstruct a Vect i a value into a tuple. I mean:

a /\ b /\ c = vectTuple $ 1 : 2 : 3 : empty

Do you think it would be valuable addition?

P.S.
I'm using this class to handle this - so this is my possible upcoming PR:

class VectTuple vect tuple | vect -> tuple where
  vectTuple :: vect -> tuple

instance VectTuple (Vect 1 a) a where
  vectTuple vect = head vect
else instance
  ( Add 1 i' i
  , VectTuple (Vect i' a) tpl
  , Compare i' NegOne GT
  , Compare i NegOne GT
  , Compare i 0 GT
  ) =>
  VectTuple (Vect i a) (a /\ tpl) where
  vectTuple vect = head vect /\ vectTuple ((drop (Proxy :: Proxy 1) vect) :: Vect i' a)

Remove duplicate qualified import?

In the imports of Data.FastVect.FastVect we have:

import Data.Array as A
import Data.Array as Array

Is there a reason for this or can we collapse both into a single import? Do you have a preference which one we keep?

Proposal: Swap `Proxy (a :: Int)` to dedicated `Index (bound :: Int)`

The gist of this proposal is to swap the Proxy idx type out in this library
for a new type (1):

-- | An int between 0 (inclusive) and bound (exclusive)
newtype Index :: Int -> Type
newtype Index bound = Index Int
type role Index nominal

With the breaking changes involved in switching to VTA already in #27, now seems like a great opportunity to consider other breaking changes against the core api.

Nice things that fall out of this:

  1. Type-safe indices into an array can be parsed at runtime
  2. Index objects can be safely manipulated at runtime while retaining access guarantees
  3. Can fully re-use standard numeric typeclasses on indices

Downsides:

  1. Code migration potentially gets a bit harder for existing users
  2. Index might not be able to be erased as aggressively by the compiler/jit, but the new type-application
    based methods introduced in #27 should be a significant mitigation

Full implementation I've been tooling around with

module Data.FastVect.Index (Index, reflectInt, index, (!!), widen, parse, modulo) where

import Prelude

import Control.Monad.ST as ST
import Data.Array.ST as ArrayST
import Data.Array.ST.Partial as ArraySTPartial
import Data.Enum (class BoundedEnum, class Enum, Cardinality(..), toEnum)
import Data.FastVect.FastVect (Vect)
import Data.FastVect.FastVect as FastVect
import Data.Maybe (Maybe(..))
import Data.Reflectable (class Reflectable, reflectType)
import Partial.Unsafe (unsafeCrashWith, unsafePartial)
import Prelude as Prelude
import Prim.Int (class Add, class Compare)
import Prim.Ordering (GT, LT)
import Prim.TypeError as TypeError
import Safe.Coerce (coerce)
import Type.Prelude (Proxy(..))

-- | An int between 0 (inclusive) and bound (exclusive)
newtype Index :: Int -> Type
newtype Index bound = Index Int
type role Index nominal

derive newtype instance Eq (Index bound)
derive newtype instance Ord (Index bound)
instance Reflectable bound Int => Show (Index bound) where
  show (Index idx) =
    "Index " <> show idx <> " within bound: " <> show (reflectType (Proxy @bound))

infixr 2 type TypeError.Beside as <>
infixr 1 type TypeError.Above as |>
type ZeroErrorMsg :: Symbol -> Symbol -> TypeError.Doc
type ZeroErrorMsg className closing =
         TypeError.Text "Cannot create " <> TypeError.QuoteLabel className
      <> TypeError.Text " instance for " <> TypeError.Quote (Index 0)
      |> TypeError.Text "A collection of size 0 has no elements to index into; "
      <> TypeError.Text "equivalently, the type " <> TypeError.Quote (Index 0)
      <> TypeError.Text "is uninhabited (i.e. cannot be instantiated like " <> TypeError.Quote Prelude.Void
      <> TypeError.Text ")."
      <> TypeError.Text closing

instance semiringIndexZeroError ::
    TypeError.Fail (ZeroErrorMsg "Semiring" "As such, the `one` and `zero` type class members cannot exist")
    => Semiring (Index 0) where
  zero = unsafeCrashWith "absurd"
  one = unsafeCrashWith "absurd"
  add _ _ = unsafeCrashWith "absurd"
  mul _ _ = unsafeCrashWith "absurd"
else instance semiringIndexDegenerate :: Semiring (Index 1) where
  zero = Index 0
  one = Index 0
  add _ _ = zero
  mul _ _ = zero
else instance semiringIndexGt1 :: (Compare bound 1 GT, Reflectable bound Int) =>
  Semiring (Index bound) where
  zero = Index 0
  one = Index 1
  add (Index a) (Index b) = (Index $ (a + b) `mod` (reflectType (Proxy @bound)))
  mul (Index a) (Index b) = (Index $ (a * b) `mod` (reflectType (Proxy @bound)))

instance (Compare bound 0 GT, Reflectable bound Int) => Bounded (Index bound) where
  top = Index $ (reflectType $ Proxy @bound) - 1
  bottom = Index 0

instance (Compare bound 0 GT, Reflectable bound Int) => Enum (Index bound) where
  succ (Index idx) =
    if idx < (reflectType $ Proxy @bound) - 1 then
      Just (Index $ idx + 1)
    else
      Nothing
  pred (Index idx) =
    if idx > 0 then
      Just (Index $ idx - 1)
    else
      Nothing

instance (Compare bound 0 GT, Reflectable bound Int) => BoundedEnum (Index bound) where
  cardinality = Cardinality 0
  toEnum int =
    if 0 <= int && int < (reflectType $ Proxy @bound) then
      Just (Index int)
    else
      Nothing
  fromEnum (Index int) = int

parse :: forall (@bound :: Int).
         Compare bound 0 GT => Reflectable bound Int =>

         Int -> Maybe (Index bound)
parse = toEnum @(Index bound)

modulo :: forall (@bound :: Int).
          Compare bound 1 GT => Reflectable bound Int =>

          Int -> Index bound
modulo idx = Index (idx `mod` (reflectType $ Proxy @bound))

-- | Reflect a type-level integer into a valid Index
-- | Note: with how `at` is implemented, the created `Index`
-- | is valid for any `Vect n a` where `@int < n`
reflectInt :: forall (@int :: Int) (bound :: Int).
              Reflectable int Int =>
              Compare int (-1) GT =>
              Add int 1 bound =>

              Index bound
reflectInt = Index (reflectType (Proxy @int))

widen :: forall (@widenedBound :: Int) (currentBound :: Int) (currentBoundMinusOne :: Int).
        Add currentBound (-1) currentBoundMinusOne =>
        Compare widenedBound currentBoundMinusOne GT =>

        Index currentBound -> Index widenedBound
widen = coerce

index :: forall (idxBound :: Int) (vectSize :: Int) (idxBoundMinusOne :: Int) a.
     Compare idxBound 0 GT => Compare vectSize 0 GT =>
     Add idxBound (-1) idxBoundMinusOne =>
     Compare idxBoundMinusOne vectSize LT =>

     Index idxBound -> Vect vectSize a -> a
index (Index idx) vec =
  ST.run
    (ArrayST.unsafeThaw (FastVect.toArray vec)
      >>= (unsafePartial ArraySTPartial.peek idx))

infixl 8 index as !!

(1): Not very attached to the name Index, nor any of the names in my code, for that matter

Should `Vect` have a `EuclideanRing` instance?

It seems like this instance would be lawful, from what I understand

instance (Compare len Common.NegOne GT, Reflectable len Int, EuclideanRing a) => EuclideanRing (Vect len a) where
  div = lift2 div
  mod = lift2 mod
  degree = const 1

The thing I'm most uncertain about is the degree implementation, I don't really understand what degree is and why it's part of EuclideanRing, but here's what the docs for EuclideanRing say:

For any EuclideanRing which is also a Field, one valid choice for degree is simply const 1. In fact, unless there's a specific reason not to, Field types should normally use this definition of degree.

Update documentation and add guide for the latest additions

The little documentation there is right now is largely still from my very first version (using symbol arithmetic instead of typelevel ints). Some sentences are outdated and wrong. Also the info about sparse vectors and matrices is completely missing.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.