HList-0.5.3.0: Heterogeneous lists
Safe HaskellSafe-Inferred
LanguageHaskell2010

Data.HList.Keyword

Description

The public interface is exposed in CommonMain#Kw

Synopsis

main

class Kw (fn :: *) (arg_def :: [*]) r where Source #

kw takes a HList whose first element is a function, and the rest of the elements are default values. A useful trick is to have a final argument () which is not eaten up by a label (A only takes 1 argument). That way when you supply the () it knows there are no more arguments (?).

>>> data A = A
>>> instance IsKeyFN (A -> a -> b) True
>>> let f A a () = a + 1
>>> let f' = f .*. A .*. 1 .*. HNil
>>> kw f' A 0 ()
1
>>> kw f' ()
2

Methods

kw :: HList (fn ': arg_def) -> r Source #

Instances

Instances details
(KW' rflag fn akws arg_def r, akws ~ Arg kws ('[] :: [Type]), ReflectFK' flag fn kws, IsKeyFN r rflag, IsKeyFN fn flag) => Kw fn arg_def r Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kw :: HList (fn ': arg_def) -> r Source #

class IsKeyFN (t :: *) (flag :: Bool) | t -> flag Source #

All our keywords must be registered

Instances

Instances details
'False ~ flag => IsKeyFN t flag Source #

overlapping/fallback case

Instance details

Defined in Data.HList.TypeEqO

IsKeyFN (Label s -> a -> b) 'True Source #

labels that impose no restriction on the type of the (single) argument which follows

>>> let testF (_ :: Label "a") (a :: Int) () = a+1
>>> kw (hBuild testF) (Label :: Label "a") 5 ()
6
Instance details

Defined in Data.HList.Keyword

r ~ (c -> b) => IsKeyFN (K s c -> r) 'True Source #

The purpose of this instance is to be able to use the same Symbol (type-level string) at different types. If they are supposed to be the same, then use Label instead of K

>>> let kA = K :: forall t. K "a" t
>>> let testF (K :: K "a" Int) a1 (K :: K "a" Integer) a2 () = a1-fromIntegral a2

therefore the following options works:

>>> kw (hBuild testF) kA (5 :: Int) kA (3 :: Integer) ()
2
>>> kw (hBuild testF) (K :: K "a" Integer) 3 (K :: K "a" Int) 5 ()
2

But you cannot leave off all Int or Integer annotations.

Instance details

Defined in Data.HList.Keyword

recToKW :: forall a b. (HMapCxt HList TaggedToKW a b, HConcat b) => Record a -> HList (HConcatR b) Source #

convert a Record into a list that can supply default arguments for kw

A bit of setup:

>>> :set -XQuasiQuotes
>>> import Data.HList.RecordPuns
>>> let f (_ :: Label "a") a (_ :: Label "b") b () = a `div` b
>>> let a = 2; b = 1; f' = f .*. recToKW [pun| a b |]
>>> kw f' ()
2
>>> kw f' (Label :: Label "a") 10 ()
10

another label type

data K s (c :: *) Source #

Constructors

K 

Instances

Instances details
r ~ (c -> b) => IsKeyFN (K s c -> r) 'True Source #

The purpose of this instance is to be able to use the same Symbol (type-level string) at different types. If they are supposed to be the same, then use Label instead of K

>>> let kA = K :: forall t. K "a" t
>>> let testF (K :: K "a" Int) a1 (K :: K "a" Integer) a2 () = a1-fromIntegral a2

therefore the following options works:

>>> kw (hBuild testF) kA (5 :: Int) kA (3 :: Integer) ()
2
>>> kw (hBuild testF) (K :: K "a" Integer) 3 (K :: K "a" Int) 5 ()
2

But you cannot leave off all Int or Integer annotations.

Instance details

Defined in Data.HList.Keyword

types for user error

demo

setup data types

>>> :set -XDataKinds -XFlexibleInstances -XMultiParamTypeClasses
>>> :set -XScopedTypeVariables -XOverlappingInstances -XTypeFamilies
>>> :set -fcontext-stack=100

