Coder Social home page Coder Social logo

purescript-variant's People

Contributors

cryogenian avatar garyb avatar joneshf avatar jordanmartinez avatar justinwoo avatar liamgoodacre avatar monoidmusician avatar natefaubion avatar sigma-andex avatar thomashoneyman 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  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  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  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  avatar  avatar  avatar  avatar  avatar

purescript-variant's Issues

RFC: User injectable evidence

Right now, Functor is the only constraint with special consideration for VariantF. Whenever you build a VariantF with inj, it captures the Functor dictionary and keeps it around with the value. Since this is the defining evidence for VariantF, dispatching it is free since we always have the appropriate dictionary on hand. For anything else (Foldable, Traversable, etc) we have to collect all possible dictionaries at the call site, and perform a lookup to dispatch the appropriate dictionary. This is inefficient all around. It would be nice if we could let users inject arbitrary evidence whenever we build a VariantF, and use that to dispatch dictionaries.

The following is an example of some machinery that accomplishes this, but I'd like to get some feedback.

module DictTest where

import Prelude

import Data.Eq (class Eq1, eq1)
import Data.Foldable (class Foldable, foldMap, foldl, foldr)
import Data.Traversable (class Traversable, sequence, traverse)

newtype FunctorDict f =
  FunctorDict
    (forall a b. (a -> b) -> f a -> f b)

data FoldableDict f =
  FoldableDict
    (forall a r. (a -> r -> r) -> r -> f a -> r)
    (forall a r. (r -> a -> r) -> r -> f a -> r)
    (forall a r. Monoid r => (a -> r) -> f a -> r)

data TraversableDict f =
  TraversableDict
    (forall a b m. Applicative m => (a -> m b) -> f a -> m (f b))
    (forall a m. Applicative m => f (m a) -> m (f a))

newtype Eq1Dict f =
  Eq1Dict
    (forall a. Eq a => f a -> f a -> Boolean)

class Gather (f :: Type -> Type) e | e -> f where
  gather :: e f

class Dict (f :: Type -> Type) d e | d e -> f where
  dict :: e f -> d f

data Boxed (e :: (Type -> Type) -> Type) (f :: Type -> Type) a = Boxed (e f) (f a)

box :: forall e f a. Gather f e => f a -> Boxed e f a
box = Boxed gather

instance functorBoxed :: Dict f FunctorDict e => Functor (Boxed e f) where
  map k (Boxed e a) = case dict e of FunctorDict map' -> Boxed e (map' k a)

instance foldableBoxed :: Dict f FoldableDict e => Foldable (Boxed e f) where
  foldr a b (Boxed e c) = case dict e of FoldableDict foldr' _ _ -> foldr' a b c
  foldl a b (Boxed e c) = case dict e of FoldableDict _ foldl' _ -> foldl' a b c
  foldMap a (Boxed e b) = case dict e of FoldableDict _ _ foldMap' -> foldMap' a b

instance traversableBoxed ::
  ( Dict f TraversableDict e
  , Dict f FoldableDict e
  , Dict f FunctorDict e
  ) => Traversable (Boxed e f) where
  traverse a (Boxed e b) = case dict e of TraversableDict traverse' _ -> Boxed e <$> traverse' a b
  sequence (Boxed e a) = case dict e of TraversableDict _ sequence' -> Boxed e <$> sequence' a

instance eq1Boxed :: Dict f Eq1Dict e => Eq1 (Boxed e f) where
  eq1 (Boxed e a) (Boxed _ b) = case dict e of Eq1Dict eq1' -> eq1' a b

---

newtype Evidence f = Evidence
  { functor :: FunctorDict f
  , foldable :: FoldableDict f
  , traversable :: TraversableDict f
  , eq1 :: Eq1Dict f
  }

