Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add record elimination, tests and docs #6

Merged
merged 12 commits into from
Jul 28, 2017
5 changes: 3 additions & 2 deletions bower.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,9 @@
"purescript-unsafe-coerce": "^3.0.0",
"purescript-partial": "^1.2.0",
"purescript-maybe": "^3.0.0",
"purescript-typelevel-prelude": "^2.3.0",
"purescript-lists": "^4.9.0"
"purescript-typelevel-prelude": "^2.4.0",
"purescript-lists": "^4.9.0",
"purescript-record": "0.2.0"
},
"devDependencies": {
"purescript-console": "^3.0.0",
Expand Down
40 changes: 33 additions & 7 deletions src/Data/Functor/Variant.purs
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
module Data.Functor.Variant
( VariantF
, FProxy(..)
, inj
, prj
, on
, case_
, contract
, default
, match
, expand
, contract
, module Exports
) where

import Prelude

import Control.Alternative (class Alternative, empty)
import Data.Symbol (SProxy, class IsSymbol, reflectSymbol)
import Data.Symbol (SProxy(..)) as Exports
import Data.Symbol (SProxy, class IsSymbol, reflectSymbol)
import Data.Tuple (Tuple(..), fst)
import Data.Variant.Internal (class Contractable, contractWith, VariantCase, RProxy(..))
import Data.Variant.Internal (class Contractable) as Exports
import Data.Variant.Internal (class Contractable, contractWith, VariantCase, class VariantFRecordMatching, RProxy(..), FProxy, unsafeGet)
import Data.Variant.Internal (class Contractable, FProxy(..)) as Exports
import Partial.Unsafe (unsafeCrashWith)
import Unsafe.Coerce (unsafeCoerce)

Expand All @@ -38,8 +40,6 @@ instance functorVariantF ∷ Functor (VariantF r) where
coerceV ∷ ∀ f a. Tuple String (FBox f a) → VariantF r a
coerceV = unsafeCoerce

data FProxy (a ∷ Type → Type) = FProxy

-- | Inject into the variant at a given label.
-- | ```purescript
-- | maybeAtFoo :: forall r. VariantF (foo :: FProxy Maybe | r) Int
Expand Down Expand Up @@ -119,6 +119,32 @@ case_ r = unsafeCrashWith case unsafeCoerce r of
default ∷ ∀ a b r. a → VariantF r b → a
default a _ = a

-- | Match a `variant` with a `record` containing methods to handle each case
-- | to produce a `result`.
-- |
-- | This means that if `variant` contains a row of type `FProxy f`, a row with
-- | the same label must have type `f a -> result` in `record`, where `result`
-- | is the same type for every row of `record`.
-- |
-- | Polymorphic methods in `record` may create problems with the type system
-- | if the polymorphism is not fully generalized to the whole record type
-- | or if not all polymorphic variables are specified in usage. When in doubt,
-- | label methods with specific types, such as `show :: Int -> String`, or
-- | give the whole record an appropriate type.
match
∷ ∀ variant record typearg result
. VariantFRecordMatching variant record typearg result
⇒ Record record
→ VariantF variant typearg
→ result
match r v =
case coerceV v of
Tuple tag (FBox _ a) →
a # unsafeGet tag r
where
coerceV ∷ ∀ f. VariantF variant typearg → Tuple String (FBox f typearg)
coerceV = unsafeCoerce

-- | Every `VariantF lt a` can be cast to some `VariantF gt a` as long as `lt` is a
-- | subset of `gt`.
expand
Expand Down
32 changes: 30 additions & 2 deletions src/Data/Variant.purs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ module Data.Variant
, on
, case_
, default
, match
, expand
, contract
, class VariantEqs, variantEqs
Expand All @@ -13,13 +14,14 @@ module Data.Variant
) where

import Prelude

import Control.Alternative (empty, class Alternative)
import Data.List as L
import Data.Symbol (SProxy(..)) as Exports
import Data.Symbol (SProxy, class IsSymbol, reflectSymbol)
import Data.Tuple (Tuple(..), fst)
import Data.Variant.Internal (RLProxy(..), class VariantTags, variantTags, VariantCase, lookupEq, lookupOrd, class Contractable, RProxy(..), contractWith)
import Data.Variant.Internal (RLProxy(..), class VariantTags, variantTags, VariantCase, lookupEq, lookupOrd, class Contractable, RProxy(..), contractWith, class VariantRecordMatching, unsafeGet)
import Data.Variant.Internal (class Contractable) as Exports
import Data.Symbol (SProxy(..)) as Exports
import Partial.Unsafe (unsafeCrashWith)
import Type.Row as R
import Unsafe.Coerce (unsafeCoerce)
Expand Down Expand Up @@ -104,6 +106,32 @@ case_ r = unsafeCrashWith case unsafeCoerce r of
default ∷ ∀ a r. a → Variant r → a
default a _ = a

-- | Match a `variant` with a `record` containing methods to handle each case
-- | to produce a `result`.
-- |
-- | This means that if `variant` contains a row of type `a`, a row with the
-- | same label must have type `a -> result` in `record`, where `result` is the
-- | same type for every row of `record`.
-- |
-- | Polymorphic methods in `record` may create problems with the type system
-- | if the polymorphism is not fully generalized to the whole record type
-- | or if not all polymorphic variables are specified in usage. When in doubt,
-- | label methods with specific types, such as `show :: Int -> String`, or
-- | give the whole record an appropriate type.
match
∷ ∀ variant record result
. VariantRecordMatching variant record result
⇒ Record record
→ Variant variant
→ result
match r v =
case coerceV v of
Tuple tag a →
a # unsafeGet tag r
where
coerceV ∷ ∀ a. Variant variant → Tuple String a
coerceV = unsafeCoerce

-- | Every `Variant lt` can be cast to some `Variant gt` as long as `lt` is a
-- | subset of `gt`.
expand
Expand Down
83 changes: 78 additions & 5 deletions src/Data/Variant/Internal.purs
Original file line number Diff line number Diff line change
@@ -1,25 +1,98 @@
module Data.Variant.Internal
( RLProxy(..)
, RProxy(..)
( FProxy(..)
, VariantCase
, class VariantTags, variantTags
, class Contractable, contractWith
, class VariantRecordMatching
, class VariantMatchEachCase
, class VariantFRecordMatching
, class VariantFMatchEachCase
, lookupTag
, lookupEq
, lookupOrd
, module Exports
) where

import Prelude
import Control.Alternative (class Alternative, empty)
import Data.List as L
import Data.Symbol (SProxy(..), class IsSymbol, reflectSymbol)
import Data.Symbol (class IsSymbol, SProxy(SProxy), reflectSymbol)
import Data.Tuple (Tuple(..))
import Data.Record.Unsafe (unsafeGet) as Exports
import Partial.Unsafe (unsafeCrashWith)
import Type.Row as R
import Type.Row (RProxy, RLProxy(..))
import Type.Row (RProxy(..), RLProxy(..)) as Exports

data RProxy (r ∷ # Type) = RProxy
-- | Proxy for a `Functor`.
data FProxy (a ∷ Type → Type) = FProxy

data RLProxy (rl ∷ R.RowList) = RLProxy
-- | Type class that matches a row for a `record` that will eliminate a row for
-- | a `variant`, producing a `result` of the specified type. Just a wrapper for
-- | `RLMatch` to convert `RowToList` and vice versa.
class VariantRecordMatching
(variant ∷ # Type)
(record ∷ # Type)
result
| → variant record result
instance variantRecordMatching
∷ ( R.RowToList variant vlist
, R.RowToList record rlist
, VariantMatchEachCase vlist rlist result
, R.ListToRow vlist variant
, R.ListToRow rlist record )
⇒ VariantRecordMatching variant record result

-- | Checks that a `RowList` matches the argument to be given to the function
-- | in the other `RowList` with the same label, such that it will produce the
-- | result type.
class VariantMatchEachCase
(vlist ∷ R.RowList)
(rlist ∷ R.RowList)
result
| vlist → rlist result
, rlist → vlist result
instance variantMatchNil
∷ VariantMatchEachCase R.Nil R.Nil r
instance variantMatchCons
∷ VariantMatchEachCase v r res
⇒ VariantMatchEachCase
(R.Cons sym a v)
(R.Cons sym (a → res) r)
res

class VariantFRecordMatching
(variant ∷ # Type)
(record ∷ # Type)
typearg
result
| → variant record typearg result
instance variantFRecordMatching
∷ ( R.RowToList variant vlist
, R.RowToList record rlist
, VariantFMatchEachCase vlist rlist typearg result
, R.ListToRow vlist variant
, R.ListToRow rlist record )
⇒ VariantFRecordMatching variant record typearg result

-- | Checks that a `RowList` matches the argument to be given to the function
-- | in the other `RowList` with the same label, such that it will produce the
-- | result type.
class VariantFMatchEachCase
(vlist ∷ R.RowList)
(rlist ∷ R.RowList)
typearg
result
| vlist → rlist typearg result
, rlist → vlist typearg result
instance variantFMatchNil
∷ VariantFMatchEachCase R.Nil R.Nil a r
instance variantFMatchCons
∷ VariantFMatchEachCase v r a res
⇒ VariantFMatchEachCase
(R.Cons sym (FProxy f) v)
(R.Cons sym (f a → res) r)
a res

foreign import data VariantCase ∷ Type

Expand Down
14 changes: 13 additions & 1 deletion test/Variant.purs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Prelude
import Control.Monad.Eff (Eff)
import Data.List as L
import Data.Maybe (Maybe(..), isJust)
import Data.Variant (Variant, on, case_, default, inj, prj, SProxy(..), contract)
import Data.Variant (Variant, on, case_, default, inj, prj, SProxy(..), match, contract)
import Test.Assert (assert', ASSERT)

type TestVariants =
Expand Down Expand Up @@ -57,6 +57,18 @@ test = do
assert' "case2: bar" $ case2 bar == "bar: bar"
assert' "case2: baz" $ case2 baz == "no match"

let
match' :: Variant TestVariants -> String
match' = match
{ foo: \a → "foo: " <> show a
, bar: \a → "bar: " <> a
, baz: \a → "baz: " <> show a
}

assert' "match': foo" $ match' foo == "foo: 42"
assert' "match': bar" $ match' bar == "bar: bar"
assert' "match': baz" $ match' baz == "baz: true"

assert' "eq: foo" $ (foo ∷ Variant TestVariants) == foo
assert' "eq: bar" $ (bar ∷ Variant TestVariants) == bar
assert' "eq: baz" $ (baz ∷ Variant TestVariants) == baz
Expand Down
14 changes: 13 additions & 1 deletion test/VariantF.purs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import Prelude
import Control.Monad.Eff (Eff)
import Data.List as L
import Data.Either (Either(..))
import Data.Functor.Variant (VariantF, on, case_, default, inj, prj, SProxy(..), FProxy, contract)
import Data.Functor.Variant (VariantF, on, case_, default, match, inj, prj, SProxy(..), FProxy, contract)
import Data.Maybe (Maybe(..), isJust)
import Data.Tuple (Tuple(..))
import Test.Assert (assert', ASSERT)
Expand Down Expand Up @@ -65,6 +65,18 @@ test = do

assert' "map" $ case3 (show <$> foo) == "foo: (Just \"42\")"

let
match' ∷ VariantF TestVariants Int → String
match' = match
{ foo: \a → "foo: " <> show a
, bar: \a → "bar: " <> show a
, baz: \a → "baz: " <> show a
}

assert' "match': foo" $ match' foo == "foo: (Just 42)"
assert' "match': bar" $ match' bar == "bar: (Tuple \"bar\" 42)"
assert' "match': baz" $ match' baz == "baz: (Left \"baz\")"

assert' "contract: pass"
$ isJust
$ contract (foo ∷ VariantF TestVariants Int) ∷ Maybe (VariantF (foo ∷ FProxy Maybe) Int)
Expand Down