We will be using an example inspired by a graphics toolkit -- the area which really benefits from keyword arguments. We first define our labels and useful datatypes

>>> data Color = Color
>>> data Size  = Size
>>> data Origin  = Origin
>>> data RaisedBorder = RaisedBorder

The number of arguments each keyword must be specified by an IsKeyFN instance.

>>> instance IsKeyFN (Color->a->b)  True
>>> instance IsKeyFN (Size->a->b)   True
>>> instance (a ~ (Int,Int)) => IsKeyFN (Origin->a->b) True
>>> instance IsKeyFN (RaisedBorder->a->b) True

Note that if a keyword is always followed by a certain type, that can be specified above using an instance like the one for Origin.

>>> data CommonColor = Red | Green | Blue deriving Show
>>> data RGBColor = RGBColor Int Int Int deriving Show

and two functions:

>>> :{
let make_square Size n Origin (x0,y0) Color (color::CommonColor) =
       unwords ["Square:", show (n :: Int), "at", show (x0,y0), show color] ++ "\n"
:}
>>> :{
let make_rect Size (nx,ny) Origin (x0,y0) Color (color::RGBColor)
        RaisedBorder border =
       unwords ["Rectangle:", show (nx,ny), "at", show (x0,y0),
            show color, if border then "raised border" else ""] ++ "\n"
:}

We are not interested in really drawing squares and rectangles here. Therefore, make_square and make_rect return a String, which we can regard as a `command' to be passed to a low-level graphics library. The functions make_square and make_rect are genuine functions and can be used as such. They are not keyword argument functions, yet, but they set the stage. These functions can be considered an interface for the keyword argument functions. We should note that the functions are polymorphic: for example, Size can be any showable. We must also emphasize the re-use of the labels: The Color of a square is the value of the enumerated type CommonColor. OTH, the color of the rectangle is given as an RGB triple. The sizes of the square and of the rectangle are specified differently too, the same label notwithstanding.

Once the user wrote the functions such as make_square and make_rect, he can _automatically_ convert them to their keyword alternatives. This transformation is done by a function kw. The user should pass the positional-argument function (labeled as above), and an HList of default values for some of the labels. The latter may be HNil if all keyword arguments are required.

The first example (no defaults)

>>> kw (make_square .*. HNil) Size (1::Int) Origin (0,10) Color Red   :: String
"Square: 1 at (0,10) Red\n"

we can permute the arguments at wish

>>> kw (make_square .*. HNil) Color Red Size (1::Int) Origin (0,10)   :: String
"Square: 1 at (0,10) Red\n"

we can also assign a name to a keyword function, or partially apply it:

>>> :{
case kw (make_square .*. HNil) Color Red of
   f -> "here: " ++ f Origin (0,10) Size (1::Int)
:}
"here: Square: 1 at (0,10) Red\n"

Note that it is necessary to use a monomorphic pattern binding here (lambda or case). One way to get around this is to pass f instead of kw f around:

>>> :{
 let f = hEnd $ hBuild make_square Color Red
 in "here: " ++ kw f Origin (0,10) Size (1::Int)
:}
"here: Square: 1 at (0,10) Red\n"

The following is a more interesting example, with the defaults:

>>> :{
let addDef f = f .*. Origin .*. (0,10) .*.
            RaisedBorder .*. True .*.
            HNil
   in kw (addDef make_square) Size (1::Int) Color Red ++
      kw (addDef make_rect)   Color (RGBColor 0 10 255)
                              Size (1.0::Float, 2.0::Float)
:}
"Square: 1 at (0,10) Red\nRectangle: (1.0,2.0) at (0,10) RGBColor 0 10 255 raised border\n"

The argument RaisedBorder is not given, and so the default value is used. Of course, we can override the default:

>>> :{
let addDef f =  f .*. Origin .*. (0,10) .*.
                   RaisedBorder .*. True .*.
                   HNil
in case kw (addDef make_square) Color of
    sq -> case kw (addDef make_rect)  of
     re ->
        sq Red Size (1::Int) ++
        re Color (RGBColor 0 10 255)
            RaisedBorder False
            Size (1.0::Float, 2.0::Float)
:}
"Square: 1 at (0,10) Red\nRectangle: (1.0,2.0) at (0,10) RGBColor 0 10 255 \n"

We have reshuffled a few arguments, just for fun. As you can see, the function `kw make_rect defaults' is polyvariadic indeed. We chose to partially apply Color to the function `kw make_square defaults' -- so that the function sq is positional in its first argument, and keyword in the rest.