instance gatherEvidence :: (Traversable f, Eq1 f) => Gather f Evidence where
  gather = Evidence
    { functor: FunctorDict map
    , foldable: FoldableDict foldr foldl foldMap
    , traversable: TraversableDict traverse sequence
    , eq1: Eq1Dict eq1
    }

instance evidenceFunctorDict :: Dict f FunctorDict Evidence where
  dict (Evidence { functor }) = functor

instance evidenceEq1Dict :: Dict f Eq1Dict Evidence where
  dict (Evidence { eq1 }) = eq1

instance evidenceFoldableDict :: Dict f FoldableDict Evidence where
  dict (Evidence { foldable }) = foldable

instance evidenceTraversableDict :: Dict f TraversableDict Evidence where
  dict (Evidence { traversable }) = traversable

The Boxed type just packages up some evidence with some functor. If we extend that with a tag, then we'd have VariantFRep, and we could implement all the instance in terms of the user injected evidence. The smart constructor ensures that it's only every built using gather, which means that we always get the same dictionaries, so they are interchangeable in the case of binary operations like eq1.

/cc @MonoidMusician @joneshf

Disassociate & associate

Not sure about the names but I am interested in two definitions like this:

disassociate :: forall u v s
  .   Contractable s u
  =>  R.Union u v s
  =>  Variant s
  ->  Either (Variant u) (Variant v)
disassociate s =
  case contract s of
    Just u -> Left u
    Nothing -> Right (unsafeCoerce s)

associate :: forall u v s
  .   R.Union u v s
  =>  R.Union v u s
  =>  Either (Variant u) (Variant v)
  ->  Variant s
associate (Left u) = expand u
associate (Right v) = expand v

Idealistically, the conversions would be with a variant of variants, but the inference for a big-union constraint I am anticipating to be a nightmare. This is more simplistic.

Single Variant Eliminator

single :: forall s t e
  .   IsSymbol s
  =>  RowCons s t () e
  =>  RowToList e (Cons s t Nil)
  =>  Variant e
  ->  t
single = on (SProxy :: SProxy s) id case_

Just an idea. I found this useful. Thanks to @MonoidMusician for the RowToList idea so s is inferrable.

A variant eliminator

Something like the following:

unVariant :: forall r x.
  (forall s t o. IsSymbol s => RowCons s t o r => SProxy s -> t -> x) ->
  Variant r ->
  x

Most (all?) of the current variant functions appear to require upfront knowledge of which key you want to deal with. The proposed function should let us deal with whichever key was actually used.

(cc @erisco)

add `map` (like `bimap` for Either)

Either has bimap, would be nice to have something like that for variant which takes record of functions, something like this:

f :: forall r. Variant (foo :: String, baz :: Int | r) -> Variant (foo :: Array String, baz :: Number | r)
f = V.map
  { foo: \s -> [s,  "foo"]
  , baz: toNumber >>> _ + 9.0
  }

Privide prisms for variants

