packages feed

jl-0.1.0: src/JL/Functions.hs

{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE LambdaCase #-}

-- |

module JL.Functions (context, scope, functions) where

import           Control.Arrow
import           Data.Aeson (Value)
import           Data.Function
import qualified Data.HashMap.Strict as HM
import           Data.List
import           Data.Map.Strict (Map)
import qualified Data.Map.Strict as M
import           Data.Maybe
import           Data.Ord
import           Data.Scientific
import           Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Vector as V
import           JL.Interpreter
import           JL.Serializer
import           JL.Types

--------------------------------------------------------------------------------
-- Lists

-- | The typing context.
context :: Map Variable Type
context = M.fromList (map (definitionName &&& definitionType) (concatMap snd functions))

-- | Bindings available in scope.
scope :: Map Variable Core
scope = M.fromList (map (definitionName &&&definitionCore) (concatMap snd functions))

-- | All functions.
functions :: [(Text, [Definition])]
functions =
  [ ("Record access", [getf, setf, modifyf, keysf, elemsf])
  , ( "Sequences"
    , [ mapf
      , filterf
      , takeWhilef
      , empty
      , len
      , rev
      , dropf
      , elemf
      , concatf
      , zipw
      , takef
      , foldf
      , dropWhilef
      , anyf
      , allf
      , nubf
      , sortf
      , appendf
      , sumf
      , productf
      , minimumf
      , maximumf
      ])
  , ("Strings", [wordsf, unwordsf, linesf, unlinesf])
  , ( "Predicate operators"
    , [predicateOperator "/=" (/=), predicateOperator "=" (==)])
  , ( "Boolean operators"
    , [boolOperator "&&" (&&), boolOperator "||" (||), boolFun "not" not])
  , ( "Numeric operators"
    , [ numericPredicateOperator ">" (>)
      , numericPredicateOperator "<" (<)
      , numericPredicateOperator ">=" (>=)
      , numericPredicateOperator "<=" (<=)
      , arithmeticOperator "*" (*)
      , arithmeticOperator "+" (+)
      , arithmeticOperator "-" (-)
      , arithmeticOperator "/" (/)
      , arithmeticOperator "min" min
      , arithmeticOperator "max" max
      , arithmeticFun "abs" abs
      ])
  , ("Function combinators", [idf, compose, flipf])
  ]

--------------------------------------------------------------------------------
-- Functions

keysf :: Definition
keysf =
  Definition
  { definitionDoc = "Get all keys of the object"
  , definitionName = Variable "keys"
  , definitionCore =
      (EvalCore
         (\obj ->
            case obj of
              RecordCore o ->
                ArrayCore
                  (V.fromList (map (ConstantCore . StringConstant) (HM.keys o)))
              _ -> error "keys function expected an object"))
  , definitionType = FunctionType JSONType JSONType
  }

elemsf :: Definition
elemsf =
  Definition
  { definitionDoc = "Get all elements of the object"
  , definitionName = Variable "elems"
  , definitionCore =
      (EvalCore
         (\obj ->
            case obj of
              RecordCore o ->
                ArrayCore
                  (V.fromList (HM.elems o))
              _ -> error "elems function expected an object"))
  , definitionType = FunctionType JSONType JSONType
  }

modifyf :: Definition
modifyf =
  Definition
  { definitionDoc = "Modify the object at k with function f"
  , definitionName = Variable "modify"
  , definitionCore =
      (EvalCore
         (\key ->
            EvalCore
              (\f ->
                 EvalCore
                   (\obj ->
                      case (key, obj) of
                        (ConstantCore (StringConstant k), (RecordCore o)) ->
                          (RecordCore
                             (HM.adjust (\v -> eval (ApplicationCore f v)) k o))
                        _ -> error "type error for args to modify"))))
  , definitionType =
      FunctionType
        JSONType
        (FunctionType
           (FunctionType JSONType JSONType)
           (FunctionType JSONType JSONType))
  }

getf :: Definition
getf =
  Definition
  { definitionDoc = "Get the value at k from the object"
  , definitionName = Variable "get"
  , definitionCore =
      (EvalCore
         (\key ->
            EvalCore
              (\obj ->
                 case (key, obj) of
                   (ConstantCore (StringConstant k), RecordCore o) ->
                     (case HM.lookup k o of
                        Nothing -> error ("missing key " <> show k)
                        Just v -> v)
                   (ConstantCore (NumberConstant i), (ArrayCore v)) ->
                     (case v V.!? (round i) of
                        Nothing -> error ("missing array index " <> show i)
                        Just v' -> v')
                   _ -> error "type error for get arguments")))
  , definitionType = FunctionType JSONType (FunctionType JSONType JSONType)
  }

setf :: Definition
setf =
  Definition
  { definitionDoc = "Set the value k to v in object"
  , definitionName = Variable "set"
  , definitionCore =
      (EvalCore
         (\key ->
            EvalCore
              (\val ->
                 EvalCore
                   (\obj ->
                      case (key, val, obj) of
                        (ConstantCore (StringConstant k), v, RecordCore o) ->
                          (RecordCore (HM.insert k v o))
                        _ -> error "type error in arguments to: set"))))
  , definitionType =
      FunctionType
        JSONType
        (FunctionType JSONType (FunctionType JSONType JSONType))
  }

idf :: Definition
idf =
  Definition
  { definitionDoc = "Identity function, returns its input unchanged"
  , definitionName = Variable "id"
  , definitionCore = (EvalCore (\x -> x))
  , definitionType = FunctionType JSONType JSONType
  }

flipf :: Definition
flipf =
  Definition
  { definitionDoc = "Flips the argument order of a function of two or more arguments"
  , definitionName = Variable "flip"
  , definitionCore =
      (EvalCore
         (\f ->
            EvalCore
              (\x ->
                 EvalCore (\y -> eval (ApplicationCore (ApplicationCore f y) x)))))
  , definitionType =
      FunctionType
        (FunctionType JSONType (FunctionType JSONType JSONType))
        (FunctionType JSONType (FunctionType JSONType JSONType))
  }

compose :: Definition
compose =
  Definition
  { definitionDoc = "Compose two functions"
  , definitionName = Variable "compose"
  , definitionCore =
      EvalCore
        (\f ->
           EvalCore
             (\g ->
                EvalCore (\x -> eval (ApplicationCore g (ApplicationCore f x)))))
  , definitionType =
      FunctionType
        (FunctionType JSONType JSONType)
        (FunctionType
           (FunctionType JSONType JSONType)
           (FunctionType JSONType JSONType))
  }

foldf :: Definition
foldf =
  Definition
  { definitionDoc = "Fold over a structure with a state."
  , definitionName = Variable "fold"
  , definitionCore =
      EvalCore
        (\cons ->
           EvalCore
             (\nil ->
                EvalCore
                  (\xs ->
                     case xs of
                       ArrayCore xs' ->
                         (V.foldl
                            (\acc x ->
                               eval
                                 (ApplicationCore (ApplicationCore cons acc) x))
                            nil
                            xs')
                       ConstantCore (StringConstant xs') ->
                         (T.foldl
                            (\acc x ->
                               eval
                                 (ApplicationCore
                                    (ApplicationCore cons acc)
                                    (ConstantCore
                                       (StringConstant (T.singleton x)))))
                            nil
                            xs')
                       _ -> error "can only fold sequences")))
  , definitionType =
      (FunctionType
         ((FunctionType JSONType (FunctionType JSONType JSONType)))
         (FunctionType JSONType (FunctionType JSONType JSONType)))
  }

zipw :: Definition
zipw =
  Definition
  { definitionDoc = "Zip two lists calling with each element to f x y"
  , definitionName = Variable "zipWith"
  , definitionCore =
      EvalCore
        (\f ->
           EvalCore
             (\xs ->
                EvalCore
                  (\ys ->
                     case (xs, ys) of
                       (ArrayCore xs', ArrayCore ys') ->
                         (ArrayCore
                            (V.zipWith
                               (\x y ->
                                  eval (ApplicationCore (ApplicationCore f x) y))
                               xs'
                               ys'))
                       _ -> error "can only zip two arrays")))
  , definitionType =
      FunctionType
        (FunctionType JSONType (FunctionType JSONType JSONType))
        (FunctionType JSONType (FunctionType JSONType JSONType))
  }

elemf :: Definition
elemf =
  Definition
  { definitionDoc = "Is x an element of y?"
  , definitionName = Variable "elem"
  , definitionCore =
      EvalCore
        (\n ->
           EvalCore
             (\xs ->
                case xs of
                  (ArrayCore xs') ->
                    (ConstantCore
                       (BoolConstant
                          (V.elem (coreToValue n) (fmap coreToValue xs'))))
                  (ConstantCore (StringConstant xs')) ->
                    (ConstantCore
                       (BoolConstant
                          (isJust
                             (T.findIndex
                                (\cc ->
                                   case n of
                                     ConstantCore (StringConstant c) ->
                                       c == T.singleton cc
                                     _ -> False)
                                xs'))))
                  _ ->
                    error
                      "can only check elements from sequences"))
  , definitionType = FunctionType JSONType (FunctionType JSONType JSONType)
  }

takef :: Definition
takef =
  Definition
  { definitionDoc = "Take n items from sequence"
  , definitionName = Variable "take"
  , definitionCore =
      EvalCore
        (\n ->
           EvalCore
             (\xs ->
                case (n, xs) of
                  (ConstantCore (NumberConstant n'), ArrayCore xs') ->
                    (ArrayCore (V.take (round n') xs'))
                  (ConstantCore (NumberConstant n'), ConstantCore (StringConstant xs')) ->
                    (ConstantCore (StringConstant (T.take (round n') xs')))
                  _ -> error "can only take from sequences"))
  , definitionType = FunctionType JSONType (FunctionType JSONType JSONType)
  }

dropf :: Definition
dropf =
  Definition
  { definitionDoc = "Drop n items from the sequence"
  , definitionName = Variable "drop"
  , definitionCore =
      EvalCore
        (\n ->
           EvalCore
             (\xs ->
                case (n, xs) of
                  (ConstantCore (NumberConstant n'), ConstantCore (StringConstant xs')) ->
                    (ConstantCore (StringConstant (T.drop (round n') xs')))
                  (ConstantCore (NumberConstant n'), ArrayCore xs') ->
                    (ArrayCore (V.drop (round n') xs'))
                  _ -> error "can only drop from sequences"))
  , definitionType = FunctionType JSONType (FunctionType JSONType JSONType)
  }

concatf :: Definition
concatf =
  Definition
  { definitionDoc = "Concatenate a list of sequences into one sequence"
  , definitionName = Variable "concat"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') ->
                (ArrayCore (V.concat (map coreToArray (V.toList xs'))))
              _ -> error "can only concat arrays"))
  , definitionType = FunctionType JSONType JSONType
  }

rev :: Definition
rev =
  Definition
  { definitionDoc = "Reverse a sequence"
  , definitionName = Variable "reverse"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') -> (ArrayCore (V.reverse xs'))
              (ConstantCore (StringConstant xs')) ->
                (ConstantCore (StringConstant (T.reverse xs')))
              _ -> error "can only reverse a sequence"))
  , definitionType = FunctionType JSONType JSONType
  }

appendf :: Definition
appendf =
  Definition
  { definitionDoc = "Append the members of the second sequence to the first sequence"
  , definitionName = Variable "append"
  , definitionCore =
      (EvalCore
         (\xs ->
            (EvalCore
               (\ys ->
                  case (xs, ys) of
                    (ArrayCore xs', ArrayCore ys') -> (ArrayCore (xs' <> ys'))
                    (ConstantCore (StringConstant xs'), ConstantCore (StringConstant ys')) ->
                      (ConstantCore (StringConstant (xs' <> ys')))
                    _ -> error "can only append two sequences of the same type"))))
  , definitionType = FunctionType JSONType (FunctionType JSONType JSONType)
  }

nubf :: Definition
nubf =
  Definition
  { definitionDoc = "Return the sequence with no duplicates; the nub of it"
  , definitionName = Variable "nub"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') ->
                (ArrayCore
                   (V.fromList (nubBy (on (==) coreToValue) (V.toList xs'))))
              (ConstantCore (StringConstant xs')) ->
                (ConstantCore
                   (StringConstant
                      (T.pack (nub (T.unpack xs')))))
              _ -> error "can only nub a sequence"))
  , definitionType = FunctionType JSONType JSONType
  }

linesf :: Definition
linesf =
  Definition
  { definitionDoc = "Split the string into a list of lines"
  , definitionName = Variable "lines"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ConstantCore (StringConstant xs')) ->
                (ArrayCore
                   (V.map
                      (ConstantCore . StringConstant)
                      ((V.fromList (T.lines xs')))))
              _ -> error "can only lines a string"))
  , definitionType = FunctionType JSONType JSONType
  }

wordsf :: Definition
wordsf =
  Definition
  { definitionDoc = "Split the string into a list of words"
  , definitionName = Variable "words"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ConstantCore (StringConstant xs')) ->
                (ArrayCore
                   (V.map
                      (ConstantCore . StringConstant)
                      ((V.fromList (T.words xs')))))
              _ -> error "can only words a string"))
  , definitionType = FunctionType JSONType JSONType
  }

unwordsf :: Definition
unwordsf =
  Definition
  { definitionDoc = "Join the list of strings into a string separated by spaces"
  , definitionName = Variable "unwords"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') ->
                (ConstantCore (StringConstant (T.unwords (V.toList (fmap coreToString xs')))))
              _ -> error "can only unwords a string"))
  , definitionType = FunctionType JSONType JSONType
  }

unlinesf :: Definition
unlinesf =
  Definition
  { definitionDoc = "Join the list of strings into a string separated by lines and terminated by a new line"
  , definitionName = Variable "unlines"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') ->
                (ConstantCore (StringConstant (T.unlines (V.toList (fmap coreToString xs')))))
              _ -> error "can only unlines a string"))
  , definitionType = FunctionType JSONType JSONType
  }

sortf :: Definition
sortf =
  Definition
  { definitionDoc = "Return the sequence sorted"
  , definitionName = Variable "sort"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') ->
                (ArrayCore
                   (V.fromList (sortBy (comparing coreToCompare) (V.toList xs'))))
              (ConstantCore (StringConstant xs')) ->
                (ConstantCore
                   (StringConstant
                      (T.pack (sort (T.unpack xs')))))
              _ -> error "can only sort a sequence"))
  , definitionType = FunctionType JSONType JSONType
  }

len :: Definition
len =
  Definition
  { definitionDoc = "Get the length of a sequence"
  , definitionName = Variable "length"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') ->
                (ConstantCore (NumberConstant (fromIntegral (V.length xs'))))
              (ConstantCore (StringConstant xs')) ->
                (ConstantCore (NumberConstant (fromIntegral (T.length xs'))))
              _ -> error "can only take length of sequences"))
  , definitionType = FunctionType JSONType JSONType
  }

sumf :: Definition
sumf =
  Definition
  { definitionDoc = "Get the sum of a sequence"
  , definitionName = Variable "sum"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') ->
                (ConstantCore (NumberConstant (V.sum (fmap coreToNumber xs'))))
              _ -> error "can only take sum of arrays"))
  , definitionType = FunctionType JSONType JSONType
  }

productf :: Definition
productf =
  Definition
  { definitionDoc = "Get the product of a sequence"
  , definitionName = Variable "product"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') ->
                (ConstantCore (NumberConstant (V.product (fmap coreToNumber xs'))))
              _ -> error "can only take product of arrays"))
  , definitionType = FunctionType JSONType JSONType
  }

maximumf :: Definition
maximumf =
  Definition
  { definitionDoc = "Get the maximum of a sequence"
  , definitionName = Variable "maximum"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') ->
                (ConstantCore (NumberConstant (V.maximum (fmap coreToNumber xs'))))
              _ -> error "can only take maximum of arrays"))
  , definitionType = FunctionType JSONType JSONType
  }

minimumf :: Definition
minimumf =
  Definition
  { definitionDoc = "Get the minimum of a sequence"
  , definitionName = Variable "minimum"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') ->
                (ConstantCore (NumberConstant (V.minimum (fmap coreToNumber xs'))))
              _ -> error "can only take minimum of arrays"))
  , definitionType = FunctionType JSONType JSONType
  }

empty :: Definition
empty =
  Definition
  { definitionDoc = "Is a sequence empty?"
  , definitionName = Variable "empty"
  , definitionCore =
      (EvalCore
         (\xs ->
            case xs of
              (ArrayCore xs') -> (ConstantCore (BoolConstant (V.null xs')))
              (ConstantCore (StringConstant xs')) ->
                (ConstantCore (BoolConstant (T.null xs')))
              _ -> error "can only check if sequences are empty"))
  , definitionType = FunctionType JSONType JSONType
  }

anyf :: Definition
anyf =
  Definition
  { definitionDoc = "Does p return true for any of the elements?"
  , definitionName = Variable "any"
  , definitionCore =
      EvalCore
        (\f ->
           EvalCore
             (\xs ->
                case xs of
                  (ArrayCore xs') ->
                    (ConstantCore
                       (BoolConstant
                          (V.any
                             (\x ->
                                case eval (ApplicationCore f x) of
                                  ConstantCore (BoolConstant b) -> b
                                  _ -> True)
                             xs')))
                  (ConstantCore (StringConstant xs')) ->
                    (ConstantCore
                       (BoolConstant
                          (T.any
                             (\x ->
                                case eval
                                       (ApplicationCore
                                          f
                                          (ConstantCore
                                             (StringConstant (T.singleton x)))) of
                                  ConstantCore (BoolConstant b) -> b
                                  _ -> True)
                             xs')))
                  _ -> error "can only any over sequences"))
  , definitionType =
      FunctionType
        (FunctionType JSONType JSONType)
        (FunctionType JSONType JSONType)
  }

allf :: Definition
allf =
  Definition
  { definitionDoc = "Does p return true for all of the elements?"
  , definitionName = Variable "all"
  , definitionCore =
      EvalCore
        (\f ->
           EvalCore
             (\xs ->
                case xs of
                  (ArrayCore xs') ->
                    (ConstantCore
                       (BoolConstant
                          (V.all
                             (\x ->
                                case eval (ApplicationCore f x) of
                                  ConstantCore (BoolConstant b) -> b
                                  _ -> True)
                             xs')))
                  (ConstantCore (StringConstant xs')) ->
                    (ConstantCore
                       (BoolConstant
                          (T.all
                             (\x ->
                                case eval
                                       (ApplicationCore
                                          f
                                          (ConstantCore
                                             (StringConstant (T.singleton x)))) of
                                  ConstantCore (BoolConstant b) -> b
                                  _ -> True)
                             xs')))
                  _ -> error "can only all over sequences"))
  , definitionType =
      FunctionType
        (FunctionType JSONType JSONType)
        (FunctionType JSONType JSONType)
  }

dropWhilef :: Definition
dropWhilef =
  Definition
  { definitionDoc = "Drop elements from a sequence while a predicate is true"
  , definitionName = Variable "dropWhile"
  , definitionCore =
      EvalCore
        (\f ->
           EvalCore
             (\xs ->
                case xs of
                  (ArrayCore xs') ->
                    (ArrayCore
                       (V.dropWhile
                          (\x ->
                             case eval (ApplicationCore f x) of
                               ConstantCore (BoolConstant b) -> b
                               _ -> True)
                          xs'))
                  (ConstantCore (StringConstant xs')) ->
                    (ConstantCore
                       (StringConstant
                          (T.dropWhile
                             (\x ->
                                case eval
                                       (ApplicationCore
                                          f
                                          (ConstantCore
                                             (StringConstant (T.singleton x)))) of
                                  ConstantCore (BoolConstant b) -> b
                                  _ -> True)
                             xs')))
                  _ -> error "can only dropWhile over sequences"))
  , definitionType =
      FunctionType
        (FunctionType JSONType JSONType)
        (FunctionType JSONType JSONType)
  }

takeWhilef :: Definition
takeWhilef =
  Definition
  { definitionDoc =
      "Take elements from a sequence while given predicate is true"
  , definitionName = Variable "takeWhile"
  , definitionCore =
      (EvalCore
         (\f ->
            EvalCore
              (\xs ->
                 case xs of
                   (ConstantCore (StringConstant xs')) ->
                     (ConstantCore
                        (StringConstant
                           (T.takeWhile
                              (\x ->
                                 case eval
                                        (ApplicationCore
                                           f
                                           (ConstantCore
                                              (StringConstant (T.singleton x)))) of
                                   ConstantCore (BoolConstant b) -> b
                                   _ -> True)
                              xs')))
                   (ArrayCore xs') ->
                     (ArrayCore
                        (V.takeWhile
                           (\x ->
                              case eval (ApplicationCore f x) of
                                ConstantCore (BoolConstant b) -> b
                                _ -> True)
                           xs'))
                   _ -> error "can only takeWhile over sequences")))
  , definitionType =
      FunctionType
        (FunctionType JSONType JSONType)
        (FunctionType JSONType JSONType)
  }

filterf :: Definition
filterf =
  Definition
  { definitionDoc = "Keep only items from the sequence for which p returns true"
  , definitionName = Variable "filter"
  , definitionCore =
      EvalCore
        (\f ->
           EvalCore
             (\xs ->
                case xs of
                  (ConstantCore (StringConstant xs')) ->
                    (ConstantCore
                       (StringConstant
                          (T.filter
                             (\x ->
                                case eval
                                       (ApplicationCore
                                          f
                                          (ConstantCore
                                             (StringConstant (T.singleton x)))) of
                                  ConstantCore (BoolConstant b) -> b
                                  _ -> True)
                             xs')))
                  (ArrayCore xs') ->
                    (ArrayCore
                       (V.filter
                          (\x ->
                             case eval (ApplicationCore f x) of
                               ConstantCore (BoolConstant b) -> b
                               _ -> True)
                          xs'))
                  _ -> error "can only filter over sequences"))
  , definitionType =
      FunctionType
        (FunctionType JSONType JSONType)
        (FunctionType JSONType JSONType)
  }

mapf :: Definition
mapf =
  Definition
  { definitionDoc = "Apply a function to every element in the sequence"
  , definitionName = Variable "map"
  , definitionCore =
      EvalCore
        (\f ->
           EvalCore
             (\xs ->
                case xs of
                  (ConstantCore (StringConstant xs')) ->
                    (ConstantCore
                       (StringConstant
                          (T.concatMap
                             (\x ->
                                case eval
                                       (ApplicationCore
                                          f
                                          (ConstantCore
                                             (StringConstant (T.singleton x)))) of
                                  ConstantCore (StringConstant b) -> b
                                  _ -> error "map over a string must return strings")
                             xs')))
                  (ArrayCore xs') ->
                    (ArrayCore (fmap (\x -> (eval (ApplicationCore f (x)))) xs'))
                  _ -> error "can only map over sequences"))
  , definitionType =
      FunctionType
        (FunctionType JSONType JSONType)
        (FunctionType JSONType JSONType)
  }

--------------------------------------------------------------------------------
-- Function builders

arithmeticOperator :: Text -> (Scientific -> Scientific -> Scientific) -> Definition
arithmeticOperator name f =
  Definition
  { definitionName = Variable name
  , definitionCore =
      EvalCore
        (\x ->
           EvalCore
             (\y ->
                case (x, y) of
                  (ConstantCore (NumberConstant a), ConstantCore (NumberConstant b)) ->
                    ConstantCore (NumberConstant (f a b))
                  _ -> error ("type error for arguments to " <> show name)))
  , definitionType = JSONType .-> JSONType .-> JSONType
  , definitionDoc = "a " <> name <> " b"
  }

boolOperator :: Text -> (Bool -> Bool -> Bool) -> Definition
boolOperator name f =
  Definition
  { definitionName = Variable name
  , definitionCore =
      EvalCore
        (\x ->
           EvalCore
             (\y ->
                case (x, y) of
                  (ConstantCore (BoolConstant a), ConstantCore (BoolConstant b)) ->
                    ConstantCore (BoolConstant (f a b))
                  _ -> error ("type error for arguments to " <> show name)))
  , definitionType = JSONType .-> JSONType .-> JSONType
  , definitionDoc = "a " <> name <> " b"
  }

arithmeticFun :: Text -> (Scientific -> Scientific) -> Definition
arithmeticFun name f =
  Definition
  { definitionName = Variable name
  , definitionCore =
      EvalCore
        (\x ->
           case (x) of
             (ConstantCore (NumberConstant a)) ->
               ConstantCore (NumberConstant (f a))
             _ -> error ("type error for arguments to " <> show name))
  , definitionType = JSONType .-> JSONType
  , definitionDoc = name <> " b"
  }

boolFun :: Text -> (Bool -> Bool) -> Definition
boolFun name f =
  Definition
  { definitionName = Variable name
  , definitionCore =
      EvalCore
        (\x ->
           case (x) of
             (ConstantCore (BoolConstant a)) ->
               ConstantCore (BoolConstant (f a))
             _ -> error ("type error for arguments to " <> show name))
  , definitionType = JSONType .-> JSONType
  , definitionDoc = name <> " b"
  }

predicateOperator :: Text -> (Value -> Value -> Bool) -> Definition
predicateOperator name f =
  Definition
  { definitionName = Variable name
  , definitionCore =
      EvalCore
        (\x ->
           EvalCore
             (\y ->
                ConstantCore (BoolConstant (f (coreToValue x) (coreToValue y)))))
  , definitionType = JSONType .-> JSONType .-> JSONType
  , definitionDoc = "a " <> name <> " b"
  }

numericPredicateOperator :: Text -> (Scientific -> Scientific -> Bool) -> Definition
numericPredicateOperator name f =
  Definition
  { definitionName = Variable name
  , definitionCore =
      EvalCore
        (\x ->
           EvalCore
             (\y ->
                case (x, y) of
                  (ConstantCore (NumberConstant a), ConstantCore (NumberConstant b)) ->
                    ConstantCore (BoolConstant (f a b))
                  _ -> error ("type error for arguments to " <> show name)))
  , definitionType = JSONType .-> JSONType .-> JSONType
  , definitionDoc = "a " <> name <> " b"
  }

--------------------------------------------------------------------------------
-- Handy combinators

-- | Type a -> b.
(.->) :: Type -> Type -> Type
a .-> b = FunctionType a b
infixr 9 .->