1

I want to build a large schema in Haskell. The constituents take parameters and the parameters are constrained. As an example, I might decide that a circle takes one parameter called Radius, which is constrained to be non-negative. I will define the parameters globally, since each can be used by multiple constituents. There may be hundreds of parameters, and many will have long, difficult-to-type names.

I have a solution of sorts, but the parameter declarations are repetitive and I'd like to simplify them. My criteria for "simple" is just to minimize the number of times a parameter name must be typed. One part of this is to simplify the parameter definitions themselves. Another is to avoid typing parameter names when creating data objects, if possible. So, one should be able to construct a Circle without actually typing "Radius".

The code is below, followed by a few more specific questions. Thanks in advance for any help!

data Constraint = Constraint
test :: Float -> Constraint -> Bool
test _ _ = undefined
--
nonnegative :: Constraint
nonnegative = undefined
--
data Expr = Constant Float -- | Variable String | Add Parameter Parameter ...
eval (Constant x) = x
--
class Parameter a where
  value :: a -> Float
  constraint :: a -> Constraint
  validate :: a -> Bool
  validate x = test (value x) (constraint x)

-- Schema.  Expecting dozens of constituents with many parameters existing
-- in complex relationships.
data Shape = Circle Radius
--
-- There may be hundreds of parameters like Radius, many with long,
-- difficult-to-type names.
data Radius = Radius Expr
instance Parameter Radius where
  constraint _ = nonnegative
  value (Radius r) = eval r
  1. Can you suggest a better way to structure this code?

  2. I think Template Haskell could be used to define a parameter (like Radius) without repeating the name. Would you recommend that approach?

  3. Is it possible to write a default rule for value? Naively, I want to match the pattern value (_ x), but that's not well-typed, of course. Is there some way of accomplishing the same thing?

  4. Is there a simpler way to associate a value with a type? For instance, Radius has a constraint associated with the type, but it seems unnecessary to have to construct a Radius to get its constraint. When I try to write constraint :: Constraint, GHC complains that the type parameter a is not used.

AndyJost
  • 1,085
  • 1
  • 10
  • 18
  • 4
    Is there a reason you couldn't just define `data Parameter a = Param a Constraint` instead of using typeclasses? Then you could just define `value (Param v _) = v` and `constraint (Param _ c) = c`. – Kwarrtz Jul 20 '18 at 23:27
  • If I understand you, that would put the definition of the parameter value and constraint together, so then Circle would need to be defined as ``Circle Parameter`` and constructed as ``Circle (Parameter r (\x -> x > 0))``. But I want to encode the constraint into the type of Circle. Later, when (or after) one constructs a Circle, it should be possible to validate the parameter. – AndyJost Jul 21 '18 at 16:48

1 Answers1

1

It sounds like you wish Haskell had the ability to declare subtypes from predicates. This can be done with smart constructors, but it's true, there is a bit of boilerplate involved. I don't think Template Haskell is such a bad idea. It wouldn't be too hard to wrap up the definition of a smart constructor into something like

subtype "Radius" 'Float [| \x -> x >= 0 |]

You said that there might be hundreds of these things, which raises a design alarm in my mind. I would be looking very hard at this point for opportunities to add more conceptual abstraction to your schema—hundreds is too many. But without more info and context all that's all I can really say: beware!

luqui
  • 59,485
  • 12
  • 145
  • 204
  • 1
    Interesting link. I think I'll give template Haskell a shot. To answer your question, the application is a system for modeling an extremely complex physical process. It's the work of hundreds of people over dozens of years. So it really is like a bag of details. Very hard to reduce. – AndyJost Jul 21 '18 at 16:35
  • 1
    https://hackage.haskell.org/package/refined is also related - a combination of smart constructors and type-level tagging for them. – Carl Jul 21 '18 at 23:33
  • 1
    @Carl Refined seems to be exactly what I was looking for! – AndyJost Jul 22 '18 at 17:29