ifcxt: 8c260e8de5222b62b0cbbd16f93d1ff8b5854d68

     1: # IfCxt
     2: 
     3: This package introduces the function:
     4: ```
     5: ifCxt :: IfCxt cxt => proxy cxt -> (cxt => a) -> a -> a
     6: ```
     7: This function acts like an `if` statement where the `proxy cxt` parameter is the condition.
     8: If the type checker can satisfy the `cxt` constraint, then the second argument `cxt => a` is returned;
     9: otherwise, the third argument `a` is returned.
    10: 
    11: Before seeing more details about how `ifCxt` is implemented,
    12: let's look at three examples of how to use it.
    13: 
    14: ### Example 1: show every type
    15: 
    16: The `cxtShow` function below is polymorphic over the type `a`.
    17: If `a` is an instance of `Show`, then `cxtShow a` evaluates to `show a`;
    18: but if `a` is not an instance of `Show`, `cxtShow a` evaluates to `<<unshowable>>`.
    19: 
    20: ```
    21: cxtShow :: forall a. IfCxt (Show a) => a -> String
    22: cxtShow a = ifCxt (Proxy::Proxy (Show a))
    23:     (show a)
    24:     "<<unshowable>>"
    25: ```
    26: In ghci:
    27: 
    28: ```
    29: ghci> cxtShow (1 :: Int)
    30: "1"
    31: ```
    32: ```
    33: ghci> cxtShow (id :: a -> a)
    34: "<<unshowable>>"
    35: ```
    36: 
    37: ### Example 2: make your code asymptotically efficient
    38: 
    39: The `nub` function removes duplicate elements from lists.
    40: It can be defined as:
    41: 
    42: ```
    43: nub :: Eq a => [a] -> [a]
    44: nub []     =  []
    45: nub (x:xs) =  x : nub (filter (x/=) xs)
    46: ```
    47: This function takes time O(n^2).
    48: But if we also have an `Ord` constraint, we can define a much more efficient version that takes time O(n log n):
    49: 
    50: ```
    51: nubOrd :: Ord a => [a] -> [a]
    52: nubOrd = go . sort
    53:     where
    54:         go (x1:x2:xs)
    55:             | x1==x2    =      go (x2:xs)
    56:             | otherwise = x1 : go (x2:xs)
    57:         go [x] = [x]
    58:         go []  = []
    59: ```
    60: Now, we can use the `ifCxt` function to define a version of `nub` that will automatically select the most efficient implementation for whatever type we happen to run it on:
    61: 
    62: ```
    63: cxtNub :: forall a. (Eq a, IfCxt (Ord a)) => [a] -> [a]
    64: cxtNub = ifCxt (Proxy::Proxy (Ord a)) nubOrd nub
    65: ```
    66: 
    67: ### Example 3: make your code numerically stable
    68: 
    69: The simplest way to sum a list of numbers is:
    70: ```
    71: sumSimple :: Num a => [a] -> a
    72: sumSimple = foldl' (+) 0
    73: ```
    74: This method has numerical stability issues on floating point representations.
    75: [Kahan summation](https://en.wikipedia.org/wiki/Kahan_summation_algorithm) is a more accurate technique shown below:
    76: ```
    77: sumKahan :: Num a => [a] -> a
    78: sumKahan = snd . foldl' go (0,0)
    79:     where
    80:         go (c,t) i = ((t'-t)-y,t')
    81:             where
    82:                 y = i-c
    83:                 t' = t+y
    84: ```
    85: Because Kahan summation does a lot more work than simple summation, we would prefer not to run it on non-floating point types.
    86: The `sumCxt` function below accomplishes this:
    87: ```
    88: cxtSum :: forall a. (Num a, IfCxt (Floating a)) => [a] -> a
    89: cxtSum = ifCxt (Proxy::Proxy (Floating a)) sumKahan sumSimple
    90: ```
    91: Notice that the `ifCxt` function is conditioning on the `Floating a` constraint,
    92: which isn't actually *used* by the `sumKahan` function.
    93: 
    94: ## How it works
    95: 
    96: The magic of the technique is in the `IfCxt` class:
    97: ```
    98: class IfCxt (cxt :: Constraint) where
    99:     ifCxt :: proxy cxt -> (cxt => a) -> a -> a
   100: ```
   101: (Notice that making a constraint an instance of a class requires the`ConstraintKinds` extension,
   102: and the higher order `(cxt => a)` parameter requires the `RankNTypes` extension.)
   103: 
   104: There is a "global" instance defined as:
   105: ```
   106: instance {-# OVERLAPPABLE #-} IfCxt cxt where ifCxt _ t f = f
   107: ```
   108: What this says is that if no more specific instance is available, then the "global" `ifCxt` function will be used, which always returns the `f` (false) parameter.
   109: 
   110: Then for every instance of every other class, we need to define an overlapping `IfCxt` instance that always returns the `t` (true) parameter.
   111: For example, for `Show Int`, we define:
   112: ```
   113: instance {-# OVERLAPS #-} IfCxt (Show Int) where ifCxt _ t f = t
   114: ```
   115: 
   116: This is a lot of boilerplate, so the template haskell function `mkIfCxtInstances` can be used to define these instances automatically.
   117: Unfortunately, due to a [bug in template haskell](https://ghc.haskell.org/trac/ghc/ticket/9699) we cannot enumerate all the classes currently in scope.
   118: So you must manually call `mkIfCxtInstances` on each class you want `ifCxt` to work with.

Generated by git2html.