If we omit a required argument, we get a type error:

] testse1 = let f x = kw make_square HNil Color Red x
]         in "here: " ++ f Origin (0,10)

  Couldn't match `ErrReqdArgNotFound Size' against `[Char]'
      Expected type: ErrReqdArgNotFound Size
      Inferred type: [Char] ...

The error message seems reasonably clear. Likewise we get an error message if we pass to a keyword function an argument it does not expect:

] testse2 = let f x = kw make_square HNil Color Red x
]         in "here: " ++ f Origin (0,10) Size (1::Int)
]                       RaisedBorder False

  No instances for (Fail (ErrUnexpectedKW RaisedBorder),
            KWApply [Char] (HCons RaisedBorder (:*: Bool HNil)) [Char])
      arising from use of `f' at ...
    In the second argument of `(++)', namely
  `f Origin (0,10) Size (1 :: Int) RaisedBorder False'

The function kw receives the appropriately labeled function (such as make_square) and the HList with default values. The function kw is polymorphic; the overloading is resolved based on the type of the user function *and* on the type of its continuation. The continuation indicates if a keyword argument is forthcoming, or not. In the latter case, kw checks to see if the supplied defaults can provide the values of the still missing arguments. We see therefore that a function type is more than it may appear -- the type of a function is truly a heterogeneous, type level collection! The function kw traverses that collection, thus performing a limited form of reflection on Haskell functions.

Implementation details

One of the key tools of the implementation is kwapply, which applies a function to a polymorphic collection of that function's arguments. The order of the arguments in the collection is irrelevant. The contraption kwapply can handle polymorphic functions with arbitrary number of labeled arguments.

For example, if we define

f1 Size n = show n
f2 Size n Color m = unwords ["size:", show n, "color:", show m]
f3 Origin x Color m Size n =
    unwords ["origin:", show x, "size:", show n, "color:",show m]

then we can run

katest1  = kwapply f1 (Size .*. () .*. HNil)
katest11 = kwapply f1 (Size .*. "Large" .*. HNil)

katest2  = kwapply f2 (Size .*. (1::Int) .*. Color .*. Red .*. HNil)
katest21 = kwapply f2 (Color .*. Red .*. Size .*. (1::Int) .*.  HNil)

katest3  = kwapply f3 (Size .*. (1::Int) .*. Origin .*. (2.0::Float) .*.
                 Color .*. Red .*. HNil)

class KWApply f arg_values r where Source #

Methods

kwapply :: f -> HList arg_values -> r Source #

Instances

Instances details
r ~ r' => KWApply r ('[] :: [Type]) r' Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwapply :: r -> HList '[] -> r' Source #

(HEq kw kw' flag, KWApply' flag (kw -> a -> f') (kw' ': (a' ': tail)) r) => KWApply (kw -> a -> f') (kw' ': (a' ': tail)) r Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwapply :: (kw -> a -> f') -> HList (kw' ': (a' ': tail)) -> r Source #

class KWApply' flag f arg_values r where Source #

Methods

kwapply' :: Proxy flag -> f -> HList arg_values -> r Source #

Instances

Instances details
(HAppendListR tail '[kw, v] ~ l', HAppendList tail '[kw, v], KWApply f l' r) => KWApply' 'False f (kw ': (v ': tail)) r Source #

Rotate the arg list ...

Instance details

Defined in Data.HList.Keyword

Methods

kwapply' :: Proxy 'False -> f -> HList (kw ': (v ': tail)) -> r Source #

(v' ~ v, KWApply f' tail r) => KWApply' 'True (kw -> v -> f') (kw ': (v' ': tail)) r Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwapply' :: Proxy 'True -> (kw -> v -> f') -> HList (kw ': (v' ': tail)) -> r Source #