Just like there are prop lenses for records, there can be prisms for variants. Here is a way to implement it, no sure if it is possible to do without unsafeCoerce. (I couldn't get expand to compile here.)

label :: forall l r1 r2 r a b. IsSymbol l => Cons l a r r1 => Cons l b r r2 => SProxy l -> Prism (Variant r1) (Variant r2) a b
label l = prism (inj l) (on l Right (expand' >>> Left))
  where
    expand' :: Variant r -> Variant r2
    expand' = unsafeCoerce

Construct a variant from a record

Is there any interest in adding something that allows constructing a variant with a singleton record?

foo :: Data.Variant.Variant (bar :: Int, baz :: String, qux :: Boolean)
foo = Data.Variant.fromRecord { qux: true }

The motivation here is that using SProxy _ is a bit more cumbersome than seems necessary. Record syntax is a bit more lightweight to toss around.

It seems like fromRecord could be implemented like:

module Data.Variant where

class FromRecord (list :: Prim.RowList.RowList) record variant where
  fromRecord' :: forall proxy. proxy list -> Record record -> Variant variant

instance fromRecordSingleton ::
  ( Data.Symbol.IsSymbol label
  , Prim.Row.Cons label value () record
  , Prim.Row.Cons label value variant' variant
  ) =>
  FromRecord (Prim.RowList.Cons label value Prim.RowList.Nil) record variant
    where
      fromRecord' _ record = inj label (Record.get label record)
        where
        label :: Data.Symbol.SProxy label
        label = Data.Symbol.SProxy

fromRecord ::
  forall list record variant.
  Prim.RowList.RowToList record list =>
  FromRecord list record variant =>
  Record record ->
  Variant variant
fromRecord = fromRecord' (Type.Data.RowList.RLProxy :: _ list)

Might want some fundeps, dunno. If we want to be helpful, adding some custom error messages for using the wrong size record should be possible as well:

else instance fromRecordMultiple ::
  ( Prim.TypeError.Fail ?someErrorMessageAboutMultipleFieldsInTheRecord
  ) =>
  FromRecord (Prim.RowList.Cons label value list) record variant
    where
      fromRecord' proxy record = fromRecord' proxy record
else instance fromRecordEmpty ::
  ( Prim.TypeError.Fail ?someErrorMessageAboutTheEmptyRecord
  ) =>
  FromRecord Prim.RowList.Nil record variant
    where
      fromRecord' proxy record = fromRecord' proxy record

`expand` is unsafe

Since it does a naive Union, it can introduce duplicate labels, which will lead to runtime errors since our semantics match records. This needs some typelevel-prelude wizardry to remove duplicates.

add a way to map value at specific label

I needed something like this at one point let me know if you like this and will open PR.

mapLabel :: forall trash a b sym rIn rOut rBase
   . IsSymbol sym
  => RowCons sym a rBase rIn
  => RowCons sym b rBase rOut
  => SProxy sym
  -> (a -> b)
  -> Variant rIn
  -> Variant rOut
mapLabel p f r =
  -- NOTE: This coerce is safe as we know `rBase` is subset of `rOut`. `expand` could
  -- be  used instead, but than you need to provide `Union` instances when it's used.
  on p (inj p <<< f) (unsafeCoerce :: Variant rBase -> Variant rOut) r

Enable multi-value `Variant`s to remove unneeded runtime boxing

Right now, a Variant only permits a single value to be associated with a tag:

newtype VariantRep a = VariantRep
  { type :: String
  , value :: a
  }

foreign import data Variant :: Row Type -> Type

data OneOrPair0
  = One Int
  | Pair Int Int

type OneOrPair1 = Variant 
  ( one :: Int
  , pair :: Tuple Int Int
  )

If one wants to define a Variant-based List type, they pay for an extra level of boxing:

newtype List a = List (
  Variant 
    ( nil :: Unit
    -- extra nesting occurs here due to usage of 
    -- `Tuple` to store 2 args
    , cons :: Tuple a (List a)
    )
  )

_nil = Proxy :: Proxy "nil"
_cons = Proxy :: Proxy "cons"

nil = List $ V.inj _nil unit
cons head tail = List $ V.inj cons_ $ Tuple head tail

cons12 = cons 1 $ cons 2 nil
{-
which has the following runtime representation on JS:

{ type: "cons"
, value:
   { tag: "Tuple" -- extra nesting
   , value0: 1
   , value1: 
      { type: "cons"
      , value:
          { tag: "Tuple" -- extra nesting
          , value0: 2
          , value1:
              { type: "nil"
              , value: undefined
              }
          }
      }
   }
}
-}

as opposed to just using List directly:

cons12 = Cons 1 $ Cons 2 Nil
{-
{ type: "Cons"
, value0: 1
, value1:
   { tag: "Cons"
   , value0: 2
   , value1: 
      { type: "Nil"
      }
   }
}
-}

AFAICT, Variant could denest this extra boxing by using the following representation, which I call Variance to distinguish it from the current Variant. I show both Variant and Variance below to show the small change made:

newtype VariantRep a = VariantRep
  { type :: String
  , value :: a
  }

newtype VarianceRep r = VarianceRep
  { __ctorTag :: String
  | r
  }

foreign import data Variant ::  Row      Type  -> Type
foreign import data Variance :: Row (Row Type) -> Type

data OneOrPair0
  = One Int
  | Pair Int Int

type OneOrPair1 = Variant 
  ( one :: Int
  , pair :: Tuple Int Int
  )
type OneOrPair2 = Variance 
  ( one :: (int :: Int)
  , pair :: (fst :: Int, snd :: Int)
  )

pairVariant :: OneOrPair1
pairVariant = V.inj _pair $ Tuple 1 2
{-
Runtime representation:

{ type: "pair"
, value:
    { tag: "Tuple"
    , value0: 1
    , value1: 2
    }
}
-}

pairVariance :: OneOrPair2
pairVariance = Variance.inj _pair { fst: 1, snd: 2 }
{-
Runtime representation:

{ __ctorTag: "pair"
, fst: 1
, snd: 2
}
-}

Variance.inj is implemented as:

inj
  :: forall sym ctorRows ctorsTail ctorsAll
   . Row.Lacks "__ctorTag" ctorRows
  => Row.Cons sym ctorRows ctorsTail ctorsAll
  => IsSymbol sym
  => Proxy sym
  -> { | ctorRows }
  -> Variance ctorsAll
inj p value = coerceV $ VarianceRep $ Record.insert __ctorTag (reflectSymbol p) value
  where
  coerceV :: VarianceRep ctorRows  Variance ctorsAll
  coerceV = unsafeCoerce

Similarly, Variance.on is implemented as below. Notably, the entire representation is passed to the function so as not to duplicate the record without the __ctorTag field:

on
  :: forall sym ctorRows remaining all b
   . Row.Cons sym ctorRows remaining all
  => Reflectable sym String
  => Proxy sym
  -> ({ __ctorTag :: String | ctorRows } -> b)
  -> (Variance remaining -> b)
  -> Variance all
  -> b
on p f g r = case coerceR r of
  VarianceRep rep | rep.__tag == reflectSymbol p -> f rep
  _ -> g (coerceTail r)
  where
  coerceR :: Variance all -> VarianceRep ctorRows
  coerceR = unsafeCoerce

  coerceTail :: Variance all -> Variance remaining
  coerceTail = unsafeCoerce

By using the above representation, it enables the following Variance-based List type:

type NIL :: forall k. Row (Row k) -> Row (Row k)
type NIL r = (nil :: () | r)
_nil = Proxy :: Proxy "nil"

type CONS :: forall k. k -> k -> Row (Row k) -> Row (Row k)
type CONS a tail r = (cons :: (head :: a, tail :: tail) | r)
_cons = Proxy :: Proxy "cons"

newtype List :: Type -> Type
newtype List a = List
  (Variance 
    ( nil :: ()
    , cons :: (head :: a, tail :: List a)
    )
  )

Besides the smaller boxing, the Nil case also doesn't specify an unused value as it's runtime representation is just { __ctorTag: "nil" }. If one wanted to remove even that boxing and just have the string "Nil" for constructors that have no arguments, then FFI would need to be added that does a typeof x === "string" check.

Adding class `Contractable`

contractToAce
    rb rs rl
  . R.RowToList (AceR ()) rl
   VariantTags rl
   Union (AceR ()) rs rb
   Variant rb
   Maybe (Variant (AceR ()))
contractToAce v = contract
contractToAce 
  :: forall r 
  . Contractable r (AceR ())
  => Variant r
  => Maybe (Variant (AceR ()))
contractToAce = contract 

Remembering that contract must have 3 constraints is a bit too complex, why not aggregate them?

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.