diff --git a/LICENSE b/LICENSE index d3249fee..311379c1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,20 +1,26 @@ -The MIT License (MIT) +Copyright 2018 PureScript -Copyright (c) 2015 PureScript +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: +1. Redistributions of source code must retain the above copyright notice, this +list of conditions and the following disclaimer. -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. +2. Redistributions in binary form must reproduce the above copyright notice, +this list of conditions and the following disclaimer in the documentation and/or +other materials provided with the distribution. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +3. Neither the name of the copyright holder nor the names of its contributors +may be used to endorse or promote products derived from this software without +specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/bower.json b/bower.json index 996ec6e2..2b7a1049 100644 --- a/bower.json +++ b/bower.json @@ -2,7 +2,7 @@ "name": "purescript-prelude", "homepage": "https://github.com/purescript/purescript-prelude", "description": "The PureScript Prelude", - "license": "MIT", + "license": "BSD-3-Clause", "repository": { "type": "git", "url": "git://github.com/purescript/purescript-prelude.git" diff --git a/package.json b/package.json index 7badb81e..c89017ab 100644 --- a/package.json +++ b/package.json @@ -3,12 +3,12 @@ "scripts": { "clean": "rimraf output && rimraf .pulp-cache", "build": "eslint src && pulp build -- --censor-lib --strict", - "test": "pulp test" + "test": "pulp test --no-check-main" }, "devDependencies": { - "eslint": "^3.17.1", - "purescript-psa": "^0.5.0-rc.1", - "pulp": "^10.0.4", - "rimraf": "^2.6.1" + "eslint": "^4.19.1", + "purescript-psa": "^0.6.0", + "pulp": "^12.2.0", + "rimraf": "^2.6.2" } } diff --git a/src/Control/Applicative.purs b/src/Control/Applicative.purs index d9d24fc7..d4c8489c 100644 --- a/src/Control/Applicative.purs +++ b/src/Control/Applicative.purs @@ -25,7 +25,7 @@ import Data.Unit (Unit, unit) -- | Instances must satisfy the following laws in addition to the `Apply` -- | laws: -- | --- | - Identity: `(pure id) <*> v = v` +-- | - Identity: `(pure identity) <*> v = v` -- | - Composition: `pure (<<<) <*> f <*> g <*> h = f <*> (g <*> h)` -- | - Homomorphism: `(pure f) <*> (pure x) = pure (f x)` -- | - Interchange: `u <*> (pure y) = (pure (_ $ y)) <*> u` diff --git a/src/Control/Apply.purs b/src/Control/Apply.purs index e6e7e1d8..2bbe40f0 100644 --- a/src/Control/Apply.purs +++ b/src/Control/Apply.purs @@ -8,7 +8,7 @@ module Control.Apply import Data.Functor (class Functor, map, void, ($>), (<#>), (<$), (<$>)) import Data.Function (const) -import Control.Category (id) +import Control.Category (identity) -- | The `Apply` class provides the `(<*>)` which is used to apply a function -- | to an argument under a type constructor. @@ -53,7 +53,7 @@ infixl 4 applyFirst as <* -- | Combine two effectful actions, keeping only the result of the second. applySecond :: forall a b f. Apply f => f a -> f b -> f b -applySecond a b = const id <$> a <*> b +applySecond a b = const identity <$> a <*> b infixl 4 applySecond as *> diff --git a/src/Control/Bind.purs b/src/Control/Bind.purs index 43c45c28..4257665f 100644 --- a/src/Control/Bind.purs +++ b/src/Control/Bind.purs @@ -13,7 +13,7 @@ module Control.Bind import Control.Applicative (class Applicative, liftA1, pure, unless, when) import Control.Apply (class Apply, apply, (*>), (<*), (<*>)) -import Control.Category (id) +import Control.Category (identity) import Data.Function (flip) import Data.Functor (class Functor, map, void, ($>), (<#>), (<$), (<$>)) @@ -81,7 +81,7 @@ instance discardUnit :: Discard Unit where -- | Collapse two applications of a monadic type constructor into one. join :: forall a m. Bind m => m (m a) -> m a -join m = m >>= id +join m = m >>= identity -- | Forwards Kleisli composition. -- | diff --git a/src/Control/Category.purs b/src/Control/Category.purs index 9fa17b23..77cb7bbc 100644 --- a/src/Control/Category.purs +++ b/src/Control/Category.purs @@ -1,5 +1,5 @@ module Control.Category - ( class Category, id + ( class Category, identity , module Control.Semigroupoid ) where @@ -12,9 +12,9 @@ import Control.Semigroupoid (class Semigroupoid, compose, (<<<), (>>>)) -- | Instances must satisfy the following law in addition to the -- | `Semigroupoid` law: -- | --- | - Identity: `id <<< p = p <<< id = p` +-- | - Identity: `identity <<< p = p <<< identity = p` class Semigroupoid a <= Category a where - id :: forall t. a t t + identity :: forall t. a t t instance categoryFn :: Category (->) where - id x = x + identity x = x diff --git a/src/Control/Semigroupoid.purs b/src/Control/Semigroupoid.purs index 729e1bbe..9c20b919 100644 --- a/src/Control/Semigroupoid.purs +++ b/src/Control/Semigroupoid.purs @@ -1,7 +1,7 @@ module Control.Semigroupoid where -- | A `Semigroupoid` is similar to a [`Category`](#category) but does not --- | require an identity element `id`, just composable morphisms. +-- | require an identity element `identity`, just composable morphisms. -- | -- | `Semigroupoid`s must satisfy the following law: -- | diff --git a/src/Data/BooleanAlgebra.purs b/src/Data/BooleanAlgebra.purs index 3babbf48..44a6c184 100644 --- a/src/Data/BooleanAlgebra.purs +++ b/src/Data/BooleanAlgebra.purs @@ -1,10 +1,14 @@ module Data.BooleanAlgebra ( class BooleanAlgebra , module Data.HeytingAlgebra + , class BooleanAlgebraRecord ) where -import Data.HeytingAlgebra (class HeytingAlgebra, ff, tt, implies, conj, disj, not, (&&), (||)) +import Data.HeytingAlgebra (class HeytingAlgebra, class HeytingAlgebraRecord, ff, tt, implies, conj, disj, not, (&&), (||)) +import Data.Symbol (class IsSymbol) import Data.Unit (Unit) +import Prim.Row as Row +import Prim.RowList as RL -- | The `BooleanAlgebra` type class represents types that behave like boolean -- | values. @@ -19,3 +23,18 @@ class HeytingAlgebra a <= BooleanAlgebra a instance booleanAlgebraBoolean :: BooleanAlgebra Boolean instance booleanAlgebraUnit :: BooleanAlgebra Unit instance booleanAlgebraFn :: BooleanAlgebra b => BooleanAlgebra (a -> b) +instance booleanAlgebraRecord :: (RL.RowToList row list, BooleanAlgebraRecord list row row) => BooleanAlgebra (Record row) + +-- | A class for records where all fields have `BooleanAlgebra` instances, used +-- | to implement the `BooleanAlgebra` instance for records. +class HeytingAlgebraRecord rowlist row subrow <= BooleanAlgebraRecord rowlist row subrow | rowlist -> subrow + +instance booleanAlgebraRecordNil :: BooleanAlgebraRecord RL.Nil row () + +instance booleanAlgebraRecordCons + :: ( IsSymbol key + , Row.Cons key focus subrowTail subrow + , BooleanAlgebraRecord rowlistTail row subrowTail + , BooleanAlgebra focus + ) + => BooleanAlgebraRecord (RL.Cons key focus rowlistTail) row subrow diff --git a/src/Data/CommutativeRing.purs b/src/Data/CommutativeRing.purs index 1cd21fc4..6fcf498f 100644 --- a/src/Data/CommutativeRing.purs +++ b/src/Data/CommutativeRing.purs @@ -2,11 +2,15 @@ module Data.CommutativeRing ( class CommutativeRing , module Data.Ring , module Data.Semiring + , class CommutativeRingRecord ) where -import Data.Ring (class Ring) +import Data.Ring (class Ring, class RingRecord) import Data.Semiring (class Semiring, add, mul, one, zero, (*), (+)) +import Data.Symbol (class IsSymbol) import Data.Unit (Unit) +import Prim.Row as Row +import Prim.RowList as RL -- | The `CommutativeRing` class is for rings where multiplication is -- | commutative. @@ -21,3 +25,18 @@ instance commutativeRingInt :: CommutativeRing Int instance commutativeRingNumber :: CommutativeRing Number instance commutativeRingUnit :: CommutativeRing Unit instance commutativeRingFn :: CommutativeRing b => CommutativeRing (a -> b) +instance commutativeRingRecord :: (RL.RowToList row list, CommutativeRingRecord list row row) => CommutativeRing (Record row) + +-- | A class for records where all fields have `CommutativeRing` instances, used +-- | to implement the `CommutativeRing` instance for records. +class RingRecord rowlist row subrow <= CommutativeRingRecord rowlist row subrow | rowlist -> subrow + +instance commutativeRingRecordNil :: CommutativeRingRecord RL.Nil row () + +instance commutativeRingRecordCons + :: ( IsSymbol key + , Row.Cons key focus subrowTail subrow + , CommutativeRingRecord rowlistTail row subrowTail + , CommutativeRing focus + ) + => CommutativeRingRecord (RL.Cons key focus rowlistTail) row subrow diff --git a/src/Data/DivisionRing.purs b/src/Data/DivisionRing.purs index c5aa86bc..227f7a94 100644 --- a/src/Data/DivisionRing.purs +++ b/src/Data/DivisionRing.purs @@ -7,9 +7,9 @@ module Data.DivisionRing , module Data.Semiring ) where +import Data.EuclideanRing ((/)) import Data.Ring (class Ring, negate, sub) import Data.Semiring (class Semiring, add, mul, one, zero, (*), (+)) -import Data.EuclideanRing ((/)) -- | The `DivisionRing` class is for non-zero rings in which every non-zero -- | element has a multiplicative inverse. Division rings are sometimes also diff --git a/src/Data/Eq.purs b/src/Data/Eq.purs index 0d589df8..9c193cea 100644 --- a/src/Data/Eq.purs +++ b/src/Data/Eq.purs @@ -1,10 +1,17 @@ module Data.Eq ( class Eq, eq, (==), notEq, (/=) , class Eq1, eq1, notEq1 + , class EqRecord, eqRecord ) where +import Data.HeytingAlgebra ((&&)) +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) import Data.Unit (Unit) import Data.Void (Void) +import Prim.Row as Row +import Prim.RowList as RL +import Record.Unsafe (unsafeGet) +import Type.Data.RowList (RLProxy(..)) -- | The `Eq` type class represents types which support decidable equality. -- | @@ -54,6 +61,9 @@ instance eqVoid :: Eq Void where instance eqArray :: Eq a => Eq (Array a) where eq = eqArrayImpl eq +instance eqRec :: (RL.RowToList row list, EqRecord list row) => Eq (Record row) where + eq = eqRecord (RLProxy :: RLProxy list) + foreign import refEq :: forall a. a -> a -> Boolean foreign import eqArrayImpl :: forall a. (a -> a -> Boolean) -> Array a -> Array a -> Boolean @@ -66,3 +76,24 @@ instance eq1Array :: Eq1 Array where notEq1 :: forall f a. Eq1 f => Eq a => f a -> f a -> Boolean notEq1 x y = (x `eq1` y) == false + +-- | A class for records where all fields have `Eq` instances, used to implement +-- | the `Eq` instance for records. +class EqRecord rowlist row where + eqRecord :: RLProxy rowlist -> Record row -> Record row -> Boolean + +instance eqRowNil :: EqRecord RL.Nil row where + eqRecord _ _ _ = true + +instance eqRowCons + :: ( EqRecord rowlistTail row + , Row.Cons key focus rowTail row + , IsSymbol key + , Eq focus + ) + => EqRecord (RL.Cons key focus rowlistTail) row where + eqRecord _ ra rb = (get ra == get rb) && tail + where + key = reflectSymbol (SProxy :: SProxy key) + get = unsafeGet key :: Record row -> focus + tail = eqRecord (RLProxy :: RLProxy rowlistTail) ra rb diff --git a/src/Data/EuclideanRing.js b/src/Data/EuclideanRing.js index 362b119e..a19fe503 100644 --- a/src/Data/EuclideanRing.js +++ b/src/Data/EuclideanRing.js @@ -4,16 +4,20 @@ exports.intDegree = function (x) { return Math.min(Math.abs(x), 2147483647); }; +// See the Euclidean definition in +// https://en.m.wikipedia.org/wiki/Modulo_operation. exports.intDiv = function (x) { return function (y) { - /* jshint bitwise: false */ - return x / y | 0; + if (y === 0) return 0; + return y > 0 ? Math.floor(x / y) : -Math.floor(x / -y); }; }; exports.intMod = function (x) { return function (y) { - return x % y; + if (y === 0) return 0; + var yy = Math.abs(y); + return ((x % yy) + yy) % yy; }; }; diff --git a/src/Data/EuclideanRing.purs b/src/Data/EuclideanRing.purs index 6d02ec1f..3f790bfe 100644 --- a/src/Data/EuclideanRing.purs +++ b/src/Data/EuclideanRing.purs @@ -41,6 +41,25 @@ import Data.Semiring (class Semiring, add, mul, one, zero, (*), (+)) -- | 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`. +-- | +-- | The `EuclideanRing Int` instance is one of the most commonly used +-- | `EuclideanRing` instances and deserves a little more discussion. In +-- | particular, there are a few different sensible law-abiding implementations +-- | to choose from, with slightly different behaviour in the presence of +-- | negative dividends or divisors. The most common definitions are "truncating" +-- | division, where the result of `a / b` is rounded towards 0, and "Knuthian" +-- | or "flooring" division, where the result of `a / b` is rounded towards +-- | negative infinity. A slightly less common, but arguably more useful, option +-- | is "Euclidean" division, which is defined so as to ensure that ``a `mod` b`` +-- | is always nonnegative. With Euclidean division, `a / b` rounds towards +-- | negative infinity if the divisor is positive, and towards positive infinity +-- | if the divisor is negative. Note that all three definitions are identical if +-- | we restrict our attention to nonnegative dividends and divisors. +-- | +-- | In versions 1.x, 2.x, and 3.x of the Prelude, the `EuclideanRing Int` +-- | instance used truncating division. As of 4.x, the `EuclideanRing Int` +-- | instance uses Euclidean division. Additional functions `quot` and `rem` are +-- | supplied if truncating division is desired. class CommutativeRing a <= EuclideanRing a where degree :: a -> Int div :: a -> a -> a diff --git a/src/Data/Field.purs b/src/Data/Field.purs index 9fc9093f..113b714d 100644 --- a/src/Data/Field.purs +++ b/src/Data/Field.purs @@ -15,14 +15,27 @@ import Data.Semiring (class Semiring, add, mul, one, zero, (*), (+)) -- | The `Field` class is for types that are (commutative) fields. -- | --- | Instances must satisfy the following law in addition to the --- | `EuclideanRing` laws: +-- | Mathematically, a field is a ring which is commutative and in which every +-- | nonzero element has a multiplicative inverse; these conditions correspond +-- | to the `CommutativeRing` and `DivisionRing` classes in PureScript +-- | respectively. However, the `Field` class has `EuclideanRing` and +-- | `DivisionRing` as superclasses, which seems like a stronger requirement +-- | (since `CommutativeRing` is a superclass of `EuclideanRing`). In fact, it +-- | is not stronger, since any type which has law-abiding `CommutativeRing` +-- | and `DivisionRing` instances permits exactly one law-abiding +-- | `EuclideanRing` instance. We use a `EuclideanRing` superclass here in +-- | order to ensure that a `Field` constraint on a function permits you to use +-- | `div` on that type, since `div` is a member of `EuclideanRing`. -- | --- | - Non-zero multiplicative inverse: ``a `mod` b = zero`` for all `a` and `b` +-- | This class has no laws or members of its own; it exists as a convenience, +-- | so a single constraint can be used when field-like behaviour is expected. -- | --- | If a type has a `Field` instance, it should also have a `DivisionRing` --- | instance. In a future release, `DivisionRing` may become a superclass of --- | `Field`. -class EuclideanRing a <= Field a +-- | This module also defines a single `Field` instance for any type which has +-- | both `EuclideanRing` and `DivisionRing` instances. Any other instance +-- | would overlap with this instance, so no other `Field` instances should be +-- | defined in libraries. Instead, simply define `EuclideanRing` and +-- | `DivisionRing` instances, and this will permit your type to be used with a +-- | `Field` constraint. +class (EuclideanRing a, DivisionRing a) <= Field a -instance fieldNumber :: Field Number +instance field :: (EuclideanRing a, DivisionRing a) => Field a diff --git a/src/Data/Function.purs b/src/Data/Function.purs index e4875d97..e6acce20 100644 --- a/src/Data/Function.purs +++ b/src/Data/Function.purs @@ -8,7 +8,7 @@ module Data.Function , module Control.Category ) where -import Control.Category (id, compose, (<<<), (>>>)) +import Control.Category (identity, compose, (<<<), (>>>)) import Data.Boolean (otherwise) import Data.Ord ((<=)) import Data.Ring ((-)) diff --git a/src/Data/Functor.purs b/src/Data/Functor.purs index 07b99497..e2754832 100644 --- a/src/Data/Functor.purs +++ b/src/Data/Functor.purs @@ -19,7 +19,7 @@ import Data.Unit (Unit, unit) -- | -- | Instances must satisfy the following laws: -- | --- | - Identity: `map id = id` +-- | - Identity: `map identity = identity` -- | - Composition: `map (f <<< g) = map f <<< map g` class Functor f where map :: forall a b. (a -> b) -> f a -> f b diff --git a/src/Data/HeytingAlgebra.purs b/src/Data/HeytingAlgebra.purs index dca83196..5b6920f1 100644 --- a/src/Data/HeytingAlgebra.purs +++ b/src/Data/HeytingAlgebra.purs @@ -1,9 +1,15 @@ module Data.HeytingAlgebra - ( class HeytingAlgebra, tt, ff, implies, conj, disj, not - , (&&), (||) + ( class HeytingAlgebra, tt, ff, implies, conj, disj, not, (&&), (||) + , class HeytingAlgebraRecord, ffRecord, ttRecord, impliesRecord, conjRecord, disjRecord, notRecord ) where +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) import Data.Unit (Unit, unit) +import Prim.Row as Row +import Prim.RowList as RL +import Record.Unsafe (unsafeGet, unsafeSet) +import Type.Data.Row (RProxy(..)) +import Type.Data.RowList (RLProxy(..)) -- | The `HeytingAlgebra` type class represents types that are bounded lattices with -- | an implication operator such that the following laws hold: @@ -65,6 +71,80 @@ instance heytingAlgebraFunction :: HeytingAlgebra b => HeytingAlgebra (a -> b) w disj f g a = f a || g a not f a = not (f a) +instance heytingAlgebraRecord :: (RL.RowToList row list, HeytingAlgebraRecord list row row) => HeytingAlgebra (Record row) where + ff = ffRecord (RLProxy :: RLProxy list) (RProxy :: RProxy row) + tt = ttRecord (RLProxy :: RLProxy list) (RProxy :: RProxy row) + conj = conjRecord (RLProxy :: RLProxy list) + disj = disjRecord (RLProxy :: RLProxy list) + implies = impliesRecord (RLProxy :: RLProxy list) + not = notRecord (RLProxy :: RLProxy list) + foreign import boolConj :: Boolean -> Boolean -> Boolean foreign import boolDisj :: Boolean -> Boolean -> Boolean foreign import boolNot :: Boolean -> Boolean + +-- | A class for records where all fields have `HeytingAlgebra` instances, used +-- | to implement the `HeytingAlgebra` instance for records. +class HeytingAlgebraRecord rowlist row subrow | rowlist -> subrow where + ffRecord :: RLProxy rowlist -> RProxy row -> Record subrow + ttRecord :: RLProxy rowlist -> RProxy row -> Record subrow + impliesRecord :: RLProxy rowlist -> Record row -> Record row -> Record subrow + disjRecord :: RLProxy rowlist -> Record row -> Record row -> Record subrow + conjRecord :: RLProxy rowlist -> Record row -> Record row -> Record subrow + notRecord :: RLProxy rowlist -> Record row -> Record subrow + +instance heytingAlgebraRecordNil :: HeytingAlgebraRecord RL.Nil row () where + conjRecord _ _ _ = {} + disjRecord _ _ _ = {} + ffRecord _ _ = {} + impliesRecord _ _ _ = {} + notRecord _ _ = {} + ttRecord _ _ = {} + +instance heytingAlgebraRecordCons + :: ( IsSymbol key + , Row.Cons key focus subrowTail subrow + , HeytingAlgebraRecord rowlistTail row subrowTail + , HeytingAlgebra focus + ) + => HeytingAlgebraRecord (RL.Cons key focus rowlistTail) row subrow where + conjRecord _ ra rb = insert (conj (get ra) (get rb)) tail + where + key = reflectSymbol (SProxy :: SProxy key) + get = unsafeGet key :: Record row -> focus + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + tail = conjRecord (RLProxy :: RLProxy rowlistTail) ra rb + + disjRecord _ ra rb = insert (disj (get ra) (get rb)) tail + where + key = reflectSymbol (SProxy :: SProxy key) + get = unsafeGet key :: Record row -> focus + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + tail = disjRecord (RLProxy :: RLProxy rowlistTail) ra rb + + impliesRecord _ ra rb = insert (implies (get ra) (get rb)) tail + where + key = reflectSymbol (SProxy :: SProxy key) + get = unsafeGet key :: Record row -> focus + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + tail = impliesRecord (RLProxy :: RLProxy rowlistTail) ra rb + + ffRecord _ row = insert ff tail + where + key = reflectSymbol (SProxy :: SProxy key) + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + tail = ffRecord (RLProxy :: RLProxy rowlistTail) row + + notRecord _ row + = insert (not (get row)) tail + where + key = reflectSymbol (SProxy :: SProxy key) + get = unsafeGet key :: Record row -> focus + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + tail = notRecord (RLProxy :: RLProxy rowlistTail) row + + ttRecord _ row = insert tt tail + where + key = reflectSymbol (SProxy :: SProxy key) + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + tail = ttRecord (RLProxy :: RLProxy rowlistTail) row diff --git a/src/Data/Monoid.purs b/src/Data/Monoid.purs new file mode 100644 index 00000000..ba496e9e --- /dev/null +++ b/src/Data/Monoid.purs @@ -0,0 +1,98 @@ +module Data.Monoid + ( class Monoid, mempty + , power + , guard + , module Data.Semigroup + , class MonoidRecord, memptyRecord + ) where + +import Data.Boolean (otherwise) +import Data.Eq ((==)) +import Data.EuclideanRing (mod, (/)) +import Data.Ord ((<=)) +import Data.Ordering (Ordering(..)) +import Data.Semigroup (class Semigroup, class SemigroupRecord, (<>)) +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) +import Data.Unit (Unit, unit) +import Prim.Row as Row +import Prim.RowList as RL +import Record.Unsafe (unsafeSet) +import Type.Data.RowList (RLProxy(..)) + +-- | A `Monoid` is a `Semigroup` with a value `mempty`, which is both a +-- | left and right unit for the associative operation `<>`: +-- | +-- | ``` +-- | forall x. mempty <> x = x <> mempty = x +-- | ``` +-- | +-- | `Monoid`s are commonly used as the result of fold operations, where +-- | `<>` is used to combine individual results, and `mempty` gives the result +-- | of folding an empty collection of elements. +class Semigroup m <= Monoid m where + mempty :: m + +instance monoidUnit :: Monoid Unit where + mempty = unit + +instance monoidOrdering :: Monoid Ordering where + mempty = EQ + +instance monoidFn :: Monoid b => Monoid (a -> b) where + mempty _ = mempty + +instance monoidString :: Monoid String where + mempty = "" + +instance monoidArray :: Monoid (Array a) where + mempty = [] + +instance monoidRecord :: (RL.RowToList row list, MonoidRecord list row row) => Monoid (Record row) where + mempty = memptyRecord (RLProxy :: RLProxy list) + +-- | Append a value to itself a certain number of times. For the +-- | `Multiplicative` type, and for a non-negative power, this is the same as +-- | normal number exponentiation. +-- | +-- | If the second argument is negative this function will return `mempty` +-- | (*unlike* normal number exponentiation). The `Monoid` constraint alone +-- | is not enough to write a `power` function with the property that `power x +-- | n` cancels with `power x (-n)`, i.e. `power x n <> power x (-n) = mempty`. +-- | For that, we would additionally need the ability to invert elements, i.e. +-- | a Group. +power :: forall m. Monoid m => m -> Int -> m +power x = go + where + go :: Int -> m + go p + | p <= 0 = mempty + | p == 1 = x + | p `mod` 2 == 0 = let x' = go (p / 2) in x' <> x' + | otherwise = let x' = go (p / 2) in x' <> x' <> x + +-- | Allow or "truncate" a Monoid to its `mempty` value based on a condition. +guard :: forall m. Monoid m => Boolean -> m -> m +guard true a = a +guard false _ = mempty + +-- | A class for records where all fields have `Monoid` instances, used to +-- | implement the `Monoid` instance for records. +class SemigroupRecord rowlist row subrow <= MonoidRecord rowlist row subrow | rowlist -> row subrow where + memptyRecord :: RLProxy rowlist -> Record subrow + +instance monoidRecordNil :: MonoidRecord RL.Nil row () where + memptyRecord _ = {} + +instance monoidRecordCons + :: ( IsSymbol key + , Monoid focus + , Row.Cons key focus subrowTail subrow + , MonoidRecord rowlistTail row subrowTail + ) + => MonoidRecord (RL.Cons key focus rowlistTail) row subrow where + memptyRecord _ + = insert mempty tail + where + key = reflectSymbol (SProxy :: SProxy key) + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + tail = memptyRecord (RLProxy :: RLProxy rowlistTail) diff --git a/src/Data/Monoid/Additive.purs b/src/Data/Monoid/Additive.purs new file mode 100644 index 00000000..6c365b14 --- /dev/null +++ b/src/Data/Monoid/Additive.purs @@ -0,0 +1,44 @@ +module Data.Monoid.Additive where + +import Prelude + +import Data.Eq (class Eq1) +import Data.Ord (class Ord1) + +-- | Monoid and semigroup for semirings under addition. +-- | +-- | ``` purescript +-- | Additive x <> Additive y == Additive (x + y) +-- | mempty :: Additive _ == Additive zero +-- | ``` +newtype Additive a = Additive a + +derive newtype instance eqAdditive :: Eq a => Eq (Additive a) +derive instance eq1Additive :: Eq1 Additive + +derive newtype instance ordAdditive :: Ord a => Ord (Additive a) +derive instance ord1Additive :: Ord1 Additive + +derive newtype instance boundedAdditive :: Bounded a => Bounded (Additive a) + +instance showAdditive :: Show a => Show (Additive a) where + show (Additive a) = "(Additive " <> show a <> ")" + +derive instance functorAdditive :: Functor Additive + +instance applyAdditive :: Apply Additive where + apply (Additive f) (Additive x) = Additive (f x) + +instance applicativeAdditive :: Applicative Additive where + pure = Additive + +instance bindAdditive :: Bind Additive where + bind (Additive x) f = f x + +instance monadAdditive :: Monad Additive + +instance semigroupAdditive :: Semiring a => Semigroup (Additive a) where + append (Additive a) (Additive b) = Additive (a + b) + +instance monoidAdditive :: Semiring a => Monoid (Additive a) where + mempty = Additive zero diff --git a/src/Data/Monoid/Conj.purs b/src/Data/Monoid/Conj.purs new file mode 100644 index 00000000..0b090c31 --- /dev/null +++ b/src/Data/Monoid/Conj.purs @@ -0,0 +1,51 @@ +module Data.Monoid.Conj where + +import Prelude + +import Data.Eq (class Eq1) +import Data.HeytingAlgebra (ff, tt) +import Data.Ord (class Ord1) + +-- | Monoid and semigroup for conjuntion. +-- | +-- | ``` purescript +-- | Conj x <> Conj y == Conj (x && y) +-- | mempty :: Conj _ == Conj top +-- | ``` +newtype Conj a = Conj a + +derive newtype instance eqConj :: Eq a => Eq (Conj a) +derive instance eq1Conj :: Eq1 Conj + +derive newtype instance ordConj :: Ord a => Ord (Conj a) +derive instance ord1Conj :: Ord1 Conj + +derive newtype instance boundedConj :: Bounded a => Bounded (Conj a) + +instance showConj :: (Show a) => Show (Conj a) where + show (Conj a) = "(Conj " <> show a <> ")" + +derive instance functorConj :: Functor Conj + +instance applyConj :: Apply Conj where + apply (Conj f) (Conj x) = Conj (f x) + +instance applicativeConj :: Applicative Conj where + pure = Conj + +instance bindConj :: Bind Conj where + bind (Conj x) f = f x + +instance monadConj :: Monad Conj + +instance semigroupConj :: HeytingAlgebra a => Semigroup (Conj a) where + append (Conj a) (Conj b) = Conj (conj a b) + +instance monoidConj :: HeytingAlgebra a => Monoid (Conj a) where + mempty = Conj tt + +instance semiringConj :: HeytingAlgebra a => Semiring (Conj a) where + zero = Conj tt + one = Conj ff + add (Conj a) (Conj b) = Conj (conj a b) + mul (Conj a) (Conj b) = Conj (disj a b) diff --git a/src/Data/Monoid/Disj.purs b/src/Data/Monoid/Disj.purs new file mode 100644 index 00000000..ea734b90 --- /dev/null +++ b/src/Data/Monoid/Disj.purs @@ -0,0 +1,51 @@ +module Data.Monoid.Disj where + +import Prelude + +import Data.Eq (class Eq1) +import Data.HeytingAlgebra (ff, tt) +import Data.Ord (class Ord1) + +-- | Monoid and semigroup for disjuntion. +-- | +-- | ``` purescript +-- | Disj x <> Disj y == Disj (x || y) +-- | mempty :: Disj _ == Disj bottom +-- | ``` +newtype Disj a = Disj a + +derive newtype instance eqDisj :: Eq a => Eq (Disj a) +derive instance eq1Disj :: Eq1 Disj + +derive newtype instance ordDisj :: Ord a => Ord (Disj a) +derive instance ord1Disj :: Ord1 Disj + +derive newtype instance boundedDisj :: Bounded a => Bounded (Disj a) + +instance showDisj :: Show a => Show (Disj a) where + show (Disj a) = "(Disj " <> show a <> ")" + +derive instance functorDisj :: Functor Disj + +instance applyDisj :: Apply Disj where + apply (Disj f) (Disj x) = Disj (f x) + +instance applicativeDisj :: Applicative Disj where + pure = Disj + +instance bindDisj :: Bind Disj where + bind (Disj x) f = f x + +instance monadDisj :: Monad Disj + +instance semigroupDisj :: HeytingAlgebra a => Semigroup (Disj a) where + append (Disj a) (Disj b) = Disj (disj a b) + +instance monoidDisj :: HeytingAlgebra a => Monoid (Disj a) where + mempty = Disj ff + +instance semiringDisj :: HeytingAlgebra a => Semiring (Disj a) where + zero = Disj ff + one = Disj tt + add (Disj a) (Disj b) = Disj (disj a b) + mul (Disj a) (Disj b) = Disj (conj a b) diff --git a/src/Data/Monoid/Dual.purs b/src/Data/Monoid/Dual.purs new file mode 100644 index 00000000..197a833c --- /dev/null +++ b/src/Data/Monoid/Dual.purs @@ -0,0 +1,44 @@ +module Data.Monoid.Dual where + +import Prelude + +import Data.Eq (class Eq1) +import Data.Ord (class Ord1) + +-- | The dual of a monoid. +-- | +-- | ``` purescript +-- | Dual x <> Dual y == Dual (y <> x) +-- | mempty :: Dual _ == Dual mempty +-- | ``` +newtype Dual a = Dual a + +derive newtype instance eqDual :: Eq a => Eq (Dual a) +derive instance eq1Dual :: Eq1 Dual + +derive newtype instance ordDual :: Ord a => Ord (Dual a) +derive instance ord1Dual :: Ord1 Dual + +derive newtype instance boundedDual :: Bounded a => Bounded (Dual a) + +instance showDual :: Show a => Show (Dual a) where + show (Dual a) = "(Dual " <> show a <> ")" + +derive instance functorDual :: Functor Dual + +instance applyDual :: Apply Dual where + apply (Dual f) (Dual x) = Dual (f x) + +instance applicativeDual :: Applicative Dual where + pure = Dual + +instance bindDual :: Bind Dual where + bind (Dual x) f = f x + +instance monadDual :: Monad Dual + +instance semigroupDual :: Semigroup a => Semigroup (Dual a) where + append (Dual x) (Dual y) = Dual (y <> x) + +instance monoidDual :: Monoid a => Monoid (Dual a) where + mempty = Dual mempty diff --git a/src/Data/Monoid/Endo.purs b/src/Data/Monoid/Endo.purs new file mode 100644 index 00000000..04df3fb7 --- /dev/null +++ b/src/Data/Monoid/Endo.purs @@ -0,0 +1,29 @@ +module Data.Monoid.Endo where + +import Prelude + +-- | Monoid and semigroup for category endomorphisms. +-- | +-- | When `c` is instantiated with `->` this composes functions of type +-- | `a -> a`: +-- | +-- | ``` purescript +-- | Endo f <> Endo g == Endo (f <<< g) +-- | mempty :: Endo _ == Endo identity +-- | ``` +newtype Endo c a = Endo (c a a) + +derive newtype instance eqEndo :: Eq (c a a) => Eq (Endo c a) + +derive newtype instance ordEndo :: Ord (c a a) => Ord (Endo c a) + +derive newtype instance boundedEndo :: Bounded (c a a) => Bounded (Endo c a) + +instance showEndo :: Show (c a a) => Show (Endo c a) where + show (Endo x) = "(Endo " <> show x <> ")" + +instance semigroupEndo :: Semigroupoid c => Semigroup (Endo c a) where + append (Endo a) (Endo b) = Endo (a <<< b) + +instance monoidEndo :: Category c => Monoid (Endo c a) where + mempty = Endo identity diff --git a/src/Data/Monoid/Multiplicative.purs b/src/Data/Monoid/Multiplicative.purs new file mode 100644 index 00000000..3b929ab8 --- /dev/null +++ b/src/Data/Monoid/Multiplicative.purs @@ -0,0 +1,44 @@ +module Data.Monoid.Multiplicative where + +import Prelude + +import Data.Eq (class Eq1) +import Data.Ord (class Ord1) + +-- | Monoid and semigroup for semirings under multiplication. +-- | +-- | ``` purescript +-- | Multiplicative x <> Multiplicative y == Multiplicative (x * y) +-- | mempty :: Multiplicative _ == Multiplicative one +-- | ``` +newtype Multiplicative a = Multiplicative a + +derive newtype instance eqMultiplicative :: Eq a => Eq (Multiplicative a) +derive instance eq1Multiplicative :: Eq1 Multiplicative + +derive newtype instance ordMultiplicative :: Ord a => Ord (Multiplicative a) +derive instance ord1Multiplicative :: Ord1 Multiplicative + +derive newtype instance boundedMultiplicative :: Bounded a => Bounded (Multiplicative a) + +instance showMultiplicative :: Show a => Show (Multiplicative a) where + show (Multiplicative a) = "(Multiplicative " <> show a <> ")" + +derive instance functorMultiplicative :: Functor Multiplicative + +instance applyMultiplicative :: Apply Multiplicative where + apply (Multiplicative f) (Multiplicative x) = Multiplicative (f x) + +instance applicativeMultiplicative :: Applicative Multiplicative where + pure = Multiplicative + +instance bindMultiplicative :: Bind Multiplicative where + bind (Multiplicative x) f = f x + +instance monadMultiplicative :: Monad Multiplicative + +instance semigroupMultiplicative :: Semiring a => Semigroup (Multiplicative a) where + append (Multiplicative a) (Multiplicative b) = Multiplicative (a * b) + +instance monoidMultiplicative :: Semiring a => Monoid (Multiplicative a) where + mempty = Multiplicative one diff --git a/src/Data/Ord.purs b/src/Data/Ord.purs index a3bec35f..66609d4b 100644 --- a/src/Data/Ord.purs +++ b/src/Data/Ord.purs @@ -168,3 +168,37 @@ class Eq1 f <= Ord1 f where instance ord1Array :: Ord1 Array where compare1 = compare + +-- Ordering for records is currently unimplemented as there are outstanding +-- questions around whether this implementation be useful. This is because it +-- prioritises the keys alphabetically, and this behaviour isn't overridable. +-- For now, we leave this unavailable, but the implementation is as follows: + +-- class EqRecord rowlist row focus <= OrdRecord rowlist row focus | rowlist -> focus where +-- compareImpl :: RLProxy rowlist -> Record row -> Record row -> Ordering +-- +-- instance ordRecordNil :: OrdRecord RL.Nil row focus where +-- compareImpl _ _ _ = EQ +-- +-- instance ordRecordCons +-- :: ( OrdRecord rowlistTail row subfocus +-- , Row.Cons key focus rowTail row +-- , IsSymbol key +-- , Ord focus +-- ) +-- => OrdRecord (RL.Cons key focus rowlistTail) row focus where +-- compareImpl _ ra rb +-- = if left /= EQ +-- then left +-- else compareImpl (RLProxy :: RLProxy rowlistTail) ra rb +-- where +-- key = reflectSymbol (SProxy :: SProxy key) +-- unsafeGet' = unsafeGet :: String -> Record row -> focus +-- left = unsafeGet' key ra `compare` unsafeGet' key rb +-- +-- instance ordRecord +-- :: ( RL.RowToList row list +-- , OrdRecord list row focus +-- ) +-- => Ord (Record row) where +-- compare = compareImpl (RLProxy :: RLProxy list) diff --git a/src/Data/Ring.purs b/src/Data/Ring.purs index 1fca86b5..1d5ab234 100644 --- a/src/Data/Ring.purs +++ b/src/Data/Ring.purs @@ -1,10 +1,16 @@ module Data.Ring ( class Ring, sub, negate, (-) , module Data.Semiring + , class RingRecord, subRecord ) where -import Data.Semiring (class Semiring, add, mul, one, zero, (*), (+)) +import Data.Semiring (class Semiring, class SemiringRecord, add, mul, one, zero, (*), (+)) +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) import Data.Unit (Unit, unit) +import Prim.Row as Row +import Prim.RowList as RL +import Record.Unsafe (unsafeGet, unsafeSet) +import Type.Data.RowList (RLProxy(..)) -- | The `Ring` class is for types that support addition, multiplication, -- | and subtraction operations. @@ -30,9 +36,34 @@ instance ringUnit :: Ring Unit where instance ringFn :: Ring b => Ring (a -> b) where sub f g x = f x - g x +instance ringRecord :: (RL.RowToList row list, RingRecord list row row) => Ring (Record row) where + sub = subRecord (RLProxy :: RLProxy list) + -- | `negate x` can be used as a shorthand for `zero - x`. negate :: forall a. Ring a => a -> a negate a = zero - a foreign import intSub :: Int -> Int -> Int foreign import numSub :: Number -> Number -> Number + +-- | A class for records where all fields have `Ring` instances, used to +-- | implement the `Ring` instance for records. +class SemiringRecord rowlist row subrow <= RingRecord rowlist row subrow | rowlist -> subrow where + subRecord :: RLProxy rowlist -> Record row -> Record row -> Record subrow + +instance ringRecordNil :: RingRecord RL.Nil row () where + subRecord _ _ _ = {} + +instance ringRecordCons + :: ( IsSymbol key + , Row.Cons key focus subrowTail subrow + , RingRecord rowlistTail row subrowTail + , Ring focus + ) + => RingRecord (RL.Cons key focus rowlistTail) row subrow where + subRecord _ ra rb = insert (get ra - get rb) tail + where + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + key = reflectSymbol (SProxy :: SProxy key) + get = unsafeGet key :: Record row -> focus + tail = subRecord (RLProxy :: RLProxy rowlistTail) ra rb diff --git a/src/Data/Semigroup.purs b/src/Data/Semigroup.purs index ec7ac207..db5bed20 100644 --- a/src/Data/Semigroup.purs +++ b/src/Data/Semigroup.purs @@ -1,7 +1,15 @@ -module Data.Semigroup (class Semigroup, append, (<>)) where +module Data.Semigroup + ( class Semigroup, append, (<>) + , class SemigroupRecord, appendRecord + ) where +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) import Data.Unit (Unit, unit) import Data.Void (Void, absurd) +import Prim.Row as Row +import Prim.RowList as RL +import Record.Unsafe (unsafeGet, unsafeSet) +import Type.Data.RowList (RLProxy(..)) -- | The `Semigroup` type class identifies an associative operation on a type. -- | @@ -31,5 +39,30 @@ instance semigroupFn :: Semigroup s' => Semigroup (s -> s') where instance semigroupArray :: Semigroup (Array a) where append = concatArray +instance semigroupRecord :: (RL.RowToList row list, SemigroupRecord list row row) => Semigroup (Record row) where + append = appendRecord (RLProxy :: RLProxy list) + foreign import concatString :: String -> String -> String foreign import concatArray :: forall a. Array a -> Array a -> Array a + +-- | A class for records where all fields have `Semigroup` instances, used to +-- | implement the `Semigroup` instance for records. +class SemigroupRecord rowlist row subrow | rowlist -> subrow where + appendRecord :: RLProxy rowlist -> Record row -> Record row -> Record subrow + +instance semigroupRecordNil :: SemigroupRecord RL.Nil row () where + appendRecord _ _ _ = {} + +instance semigroupRecordCons + :: ( IsSymbol key + , Row.Cons key focus subrowTail subrow + , SemigroupRecord rowlistTail row subrowTail + , Semigroup focus + ) + => SemigroupRecord (RL.Cons key focus rowlistTail) row subrow where + appendRecord _ ra rb = insert (get ra <> get rb) tail + where + key = reflectSymbol (SProxy :: SProxy key) + get = unsafeGet key :: Record row -> focus + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + tail = appendRecord (RLProxy :: RLProxy rowlistTail) ra rb diff --git a/src/Data/Semigroup/First.purs b/src/Data/Semigroup/First.purs new file mode 100644 index 00000000..18681bb0 --- /dev/null +++ b/src/Data/Semigroup/First.purs @@ -0,0 +1,40 @@ +module Data.Semigroup.First where + +import Prelude + +import Data.Eq (class Eq1) +import Data.Ord (class Ord1) + +-- | Semigroup where `append` always takes the first option. +-- | +-- | ``` purescript +-- | First x <> First y == First x +-- | ``` +newtype First a = First a + +derive newtype instance eqFirst :: Eq a => Eq (First a) +derive instance eq1First :: Eq1 First + +derive newtype instance ordFirst :: Ord a => Ord (First a) +derive instance ord1First :: Ord1 First + +derive newtype instance boundedFirst :: Bounded a => Bounded (First a) + +instance showFirst :: Show a => Show (First a) where + show (First a) = "(First " <> show a <> ")" + +derive instance functorFirst :: Functor First + +instance applyFirst :: Apply First where + apply (First f) (First x) = First (f x) + +instance applicativeFirst :: Applicative First where + pure = First + +instance bindFirst :: Bind First where + bind (First x) f = f x + +instance monadFirst :: Monad First + +instance semigroupFirst :: Semigroup (First a) where + append x _ = x diff --git a/src/Data/Semigroup/Last.purs b/src/Data/Semigroup/Last.purs new file mode 100644 index 00000000..1dbd3244 --- /dev/null +++ b/src/Data/Semigroup/Last.purs @@ -0,0 +1,40 @@ +module Data.Semigroup.Last where + +import Prelude + +import Data.Eq (class Eq1) +import Data.Ord (class Ord1) + +-- | Semigroup where `append` always takes the second option. +-- | +-- | ``` purescript +-- | Last x <> Last y == Last x +-- | ``` +newtype Last a = Last a + +derive newtype instance eqLast :: Eq a => Eq (Last a) +derive instance eq1Last :: Eq1 Last + +derive newtype instance ordLast :: Ord a => Ord (Last a) +derive instance ord1Last :: Ord1 Last + +derive newtype instance boundedLast :: Bounded a => Bounded (Last a) + +instance showLast :: Show a => Show (Last a) where + show (Last a) = "(Last " <> show a <> ")" + +derive instance functorLast :: Functor Last + +instance applyLast :: Apply Last where + apply (Last f) (Last x) = Last (f x) + +instance applicativeLast :: Applicative Last where + pure = Last + +instance bindLast :: Bind Last where + bind (Last x) f = f x + +instance monadLast :: Monad Last + +instance semigroupLast :: Semigroup (Last a) where + append _ x = x diff --git a/src/Data/Semiring.purs b/src/Data/Semiring.purs index 75f1e591..e221cf3d 100644 --- a/src/Data/Semiring.purs +++ b/src/Data/Semiring.purs @@ -1,6 +1,15 @@ -module Data.Semiring (class Semiring, add, (+), zero, mul, (*), one) where +module Data.Semiring + ( class Semiring, add, (+), zero, mul, (*), one + , class SemiringRecord, addRecord, mulRecord, oneRecord, zeroRecord + ) where +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) import Data.Unit (Unit, unit) +import Prim.Row as Row +import Prim.RowList as RL +import Record.Unsafe (unsafeGet, unsafeSet) +import Type.Data.Row (RProxy(..)) +import Type.Data.RowList (RLProxy(..)) -- | The `Semiring` class is for types that support an addition and -- | multiplication operation. @@ -56,7 +65,60 @@ instance semiringUnit :: Semiring Unit where mul _ _ = unit one = unit +instance semiringRecord :: (RL.RowToList row list, SemiringRecord list row row) => Semiring (Record row) where + add = addRecord (RLProxy :: RLProxy list) + mul = mulRecord (RLProxy :: RLProxy list) + one = oneRecord (RLProxy :: RLProxy list) (RProxy :: RProxy row) + zero = zeroRecord (RLProxy :: RLProxy list) (RProxy :: RProxy row) + foreign import intAdd :: Int -> Int -> Int foreign import intMul :: Int -> Int -> Int foreign import numAdd :: Number -> Number -> Number foreign import numMul :: Number -> Number -> Number + +-- | A class for records where all fields have `Semiring` instances, used to +-- | implement the `Semiring` instance for records. +class SemiringRecord rowlist row subrow | rowlist -> subrow where + addRecord :: RLProxy rowlist -> Record row -> Record row -> Record subrow + mulRecord :: RLProxy rowlist -> Record row -> Record row -> Record subrow + oneRecord :: RLProxy rowlist -> RProxy row -> Record subrow + zeroRecord :: RLProxy rowlist -> RProxy row -> Record subrow + +instance semiringRecordNil :: SemiringRecord RL.Nil row () where + addRecord _ _ _ = {} + mulRecord _ _ _ = {} + oneRecord _ _ = {} + zeroRecord _ _ = {} + +instance semiringRecordCons + :: ( IsSymbol key + , Row.Cons key focus subrowTail subrow + , SemiringRecord rowlistTail row subrowTail + , Semiring focus + ) + => SemiringRecord (RL.Cons key focus rowlistTail) row subrow where + addRecord _ ra rb = insert (get ra + get rb) tail + where + key = reflectSymbol (SProxy :: SProxy key) + get = unsafeGet key :: Record row -> focus + tail = addRecord (RLProxy :: RLProxy rowlistTail) ra rb + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + + mulRecord _ ra rb = insert (get ra * get rb) tail + where + key = reflectSymbol (SProxy :: SProxy key) + get = unsafeGet key :: Record row -> focus + tail = mulRecord (RLProxy :: RLProxy rowlistTail) ra rb + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + + oneRecord _ _ = insert one tail + where + key = reflectSymbol (SProxy :: SProxy key) + tail = oneRecord (RLProxy :: RLProxy rowlistTail) (RProxy :: RProxy row) + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow + + zeroRecord _ _ = insert zero tail + where + key = reflectSymbol (SProxy :: SProxy key) + tail = zeroRecord (RLProxy :: RLProxy rowlistTail) (RProxy :: RProxy row) + insert = unsafeSet key :: focus -> Record subrowTail -> Record subrow diff --git a/src/Data/Show.js b/src/Data/Show.js index 1bef3390..4a85cd66 100644 --- a/src/Data/Show.js +++ b/src/Data/Show.js @@ -59,3 +59,15 @@ exports.showArrayImpl = function (f) { return "[" + ss.join(",") + "]"; }; }; + +exports.cons = function (head) { + return function (tail) { + return [head].concat(tail); + }; +}; + +exports.join = function (separator) { + return function (xs) { + return xs.join(separator); + }; +}; diff --git a/src/Data/Show.purs b/src/Data/Show.purs index 18ec08cf..836a61c9 100644 --- a/src/Data/Show.purs +++ b/src/Data/Show.purs @@ -1,4 +1,12 @@ -module Data.Show (class Show, show) where +module Data.Show + ( class Show, show + , class ShowRecordFields, showRecordFields + ) where + +import Data.Symbol (class IsSymbol, SProxy(..), reflectSymbol) +import Prim.RowList as RL +import Record.Unsafe (unsafeGet) +import Type.Data.RowList (RLProxy(..)) -- | The `Show` type class represents those types which can be converted into -- | a human-readable `String` representation. @@ -28,8 +36,36 @@ instance showString :: Show String where instance showArray :: Show a => Show (Array a) where show = showArrayImpl show +instance showRecord :: (RL.RowToList rs ls, ShowRecordFields ls rs) => Show (Record rs) where + show record = case showRecordFields (RLProxy :: RLProxy ls) record of + [] -> "{}" + fields -> join " " ["{", join ", " fields, "}"] + +-- | A class for records where all fields have `Show` instances, used to +-- | implement the `Show` instance for records. +class ShowRecordFields rowlist row where + showRecordFields :: RLProxy rowlist -> Record row -> Array String + +instance showRecordFieldsNil :: ShowRecordFields RL.Nil row where + showRecordFields _ _ = [] + +instance showRecordFieldsCons + :: ( IsSymbol key + , ShowRecordFields rowlistTail row + , Show focus + ) + => ShowRecordFields (RL.Cons key focus rowlistTail) row where + showRecordFields _ record + = cons (join ": " [ key, show focus ]) tail + where + key = reflectSymbol (SProxy :: SProxy key) + focus = unsafeGet key record :: focus + tail = showRecordFields (RLProxy :: RLProxy rowlistTail) record + foreign import showIntImpl :: Int -> String foreign import showNumberImpl :: Number -> String foreign import showCharImpl :: Char -> String foreign import showStringImpl :: String -> String foreign import showArrayImpl :: forall a. (a -> String) -> Array a -> String +foreign import cons :: forall a. a -> Array a -> Array a +foreign import join :: String -> Array String -> String diff --git a/src/Data/Symbol.js b/src/Data/Symbol.js new file mode 100644 index 00000000..b4b6e28f --- /dev/null +++ b/src/Data/Symbol.js @@ -0,0 +1,8 @@ +"use strict"; + +// module Data.Symbol + +exports.unsafeCoerce = function (arg) { + return arg; +}; + diff --git a/src/Data/Symbol.purs b/src/Data/Symbol.purs new file mode 100644 index 00000000..bda2cd3e --- /dev/null +++ b/src/Data/Symbol.purs @@ -0,0 +1,24 @@ +module Data.Symbol + ( class IsSymbol + , reflectSymbol + , reifySymbol + , SProxy(..) + ) where + +-- | A value-level proxy for a type-level symbol. +data SProxy (sym :: Symbol) = SProxy + +-- | A class for known symbols +class IsSymbol (sym :: Symbol) where + reflectSymbol :: SProxy sym -> String + +-- local definition for use in `reifySymbol` +foreign import unsafeCoerce :: forall a b. a -> b + +reifySymbol :: forall r. String -> (forall sym. IsSymbol sym => SProxy sym -> r) -> r +reifySymbol s f = coerce f { reflectSymbol: \_ -> s } SProxy where + coerce + :: (forall sym1. IsSymbol sym1 => SProxy sym1 -> r) + -> { reflectSymbol :: SProxy "" -> String } -> SProxy "" -> r + coerce = unsafeCoerce + diff --git a/src/Prelude.purs b/src/Prelude.purs index af9dd21e..3a1cd439 100644 --- a/src/Prelude.purs +++ b/src/Prelude.purs @@ -16,6 +16,7 @@ module Prelude , module Data.Function , module Data.Functor , module Data.HeytingAlgebra + , module Data.Monoid , module Data.NaturalTransformation , module Data.Ord , module Data.Ordering @@ -30,7 +31,7 @@ module Prelude import Control.Applicative (class Applicative, pure, liftA1, unless, when) import Control.Apply (class Apply, apply, (*>), (<*), (<*>)) import Control.Bind (class Bind, bind, class Discard, discard, ifM, join, (<=<), (=<<), (>=>), (>>=)) -import Control.Category (class Category, id) +import Control.Category (class Category, identity) import Control.Monad (class Monad, ap, liftM1, unlessM, whenM) import Control.Semigroupoid (class Semigroupoid, compose, (<<<), (>>>)) @@ -45,6 +46,7 @@ import Data.Field (class Field) import Data.Function (const, flip, ($), (#)) import Data.Functor (class Functor, flap, map, void, ($>), (<#>), (<$), (<$>), (<@>)) import Data.HeytingAlgebra (class HeytingAlgebra, conj, disj, not, (&&), (||)) +import Data.Monoid (class Monoid, mempty) import Data.NaturalTransformation (type (~>)) import Data.Ord (class Ord, compare, (<), (<=), (>), (>=), comparing, min, max, clamp, between) import Data.Ordering (Ordering(..)) diff --git a/src/Record/Unsafe.js b/src/Record/Unsafe.js new file mode 100644 index 00000000..c47acf8d --- /dev/null +++ b/src/Record/Unsafe.js @@ -0,0 +1,40 @@ +"use strict"; + +exports.unsafeHas = function (label) { + return function (rec) { + return {}.hasOwnProperty.call(rec, label); + }; +}; + +exports.unsafeGet = function (label) { + return function (rec) { + return rec[label]; + }; +}; + +exports.unsafeSet = function (label) { + return function (value) { + return function (rec) { + var copy = {}; + for (var key in rec) { + if ({}.hasOwnProperty.call(rec, key)) { + copy[key] = rec[key]; + } + } + copy[label] = value; + return copy; + }; + }; +}; + +exports.unsafeDelete = function (label) { + return function (rec) { + var copy = {}; + for (var key in rec) { + if (key !== label && {}.hasOwnProperty.call(rec, key)) { + copy[key] = rec[key]; + } + } + return copy; + }; +}; diff --git a/src/Record/Unsafe.purs b/src/Record/Unsafe.purs new file mode 100644 index 00000000..adeaade7 --- /dev/null +++ b/src/Record/Unsafe.purs @@ -0,0 +1,27 @@ +-- | The functions in this module are highly unsafe as they treat records like +-- | stringly-keyed maps and can coerce the row of labels that a record has. +-- | +-- | These function are intended for situations where there is some other way of +-- | proving things about the structure of the record - for example, when using +-- | `RowToList`. **They should never be used for general record manipulation.** +module Record.Unsafe where + +-- | Checks if a record has a key, using a string for the key. +foreign import unsafeHas :: forall r1. String -> Record r1 -> Boolean + +-- | Unsafely gets a value from a record, using a string for the key. +-- | +-- | If the key does not exist this will cause a runtime error elsewhere. +foreign import unsafeGet :: forall r a. String -> Record r -> a + +-- | Unsafely sets a value on a record, using a string for the key. +-- | +-- | The output record's row is unspecified so can be coerced to any row. If the +-- | output type is incorrect it will cause a runtime error elsewhere. +foreign import unsafeSet :: forall r1 r2 a. String -> a -> Record r1 -> Record r2 + +-- | Unsafely removes a value on a record, using a string for the key. +-- | +-- | The output record's row is unspecified so can be coerced to any row. If the +-- | output type is incorrect it will cause a runtime error elsewhere. +foreign import unsafeDelete :: forall r1 r2. String -> Record r1 -> Record r2 diff --git a/src/Type/Data/Row.purs b/src/Type/Data/Row.purs new file mode 100644 index 00000000..0dd2113b --- /dev/null +++ b/src/Type/Data/Row.purs @@ -0,0 +1,4 @@ +module Type.Data.Row where + +data RProxy (row :: # Type) + = RProxy diff --git a/src/Type/Data/RowList.purs b/src/Type/Data/RowList.purs new file mode 100644 index 00000000..68e8aba6 --- /dev/null +++ b/src/Type/Data/RowList.purs @@ -0,0 +1,7 @@ +module Type.Data.RowList where + +import Prim.RowList (kind RowList) + +-- | A proxy to carry information about a rowlist. +data RLProxy (rowlist :: RowList) + = RLProxy diff --git a/test/Test/Main.purs b/test/Test/Main.purs index 983f5b47..9e01851e 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -1,6 +1,8 @@ module Test.Main where import Prelude +import Data.HeytingAlgebra (ff, tt, implies) +import Data.Ord (abs) type AlmostEff = Unit -> Unit @@ -9,7 +11,9 @@ main = do testNumberShow show testOrderings testOrdUtils + testIntDivMod testIntDegree + testRecordInstances foreign import testNumberShow :: (Number -> String) -> AlmostEff foreign import throwErr :: String -> AlmostEff @@ -27,10 +31,6 @@ testOrd x y ord = nan :: Number nan = 0.0/0.0 --- Unfortunately, NaN inhabits our Int -intNan :: Int -intNan = mod 1 0 - plusInfinity :: Number plusInfinity = 1.0/0.0 @@ -58,7 +58,8 @@ testOrderings = do assert "NaN > 1 should be false" $ (nan > 1.0) == false assert "NaN < 1 should be false" $ (nan < 1.0) == false assert "NaN == 1 should be false" $ nan /= 1.0 - testOrd intNan 2147483647 GT + testOrd (1 / 0) 0 EQ + testOrd (mod 1 0) 0 EQ testOrd 'a' 'b' LT testOrd 'b' 'A' GT testOrd "10" "0" GT @@ -82,6 +83,32 @@ testOrdUtils = do assert "5 should be between 0 and 10" $ between 0 10 5 == true assert "15 should not be between 0 10" $ between 0 10 15 == false +testIntDivMod :: AlmostEff +testIntDivMod = do + -- Check when dividend goes into divisor exactly + go 8 2 + go (-8) 2 + go 8 (-2) + go (-8) (-2) + + -- Check when dividend does not go into divisor exactly + go 2 3 + go (-2) 3 + go 2 (-3) + go (-2) (-3) + + where + go a b = + let + q = a / b + r = a `mod` b + msg = show a <> " / " <> show b <> ": " + in do + assert (msg <> "Quotient/remainder law") $ + q * b + r == a + assert (msg <> "Remainder should be between 0 and `abs b`, got: " <> show r) $ + 0 <= r && r < abs b + testIntDegree :: AlmostEff testIntDegree = do let bot = bottom :: Int @@ -89,3 +116,31 @@ testIntDegree = do assert "degree returns absolute integers" $ degree 4 == 4 assert "degree returns absolute integers" $ degree bot >= 0 assert "degree does not return out-of-bounds integers" $ degree bot <= top + +testRecordInstances :: AlmostEff +testRecordInstances = do + assert "Record equality" $ { a: 1 } == { a: 1 } + assert "Record inequality" $ { a: 2 } /= { a: 1 } + assert "Record show" $ show { a: 1 } == "{ a: 1 }" + assert "Record +" $ ({ a: 1, b: 2.0 } + { a: 0, b: (-2.0) }) == { a: 1, b: 0.0 } + assert "Record *" $ ({ a: 1, b: 2.0 } * { a: 0, b: (-2.0) }) == { a: 0, b: -4.0 } + assert "Record one" $ one == { a: 1, b: 1.0 } + assert "Record zero" $ zero == { a: 0, b: 0.0 } + assert "Record sub" $ { a: 2, b: 2.0 } - { a: 1, b: 1.0 } == { a: 1, b: 1.0 } + assert "Record append" $ { a: [], b: "T" } <> { a: [1], b: "OM" } == { a: [1], b: "TOM" } + assert "Record mempty" $ mempty == { a: [] :: Array Int, b: "" } + assert "Record ff" $ ff == { a: false, b: false } + assert "Record tt" $ tt == { a: true, b: true } + assert "Record not" $ not { a: true, b: false } == { a: false, b: true } + assert "Record conj" $ conj + { a: true, b: false, c: true, d: false } + { a: true, b: true, c: false, d: false } + == { a: true, b: false, c: false, d: false } + assert "Record disj" $ disj + { a: true, b: false, c: true, d: false } + { a: true, b: true, c: false, d: false } + == { a: true, b: true, c: true, d: false } + assert "Record implies" $ implies + { a: true, b: false, c: true, d: false } + { a: true, b: true, c: false, d: false } + == { a: true, b: true, c: false, d: true }