newtype Arg arg_types arg_values Source #

The datatype Arg below is to maintain the state of keyword accumulation: which keywords we need, and which keyword and values we have already got. arg_types is the phantom HList of keywords that are yet to be satisfied. arg_values is the HList (kw .*. kw_value .*. etc) of already found keywords and their values.

Constructors

Arg (HList arg_values) 

Instances

Instances details
KWMerge arg_needed arg_values arg_def f r => KW' 'False f (Arg arg_needed arg_values) arg_def r Source #

If the continuation r does not promise any more keyword arguments, apply the defaults

Instance details

Defined in Data.HList.Keyword

Methods

kw' :: Proxy 'False -> f -> Arg arg_needed arg_values -> HList arg_def -> r Source #

Show (HList vals) => Show (Arg tys vals) Source # 
Instance details

Defined in Data.HList.Keyword

Methods

showsPrec :: Int -> Arg tys vals -> ShowS #

show :: Arg tys vals -> String #

showList :: [Arg tys vals] -> ShowS #

(HDelete kw arg_types arg_types', KW f (Arg arg_types' (kw ': (a ': arg_values))) arg_def r) => KWAcc (Arg arg_types arg_values) kw a f arg_def r Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwaccum :: Arg arg_types arg_values -> kw -> a -> f -> HList arg_def -> r Source #

producing lists from a function's arguments

reflect_fk :: ReflectFK fn kws => fn -> Arg kws '[] Source #

that reflects on a user-supplied function. It converts the *type* of a user function to a collection of keywords required by that function. This and the previous contraptions may be used to define an extended version of some user function that takes more arguments -- without the need to enumerate all arguments of the original function. We thus infringe on the area of object and module systems.

