diff --git a/.gitignore b/.gitignore index 56aea88..9ccce03 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.pulp-cache .psci .psci_modules/ node_modules/ diff --git a/bower.json b/bower.json index 15cab7f..a498e69 100644 --- a/bower.json +++ b/bower.json @@ -17,7 +17,11 @@ ], "dependencies": { "purescript-foreign": "~0.7.0", - "purescript-unsafe-coerce": "~0.1.0" + "purescript-maps": "~0.5.4", + "purescript-tuples": "~0.4.0", + "purescript-monoid": "~0.3.2", + "purescript-maybe": "~0.3.5", + "purescript-contravariant": "~0.2.3" }, "devDependencies": { "purescript-console": "~0.1.1" diff --git a/docs/Data/Options.md b/docs/Data/Options.md index 01ad7d0..ba35387 100644 --- a/docs/Data/Options.md +++ b/docs/Data/Options.md @@ -3,77 +3,99 @@ #### `Options` ``` purescript -data Options :: * -> * +newtype Options opt ``` +The `Options` type represents a set of options. The type argument is a +phantom type, which is useful for ensuring that options for one particular +API are not accidentally passed to some other API. + ##### Instances ``` purescript -instance semigroupOptions :: Semigroup (Options a) -instance monoidOptions :: Monoid (Options a) +Semigroup (Options opt) +Monoid (Options opt) ``` -#### `Option` +#### `runOptions` ``` purescript -data Option :: * -> * -> * +runOptions :: forall opt. Options opt -> Array (Tuple String Foreign) ``` -#### `IsOption` +#### `options` ``` purescript -class IsOption value where - assoc :: forall opt. Option opt value -> value -> Options opt +options :: forall opt. Options opt -> Foreign ``` -##### Instances +Convert an `Options` value into a JavaScript object, suitable for passing +to JavaScript APIs. + +#### `Option` + ``` purescript -instance isOptionString :: IsOption String -instance isOptionBoolean :: IsOption Boolean -instance isOptionNumber :: IsOption Number -instance isOptionInt :: IsOption Int -instance isOptionRecord :: IsOption { | a } -instance isOptionUnit :: IsOption Unit -instance isOptionFunction :: IsOption (a -> b) -instance isOptionArray :: (IsOption a) => IsOption (Array a) -instance isOptionMaybe :: (IsOption a) => IsOption (Maybe a) +type Option opt = Op (Options opt) ``` -#### `(:=)` +An `Option` represents an opportunity to configure a specific attribute +of a call to some API. This normally corresponds to one specific property +of an "options" object in JavaScript APIs, but can in general correspond +to zero or more actual properties. + +#### `assoc` ``` purescript -(:=) :: forall opt value. (IsOption value) => Option opt value -> value -> Options opt +assoc :: forall opt value. Option opt value -> value -> Options opt ``` -_right-associative / precedence 6_ +Associates a value with a specific option. -#### `optionFn` +#### `(:=)` ``` purescript -optionFn :: forall opt from to. Option opt from -> Option opt to +(:=) :: forall opt value. Option opt value -> value -> Options opt ``` -#### `key` +_right-associative / precedence 6_ + +An infix version of `assoc`. + +#### `optional` ``` purescript -key :: forall opt value. Option opt value -> String +optional :: forall opt value. Option opt value -> Option opt (Maybe value) ``` +A version of `assoc` which takes possibly absent values. `Nothing` values +are ignored; passing `Nothing` for the second argument will result in an +empty `Options`. + #### `opt` ``` purescript -opt :: forall opt value. (IsOption value) => String -> Option opt value +opt :: forall opt value. String -> Option opt value ``` -#### `runOptions` +The default way of creating `Option` values. Constructs an `Option` with +the given key, which passes the given value through unchanged. + +#### `tag` ``` purescript -runOptions :: forall a. Options a -> Array (Tuple String Foreign) +tag :: forall opt value. Option opt value -> value -> Option opt Unit ``` -#### `options` +Create a `tag`, by fixing an `Option` to a single value. + +#### `defaultToOptions` ``` purescript -options :: forall a. Options a -> Foreign +defaultToOptions :: forall opt value. String -> value -> Options opt ``` +The default method for turning a string property key into an +`Option`. This function simply calls `toForeign` on the value. If +you need some other behaviour, you can write your own function to replace +this one, and construct an `Option` yourself. + diff --git a/src/Data/Options.js b/src/Data/Options.js deleted file mode 100644 index f7e26bd..0000000 --- a/src/Data/Options.js +++ /dev/null @@ -1,63 +0,0 @@ -'use strict'; - -// module Data.Options - -var memptyFn = []; - -function appendFn(o1, o2) { - return o1.concat(o2); -} - -function joinFn(os) { - var k = null; - var vs = []; - var i = -1; - var n = os.length; - while(++i < n) { - k = os[i][0][0]; - vs.push(os[i][0][1]); - } - return [[k, vs]]; -} - -function isOptionPrimFn(k, v) { - return [[k, v]]; -} - -function options(o) { - var res = {}; - var n = o.length; - var i; - for (i = 0; i < n; i++) { - var k = o[i][0]; - var v = o[i][1]; - res[k] = v; - } - return res; -} - -function runOptionsFn(Tuple) { - return function(o){ - var res = []; - var n = o.length; - var i; - for (i = 0; i < n; i++) { - var k = o[i][0]; - var v = o[i][1]; - res[i] = Tuple(k)(v); - } - return res; - }; -} - -exports.memptyFn = memptyFn; - -exports.appendFn = appendFn; - -exports.joinFn = joinFn; - -exports.isOptionPrimFn = isOptionPrimFn; - -exports.options = options; - -exports.runOptionsFn = runOptionsFn diff --git a/src/Data/Options.purs b/src/Data/Options.purs index cb3d1ab..c66977d 100644 --- a/src/Data/Options.purs +++ b/src/Data/Options.purs @@ -1,94 +1,78 @@ module Data.Options ( Options() - , Option() - , IsOption - , assoc, (:=) - , optionFn - , options , runOptions - , opt, key + , options + , Option + , assoc, (:=) + , optional + , opt + , tag + , defaultToOptions ) where import Prelude -import Data.Foreign (Foreign()) - -import Data.Function (Fn2(), runFn2) +import Data.Foreign (toForeign, Foreign()) +import Data.Maybe (Maybe(), maybe) +import Data.Monoid (mempty, Monoid) +import Data.Op (Op(Op), runOp) +import Data.StrMap as StrMap +import Data.Tuple (Tuple(..)) -import Data.Maybe (Maybe(..)) +-- | The `Options` type represents a set of options. The type argument is a +-- | phantom type, which is useful for ensuring that options for one particular +-- | API are not accidentally passed to some other API. +newtype Options opt = + Options (Array (Tuple String Foreign)) -import Data.Monoid (Monoid) +runOptions :: forall opt. Options opt -> Array (Tuple String Foreign) +runOptions (Options xs) = xs -import Data.Tuple (Tuple(..)) +instance semigroupOptions :: Semigroup (Options opt) where + append (Options xs) (Options ys) = Options (xs <> ys) -import Unsafe.Coerce (unsafeCoerce) +instance monoidOptions :: Monoid (Options opt) where + mempty = Options [] -foreign import data Options :: * -> * +-- | Convert an `Options` value into a JavaScript object, suitable for passing +-- | to JavaScript APIs. +options :: forall opt. Options opt -> Foreign +options = toForeign <<< StrMap.fromFoldable <<< runOptions -foreign import data Option :: * -> * -> * +-- | An `Option` represents an opportunity to configure a specific attribute +-- | of a call to some API. This normally corresponds to one specific property +-- | of an "options" object in JavaScript APIs, but can in general correspond +-- | to zero or more actual properties. +type Option opt = Op (Options opt) -class IsOption value where - assoc :: forall opt. Option opt value -> value -> Options opt +-- | Associates a value with a specific option. +assoc :: forall opt value. Option opt value -> value -> Options opt +assoc o value = runOp o value infixr 6 := -(:=) :: forall opt value. (IsOption value) => Option opt value -> value -> Options opt +-- | An infix version of `assoc`. +(:=) :: forall opt value. Option opt value -> value -> Options opt (:=) = assoc -instance semigroupOptions :: Semigroup (Options a) where - append = runFn2 appendFn - -instance monoidOptions :: Monoid (Options a) where - mempty = memptyFn - -instance isOptionString :: IsOption String where - assoc = runFn2 isOptionPrimFn - -instance isOptionBoolean :: IsOption Boolean where - assoc = runFn2 isOptionPrimFn - -instance isOptionNumber :: IsOption Number where - assoc = runFn2 isOptionPrimFn - -instance isOptionInt :: IsOption Int where - assoc = runFn2 isOptionPrimFn - -instance isOptionRecord :: IsOption { | a } where - assoc = runFn2 isOptionPrimFn - -instance isOptionUnit :: IsOption Unit where - assoc = runFn2 isOptionPrimFn - -instance isOptionFunction :: IsOption (a -> b) where - assoc = runFn2 isOptionPrimFn - -instance isOptionArray :: (IsOption a) => IsOption (Array a) where - assoc k vs = joinFn $ assoc (optionFn k) <$> vs - -instance isOptionMaybe :: (IsOption a) => IsOption (Maybe a) where - assoc k Nothing = memptyFn - assoc k (Just a) = assoc (optionFn k) a - -optionFn :: forall opt from to. Option opt from -> Option opt to -optionFn = unsafeCoerce - -key :: forall opt value. Option opt value -> String -key = unsafeCoerce - -opt :: forall opt value. (IsOption value) => String -> Option opt value -opt = unsafeCoerce - -runOptions :: forall a. Options a -> Array (Tuple String Foreign) -runOptions = runOptionsFn Tuple - -foreign import memptyFn :: forall a. Options a - -foreign import appendFn :: forall a. Fn2 (Options a) (Options a) (Options a) - -foreign import joinFn :: forall a b. Array (Options a) -> Options b - -foreign import isOptionPrimFn :: forall b a. Fn2 (Option b a) a (Options b) - -foreign import options :: forall a. Options a -> Foreign - -foreign import runOptionsFn :: forall a. (String -> Foreign -> Tuple String Foreign) -> Options a -> Array (Tuple String Foreign) +-- | A version of `assoc` which takes possibly absent values. `Nothing` values +-- | are ignored; passing `Nothing` for the second argument will result in an +-- | empty `Options`. +optional :: forall opt value. Option opt value -> Option opt (Maybe value) +optional option = Op $ maybe mempty (option :=) + +-- | The default way of creating `Option` values. Constructs an `Option` with +-- | the given key, which passes the given value through unchanged. +opt :: forall opt value. String -> Option opt value +opt = Op <<< defaultToOptions + +-- | Create a `tag`, by fixing an `Option` to a single value. +tag :: forall opt value. Option opt value -> value -> Option opt Unit +tag o value = Op \_ -> o := value + +-- | The default method for turning a string property key into an +-- | `Option`. This function simply calls `toForeign` on the value. If +-- | you need some other behaviour, you can write your own function to replace +-- | this one, and construct an `Option` yourself. +defaultToOptions :: forall opt value. String -> value -> Options opt +defaultToOptions k v = Options [Tuple k (toForeign v)] diff --git a/test/Test/Main.purs b/test/Test/Main.purs index e283b50..5eb66c3 100644 --- a/test/Test/Main.purs +++ b/test/Test/Main.purs @@ -3,13 +3,10 @@ module Test.Main where import Prelude import Control.Monad.Eff.Console (log, print) - import Data.Foreign (Foreign()) - +import Data.Functor.Contravariant (cmap) import Data.Maybe (Maybe(..)) - -import Data.Options (Option(), IsOption, optionFn, options, opt, (:=), assoc, runOptions) - +import Data.Options (Option, Options, optional, options, opt, (:=), runOptions) import Data.Tuple (Tuple(..)) data Shape = Circle | Square | Triangle @@ -19,20 +16,33 @@ instance shapeShow :: Show Shape where show Square = "square" show Triangle = "triangle" -instance shapeIsOption :: IsOption Shape where - assoc k a = assoc (optionFn k) (show a) - foreign import data Foo :: * -foo = opt "foo" :: Option Foo String -bar = opt "bar" :: Option Foo Int -baz = opt "baz" :: Option Foo Boolean -bam = opt "bam" :: Option Foo (Maybe String) -fiz = opt "fiz" :: Option Foo (Maybe String) -biz = opt "biz" :: Option Foo Shape -buz = opt "buz" :: Option Foo (Int -> Int -> Int -> Int) -fuz = opt "fuz" :: Option Foo (Array Shape) +foo :: Option Foo String +foo = opt "foo" + +bar :: Option Foo Int +bar = opt "bar" + +baz :: Option Foo Boolean +baz = opt "baz" + +bam :: Option Foo (Maybe String) +bam = optional (opt "bam") + +fiz :: Option Foo (Maybe String) +fiz = optional (opt "fiz") + +biz :: Option Foo Shape +biz = cmap show (opt "shape") + +buz :: Option Foo (Int -> Int -> Int -> Int) +buz = opt "buz" + +fuz :: Option Foo (Array Shape) +fuz = cmap (map show) (opt "fuz") +opts :: Options Foo opts = foo := "aaa" <> bar := 10 <> baz := true <> @@ -42,8 +52,6 @@ opts = foo := "aaa" <> buz := (\a b c -> a + b + c) <> fuz := [Square, Circle, Triangle] -main = do - (log <<< showForeign <<< options) opts - print $ (\(Tuple k v) -> Tuple k (showForeign v)) <$> runOptions opts +main = log <<< showForeign <<< options $ opts foreign import showForeign :: Foreign -> String