implicit-params 0.1 → 0.2
raw patch · 3 files changed
+216/−100 lines, 3 filesdep +data-default-classdep −data-defaultdep ~basePVP ok
version bump matches the API change (PVP)
Dependencies added: data-default-class
Dependencies removed: data-default
Dependency ranges changed: base
API changes (from Hackage documentation)
- Data.Implicit: ($$~) :: (Implicit s a => b) -> (proxy s, a) -> b
- Data.Implicit: instance [overlap ok] (?param::Tagged s a) => Implicit s (Lift a)
- Data.Implicit: instance [overlap ok] Default a => Implicit s a
- Data.Implicit: type Implicit_ = Implicit Any
+ Data.Implicit: ($$) :: (a -> b) -> a -> b
+ Data.Implicit: (~$) :: (Implicit s a => b) -> proxy s -> a -> b
+ Data.Implicit: class Implicit_ a
+ Data.Implicit: instance Default a => Implicit k s a
+ Data.Implicit: instance Default a => Implicit_ a
- Data.Implicit: class Implicit (s :: Symbol) a
+ Data.Implicit: class Implicit s a
Files
- README.md +140/−0
- implicit-params.cabal +5/−4
- src/Data/Implicit.hs +71/−96
+ README.md view
@@ -0,0 +1,140 @@+# Implicit parameters++There are certain types of applications which are configurable where it makes+sense to model this configurability as a global or semi-global set of+configuration values that some or all parts of the program can "implicitly"+access. These configuration values are called "implicit parameters".++## `ImplicitParams` in Haskell++Haskell already has support for implicit parameters via the `ImplicitParams`+[extension][ImplicitParams]. However, `ImplicitParams` has several flaws and+is barely used at all in modern Haskell code. Many Haskellers consider its+(ab)use of `let`/`where` bindings to pass implicit parameters to be ugly.+Also, it's questionable how "implicit" the implicit parameters of+`ImplicitParams` actually are, as they show up in the context of the type+signature of any function which uses them. There's also no way you can call a+function taking an implicit parameter without passing it that parameter if it+isn't already in context: i.e., there is no way to specify a "default" value+for an implicit parameter if none is passed.++## Motivating example++`implicit-params` solves some of these problems and introduces new problems of+its own. However, there is one particular use case which motivated me to+develop `implicit-params` that isn't supported by the existing+`ImplicitParams` extension. Imagine you have the following code:++ app :: Config -> IO ()+ app config = doStuffWith config+ + defaultConfig :: Config+ defaultConfig = ...++Which is used by a program as follows:++ main = app defaultConfig++There are two problems with this code. One is that `app` has to manually plumb+the `Config` value it was given around everywhere. One solution here would be+for `app` to use a [Reader][Reader] monad internally, but that can complicate+the code in some ways and it seems like overkill. If it used the+`ImplicitParams` extension, the above code would look like this:++ app :: (?config :: Config) => IO ()+ app = doStuffWith ?config+ + defaultConfig :: Config+ defaultConfig = ...++ main = let ?config = defaultConfig in app++You can see why `ImplicitParams` isn't very highly regarded: it made the type+signature of `app` longer, as well as the code that uses it. However, at least+the internals of `app` will be somewhat nicer now as the `Config` value won't+have to be manually plumbed around everywhere.++## `data-default`++The [`data-default`][data-default] package provides a type class `Default`+which represents the class of types which have a "default" value. It has a+single operation `def` which returns the default value for a given type (the+type is given by type inference). Using `Default` the above code could be made+a little nicer:++ app :: (?config :: Config) => IO ()+ app = doStuffWith ?config+ + instance Default Config where def = ...++ main = let ?config = def in app++However, the above code is still so *ugly* considering what we're trying to+do: all we want to do is run `app` with the defaults. This should be as simple+as typing `app`, and only if we're overriding the defaults should the code+need to be any longer than this. This is exactly what `implicit-params` does.+If an implicit parameter is not explicitly given to a function which requires+it, its value is given by `def` for the `Default` instance for the type of the+parameter. And if the type does not have a `Default` instance, then it is a+type error to call that function without explicitly setting the implicit+parameter (but it will work fine if you do set it). This is how the above code+looks using `implicit-params`:++ app :: Implicit_ Config => IO ()+ app = doStuffWith param_+ + instance Default Config where def = defaultConfig++ main = app++Perfect! What if we want to pass a non-default config to `app`? That's easy+too:++ main = setParam_ (def {option = 1}) app++`setParam_` even has an infix synonym `$~` which makes the above code even+nicer:++ main = app $~ def {option = 1}++(Bonus points for not abusing `let`/`where` bindings.)++### Named implicit parameters++The above code uses unnamed implicit parameters, which will suffice for most+code. Sometimes you might want to pass more than one implicit parameter of the+same type to a single function, and for this you need some way of selecting+the particular implicit parameter on which to operate. `implicit-params` uses+type level [symbols][Symbols] for this, which require the `DataKinds`+[extension][DataKinds].++`Implicit_`, which is used in the examples above, denotes an unnamed implicit+parameter. `Implicit "foo"` can be used to denote a named implicit parameter+named `"foo"`. Named implicit parameters are slightly more awkward to use+because they require passing [`Proxy`][Proxy] parameters to the `param` and+`setParam` functions to specify the names of the implicit parameters on which+they are to operate. See the Haddock documentation of the `Data.Implicit`+module for more details.++## Acknowledgements++This wouldn't be possible without techniques that I learnt from+[Edward Kmett][edwardk] and [Philip JF][philipjf]. In particular, this package+uses ideas from Edward's packages [tagged][tagged], [constraints][constraints]+and [reflection][reflection] and Philip's blog posts [Haskell Supports+First-Class Instances][firstclass] and [Using Compiler Bugs for Fun and+Profit: Introducing Cartesian Closed Constraints][profit].++[ImplicitParams]: http://www.haskell.org/ghc/docs/latest/html/users_guide/other-type-extensions.html#implicit-parameters+[Reader]: http://hackage.haskell.org/packages/archive/mtl/latest/doc/html/Control-Monad-Reader-Class.html+[data-default]: http://hackage.haskell.org/package/data-default+[Symbols]: http://www.haskell.org/ghc/docs/latest/html/libraries/base/GHC-TypeLits.html#t:Symbol+[DataKinds]: http://www.haskell.org/ghc/docs/7.4.1/html/users_guide/kind-polymorphism-and-promotion.html+[Proxy]: http://hackage.haskell.org/packages/archive/tagged/latest/doc/html/Data-Proxy.html+[edwardk]: http://comonad.com/reader/+[philipjf]: http://joyoftypes.blogspot.com/+[tagged]: http://hackage.haskell.org/package/tagged+[constraints]: http://hackage.haskell.org/package/constraints+[reflection]: http://hackage.haskell.org/package/reflection+[firstclass]: http://joyoftypes.blogspot.com/2012/02/haskell-supports-first-class-instances.html+[profit]: http://joyoftypes.blogspot.com/2013/01/using-compiler-bugs-for-fun-and-profit.html
implicit-params.cabal view
@@ -1,5 +1,5 @@ name: implicit-params-version: 0.1+version: 0.2 synopsis: Named and unnamed implicit parameters with defaults. license: BSD3 license-file: LICENSE@@ -17,7 +17,8 @@ extra-source-files: CONTRIBUTORS,- LICENSE+ LICENSE,+ README.md library hs-source-dirs:@@ -27,8 +28,8 @@ Data.Implicit build-depends:- base >= 4.6 && < 5,- data-default > 0.2 && < 1+ base >= 4.3 && < 5,+ data-default-class > 0 && < 0.1 ghc-options: -Wall
src/Data/Implicit.hs view
@@ -1,11 +1,12 @@-{-# LANGUAGE ConstraintKinds #-}-{-# LANGUAGE DataKinds #-}-{-# LANGUAGE GADTs #-}-{-# LANGUAGE KindSignatures #-}-{-# LANGUAGE ImplicitParams #-}+{-# LANGUAGE CPP #-}+{-# LANGUAGE FlexibleContexts #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-}-{-# LANGUAGE OverlappingInstances #-}+#if __GLASGOW_HASKELL__ >= 706+{-# LANGUAGE PolyKinds #-}+#endif {-# LANGUAGE Rank2Types #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE UndecidableInstances #-} {-|@@ -55,32 +56,30 @@ import "Data.Implicit" import "Data.Proxy" -putFooBar :: ('Implicit' \"foo\" String, 'Implicit' \"bar\" String) => IO ()-putFooBar = do- putStrLn $ \"foo was: \" ++ show foo- putStrLn $ \"bar was: \" ++ show bar--foo :: 'Implicit' \"foo\" String => String-foo = 'param' (Proxy :: Proxy \"foo\")--bar :: 'Implicit' \"bar\" String => String-bar = 'param' (Proxy :: Proxy \"bar\")+foo :: Proxy \"foo\"+foo = Proxy -setFoo :: String -> ('Implicit' \"foo\" String => a) -> a-setFoo = 'setParam' (Proxy :: Proxy \"foo\")+bar :: Proxy \"bar\"+bar = Proxy -setBar :: String -> ('Implicit' \"bar\" String => a) -> a-setBar = 'setParam' (Proxy :: Proxy \"bar\")+putFooBar :: ('Implicit' \"foo\" String, 'Implicit' \"bar\" String) => IO ()+putFooBar = do+ putStrLn $ \"foo was: \" ++ show (param foo :: String)+ putStrLn $ \"bar was: \" ++ show (param bar :: String) @ The 'Implicit' constraint is the named equivalent of 'Implicit_'. It takes an-additional argument of kind 'Symbol' (which requires the @DataKinds@-extension; see the "GHC.TypeLits" module) to specify the name of the implicit-parameter. 'param' and 'setParam' work like their unnamed counterparts-'param_' and 'setParam_', but they also take a proxy argument to specify the-name of the implicit parameter. The code above defines the wrappers @foo@ and-@bar@ and @setFoo@ and @setBar@ around @param@ and @setParam@ respectively,-which hide all the (slightly ugly) proxy stuff.+additional argument @s@ to specify the name of the implicit parameter.+Implicit parameters can be \"named\" using any type (of any kind, on compilers+that support the @PolyKinds@ extension). (The above code uses type-level+strings of the @Symbol@ kind from the "GHC.TypeLits" module, which is the+recommended way to name implicit parameters. However, @Symbol@ requires the+@DataKinds@ extension and at least version 7.8 of GHC (7.6 is broken; see GHC+bug \#7502), so you are free to use other types of other kinds if you want to+support older versions of GHC.) 'param' and 'setParam' work like their unnamed+counterparts 'param_' and 'setParam_', but they also take a proxy argument to+specify the name of the implicit parameter. The code above defines @foo@ and+@bar@ to hide away the (slightly ugly) proxy stuff. >>> putFooBar foo was: ""@@ -89,18 +88,18 @@ Once again, the defaults of unspecified implicit parameters are given by the 'Default' class. ->>> setFoo "hello, world" putFooBar+>>> setParam foo "hello, world" putFooBar foo was: "hello, world" bar was: "" ->>> setBar "goodbye" $ setFoo "hello, world" putFooBar+>>> setParam bar "goodbye" $ setParam foo "hello, world" putFooBar foo was: "hello, world" bar was: "goodbye" -An infix version of @setParam@ is also provided, '$$~'. Using @$$~@, the above+An infix version of @setParam@ is also provided, '~$'. Using @~$@, the above example would be: ->>> putFooBar $$~ (Proxy :: Proxy "foo", "hello, world") $$~ (Proxy :: Proxy "bar", "goodbye")+>>> putFooBar ~$ foo ~$ bar $$ "goodbye" $$ "hello, world" foo was: "hello, world" bar was: "goodbye @@ -110,7 +109,8 @@ ( Implicit , param , setParam- , ($$~)+ , (~$)+ , ($$) , Implicit_ , param_@@ -119,9 +119,7 @@ ) where -import Data.Default (Default, def)-import GHC.TypeLits (Symbol)-import GHC.Exts (Any)+import Data.Default.Class (Default, def) import Unsafe.Coerce (unsafeCoerce) @@ -129,99 +127,76 @@ -- | The constraint @'Implicit' \"foo\" String@ on a function @f@ indicates -- that an implicit parameter named @\"foo\"@ of type @String@ is passed to -- @f@.------ The name @\"foo\"@ is a type of kind 'Symbol' (from the "GHC.TypeLits"--- module). The @DataKinds@ extension is required to refer to 'Symbol'-kinded--- types.-class Implicit (s :: Symbol) a where- _param :: proxy s -> a+class Implicit s a where+ -- | 'param' retrieves the implicit parameter named @s@ of type @a@ from+ -- the context @'Implicit' s a@. The name @s@ is specified by a proxy+ -- argument passed to @param@.+ param :: proxy s -> a ------------------------------------------------------------------------------ instance Default a => Implicit s a where- _param _ = def+ param _ = def --------------------------------------------------------------------------------- | 'param' retrieves the implicit parameter named @s@ of type @a@ from the--- context @'Implicit' s a@. The name @s@ is specified by a proxy argument--- passed to @param@.-param :: Implicit s a => proxy s -> a-param = _param+newtype Param s a b = Param (Implicit s a => b) ------------------------------------------------------------------------------ -- | 'setParam' supplies a value for an implicit parameter named @s@ to a -- function which takes a homotypic and homonymous implicit parameter. The -- name @s@ is specified by a proxy argument passed to @setParam@.-setParam :: proxy s -> a -> (Implicit s a => b) -> b-setParam = using+setParam :: forall a b proxy s. proxy s -> a -> (Implicit s a => b) -> b+setParam _ a f = unsafeCoerce (Param f :: Param s a b) (const a)+{-# INLINE setParam #-} ------------------------------------------------------------------------------ -- | An infix version of 'setParam' with flipped arguments.-($$~) :: (Implicit s a => b) -> (proxy s, a) -> b-($$~) f (proxy, a) = using proxy a f------------------------------------------------------------------------------------ | The constraint @'Implicit_' String@ on a function @f@ indicates that an--- unnamed implicit parameter of type @String@ is passed to @f@.-type Implicit_ = Implicit Any------------------------------------------------------------------------------------ | 'param_' retrieves the unnamed implicit parameter of type @a@ from the--- context @'Implicit_' a@.-param_ :: Implicit_ a => a-param_ = param (Proxy :: Proxy Any)------------------------------------------------------------------------------------ | 'setParam_' supplies a value for an unnamed implicit parameter to a--- function which takes a homotypic implicit parameter.-setParam_ :: a -> (Implicit_ a => b) -> b-setParam_ = setParam (Proxy :: Proxy Any)------------------------------------------------------------------------------------ | An infix version of 'setParam_' with flipped arguments.-($~) :: (Implicit_ a => b) -> a -> b-infixr 1 $~-f $~ a = using (Proxy :: Proxy Any) a f+(~$) :: forall a b proxy s. (Implicit s a => b) -> proxy s -> a -> b+infixl 1 ~$+(~$) f _ a = unsafeCoerce (Param f :: Param s a b) (const a)+{-# INLINE (~$) #-} -------------------------------------------------------------------------------data Proxy (s :: Symbol) = Proxy+-- | A left-associated version of '$'.+($$) :: (a -> b) -> a -> b+infixl 0 $$+($$) = id+{-# INLINE ($$) #-} -------------------------------------------------------------------------------newtype Lift a = Lift a+-- | The constraint @'Implicit_' String@ on a function @f@ indicates that an+-- unnamed implicit parameter of type @String@ is passed to @f@.+class Implicit_ a where+ param_ :: a -------------------------------------------------------------------------------newtype Tagged (s :: Symbol) a = Tagged a+instance Default a => Implicit_ a where+ -- | 'param_' retrieves the unnamed implicit parameter of type @a@ from+ -- the context @'Implicit_' a@.+ param_ = def -------------------------------------------------------------------------------data Dict c where- Dict :: c => Dict c+newtype Param_ a b = Param_ (Implicit_ a => b) -------------------------------------------------------------------------------using :: proxy s -> a -> (Implicit s a => b) -> b-using p a = with (unlift (dict p a))- where- with :: Dict c -> (c => b) -> b- with Dict b = b-- unlift :: Dict (c (Lift p)) -> Dict (c p)- unlift = unsafeCoerce-- dict :: proxy s -> a -> Dict (Implicit s (Lift a))- dict _ a' = let ?param = Tagged a' in Dict+-- | 'setParam_' supplies a value for an unnamed implicit parameter to a+-- function which takes a homotypic implicit parameter.+setParam_ :: forall a b. a -> (Implicit_ a => b) -> b+setParam_ a f = unsafeCoerce (Param_ f :: Param_ a b) a+{-# INLINE setParam_ #-} -------------------------------------------------------------------------------instance (?param :: Tagged s a) => Implicit s (Lift a) where- _param _ = let Tagged a = ?param in Lift a+-- | An infix version of 'setParam_' with flipped arguments.+($~) :: forall a b. (Implicit_ a => b) -> a -> b+infixl 1 $~+f $~ a = unsafeCoerce (Param_ f :: Param_ a b) a+{-# INLINE ($~) #-}