The rest of the implementation is just to convert `kw fn defaults' into the application of kwapply.

Another key contraption is

class ReflectFK f (kws :: [*]) Source #

Reflection on a function: Given a function, return the type list of its keywords

>>> :t reflect_fk (undefined::Size->Int->Color->CommonColor->String)
reflect_fk (undefined::Size->Int->Color->CommonColor->String)
  :: Arg '[Size, Color] '[]
>>> :t reflect_fk (undefined::Size->Int->()->Int)
reflect_fk (undefined::Size->Int->()->Int) :: Arg '[Size] '[]

Instances

Instances details
(IsKeyFN f flag, ReflectFK' flag f kws) => ReflectFK (f :: Type) kws Source # 
Instance details

Defined in Data.HList.Keyword

class ReflectFK' (flag :: Bool) f kws Source #

Instances

Instances details
('[] :: [k1]) ~ nil => ReflectFK' 'False (f :: k2) (nil :: [k1]) Source # 
Instance details

Defined in Data.HList.Keyword

(kkws ~ (kw ': kws), ReflectFK rest kws) => ReflectFK' 'True (kw -> a -> rest :: Type) (kkws :: [Type]) Source # 
Instance details

Defined in Data.HList.Keyword

collecting arguments

class KW f arg_desc arg_def r where Source #

The main class: collect and apply the keyword arguments

Methods

kwdo :: f -> arg_desc -> HList arg_def -> r Source #

Instances

Instances details
(IsKeyFN r rflag, KW' rflag f arg_desc arg_def r) => KW f arg_desc arg_def r Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwdo :: f -> arg_desc -> HList arg_def -> r Source #

class KW' rflag f arg_desc arg_def r where Source #

Methods

kw' :: Proxy rflag -> f -> arg_desc -> HList arg_def -> r Source #

Instances

Instances details
(KWAcc arg_desc kw a f arg_def r, (kw -> a -> r) ~ kwar) => KW' 'True f arg_desc arg_def kwar Source #

Otherwise, collect the supplied keyword and its value, and recurse for more:

Instance details

Defined in Data.HList.Keyword

Methods

kw' :: Proxy 'True -> f -> arg_desc -> HList arg_def -> kwar Source #

KWMerge arg_needed arg_values arg_def f r => KW' 'False f (Arg arg_needed arg_values) arg_def r Source #

If the continuation r does not promise any more keyword arguments, apply the defaults

Instance details

Defined in Data.HList.Keyword

Methods

kw' :: Proxy 'False -> f -> Arg arg_needed arg_values -> HList arg_def -> r Source #

class KWAcc arg_desc kw a f arg_def r where Source #

Add the real argument to the Arg structure, and continue

Methods

kwaccum :: arg_desc -> kw -> a -> f -> HList arg_def -> r Source #

Instances

Instances details
(HDelete kw arg_types arg_types', KW f (Arg arg_types' (kw ': (a ': arg_values))) arg_def r) => KWAcc (Arg arg_types arg_values) kw a f arg_def r Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwaccum :: Arg arg_types arg_values -> kw -> a -> f -> HList arg_def -> r Source #

merging default with supplied arguments

class KWMerge arg_needed arg_values arg_def f r where Source #

Add the needed arguments from arg_def to arg_values and continue with kwapply.

That is, we try to satisfy the missing arguments from the defaults. It will be a type error if some required arguments are missing

Methods

kwmerge :: Arg arg_needed arg_values -> HList arg_def -> f -> r Source #

Instances

Instances details
KWApply f arg_values r => KWMerge ('[] :: [k]) arg_values arg_def f r Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwmerge :: Arg '[] arg_values -> HList arg_def -> f -> r Source #

KWMerge' kw arg_def atail arg_values arg_def f r => KWMerge (kw ': atail :: [Type]) arg_values arg_def f r Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwmerge :: Arg (kw ': atail) arg_values -> HList arg_def -> f -> r Source #

class KWMerge' kw list atail arg_values arg_def f r where Source #

Methods

kwmerge' :: kw -> HList list -> Arg atail arg_values -> HList arg_def -> f -> r Source #

Instances

Instances details
(Fail (ErrReqdArgNotFound kw), nff ~ ErrReqdArgNotFound kw) => KWMerge' kw ('[] :: [Type]) (atail :: k) arg_values arg_def f nff Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwmerge' :: kw -> HList '[] -> Arg atail arg_values -> HList arg_def -> f -> nff Source #

(HEq kw kw' flag, KWMerge'' flag kw (kw' ': etc) atail arg_values arg_def f r) => KWMerge' kw (kw' ': etc) (atail :: k) arg_values arg_def f r Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwmerge' :: kw -> HList (kw' ': etc) -> Arg atail arg_values -> HList arg_def -> f -> r Source #

class KWMerge'' (flag :: Bool) kw (list :: [*]) atail arg_values arg_def f r where Source #

Methods

kwmerge'' :: Proxy flag -> kw -> HList list -> Arg atail arg_values -> HList arg_def -> f -> r Source #

Instances

Instances details
KWMerge' kw tail atail arg_values arg_def f r => KWMerge'' 'False kw (kw' ': (v' ': tail)) (atail :: k) arg_values arg_def f r Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwmerge'' :: Proxy 'False -> kw -> HList (kw' ': (v' ': tail)) -> Arg atail arg_values -> HList arg_def -> f -> r Source #

KWMerge atail (kw ': (v ': arg_values)) arg_def f r => KWMerge'' 'True kw (kw ': (v ': tail)) (atail :: k) arg_values arg_def f r Source # 
Instance details

Defined in Data.HList.Keyword

Methods

kwmerge'' :: Proxy 'True -> kw -> HList (kw ': (v ': tail)) -> Arg atail arg_values -> HList arg_def -> f -> r Source #

class HDelete e (l :: [k]) (l' :: [k]) Source #

Delete e from l to yield l' The element e must occur in l

Instances

Instances details
(Fail (ErrUnexpectedKW e), r ~ ('[] :: [k2])) => HDelete (e :: k1) ('[] :: [k2]) (r :: [k2]) Source # 
Instance details

Defined in Data.HList.Keyword

(HEq e e' flag, HDelete' flag e (e' ': tail) l') => HDelete (e :: k) (e' ': tail :: [k]) (l' :: [k]) Source # 
Instance details

Defined in Data.HList.Keyword

class HDelete' (flag :: Bool) e l l' Source #

Instances

Instances details
tail' ~ tail => HDelete' 'True (e :: a) (e ': tail :: [a]) (tail' :: [a]) Source # 
Instance details

Defined in Data.HList.Keyword

(HDelete e tail tail', e'tail ~ (e' ': tail')) => HDelete' 'False (e :: k) (e' ': tail :: [a]) (e'tail :: [a]) Source # 
Instance details

Defined in Data.HList.Keyword

original introduction

From oleg-at-okmij.org Fri Aug 13 14:58:35 2004
To: haskell@haskell.org
Subject: Keyword arguments
From: oleg-at-pobox.com
Message-ID: <20040813215834.F1FF3AB7E@Adric.metnet.navy.mil>
Date: Fri, 13 Aug 2004 14:58:34 -0700 (PDT)
Status: OR

We show the Haskell implementation of keyword arguments, which goes well beyond records (e.g., in permitting the re-use of labels). Keyword arguments indeed look just like regular, positional arguments. However, keyword arguments may appear in any order. Furthermore, one may associate defaults with some keywords; the corresponding arguments may then be omitted. It is a type error to omit a required keyword argument. The latter property is in stark contrast with the conventional way of emulating keyword arguments via records. Also in marked contrast with records, keyword labels may be reused throughout the code with no restriction; the same label may be associated with arguments of different types in different functions. Labels of Haskell records may not be re-used. Our solution is essentially equivalent to keyword arguments of DSSSL Scheme or labels of OCaml.

Keyword argument functions are naturally polyvariadic: Haskell does support varargs! Keyword argument functions may be polymorphic. As usual, functions with keyword arguments may be partially applied. On the downside, sometimes one has to specify the type of the return value of the function (if the keyword argument function has no signature -- the latter is the norm, see below) -- provided that the compiler cannot figure the return type out on its own. This is usually only the case when we use keyword functions at the top level (GHCi prompt).

Our solution requires no special extensions to Haskell and works with the existing Haskell compilers; it is tested on GHC 6.0.1. The overlapping instances extension is not necessary (albeit it is convenient).

The gist of our implementation is the realization that the type of a function is a polymorphic collection of its argument types -- a collection that we can traverse. This message thus illustrates a limited form of the reflection on a function.

Our implementation is a trivial extension of the strongly-typed polymorphic open records described in http://homepages.cwi.nl/~ralf/HList/

In fact, the implementation relies on the HList library. To run the code (which this message is), one needs to download the HList library from the above site.

The HList paper discusses the issue of labels in some detail. The paper gives three different representations. One of them needs no overlapping instances and is very portable. In this message, we chose a representation that relies on generic type equality and therefore needs overlapping instances as implemented in GHC. Again, this is merely an outcome of our non-deterministic choice. It should be emphasized that other choices are possible, which do not depend on overlapping instances at all. Please see the HList paper for details.

todo

better instances for Symbol

There isn't a pair (K2 "Origin" (Int, Int)) (K "hi") that behaves just like Origin below. something is possible between constraintkinds. See Fun

instance (a ~ (Int,Int)) => IsKeyFN (Origin->a->b) True
wildcard/catchall

like in R. This would be a special keyword for keyword args that didn't match. They would be put in a HList/Record argument like ...

investigate first-classness of varargs
for whatever reason you can't have f = kw fn blah and then pass more arguments on to fn. This is bad. It used to work (in the ghc6.0 days and probably up to 6.12). Some convenience functions/operators should be added which do the same thing as:
fn `hAppendList` hBuild a b c d e

internal for type signature prettiness

data TaggedToKW Source #

Instances

Instances details
(x ~ Tagged l v, y ~ HList '[Label l, v]) => ApplyAB TaggedToKW x y Source # 
Instance details

Defined in Data.HList.Keyword

Methods

applyAB :: TaggedToKW -> x -> y Source #

Orphan instances

IsKeyFN (Label s -> a -> b) 'True Source #

labels that impose no restriction on the type of the (single) argument which follows

>>> let testF (_ :: Label "a") (a :: Int) () = a+1
>>> kw (hBuild testF) (Label :: Label "a") 5 ()
6
Instance details