Question

I hope my terminology is correct here - if not, feel free to edit anything.

I'm using Haskell as a helper-language whilst writing a paper on combinatorial game theory - i.e., all of my functions just juggle numbers and help me to come to a solution for the game I'm studying.

I wrote all of the functions for a concrete, integral 'board size' (think chequerboard, 5x5 etc). I wanted to expand to study boards of any size, so modified all the relevant functions by including an Integer parameter. e.g.,

type Size = Int

squares :: Size -> [Square]
squares size = [ (x,y) | x <- [1..size],  
                         y <- [1..size]]

This, however, has started to get messy. Functions which normally I would consider size-independent have to be supplied with a Size whenever they access a function which requires a Size.

This very quickly leads to lines like this:

reaching size = (losing size) \\\ (setA size)

takeUDmir _  []       = []
takeUDmir size (x:xs) = [x] ++ takeUDmir size (xs \\\ mirUD size x)

rotate size s = concatMap (mirUD size) (mirLR size s)

(Note that the content of the functions really doesn't matter, I'm just trying to show how out-of-hand it has become.)

I'm pretty confident using Haskell, and with functional programming in general, but I've got no idea how I could go about removing all of these size references, and simply relying on something else which sets the size for each function which requires its use.

I think I'm probably looking for a monad, but I've got no idea.

Was it helpful?

Solution

This is a perfect time to pull out the Reader monad—it abstracts the notion of some globally available, read-only configuration data.

data Config = Config { size :: Int, ... }

type MyMonad = Reader Config

fun :: MyMonad Result
fun = funNeedingTheSize <$> asks size <*> pure arg1 <*> pure arg2

runMyMonad :: Config -> MyMonad a -> a
runMyMonad = flip runReader

OTHER TIPS

I'm going to flesh out Augustss' suggestion here. If you just need to define size once then you could build all of the dependent machinery in a local binding. This effectively lets you create a number of functions all inside the context of a specific definition of size and then later use all of these functions just picking the size once. It goes especially well with RecordWildCards

{-# LANGUAGE RecordWildCards -#}

data Methods =
  Methods { meth1 :: Int -> Int
          , meth2 :: Int -> Int
          ...
          }

mkMethods :: Int -- ^ size
          -> Methods
mkMethods size = 
  Methods { meth1 = \i -> size + i
          , meth2 = \i -> size - i
          ...
          }

...

someFn :: Int -> Result
someFn size = ... meth1 ... meth2 ...
  where Methods {..} = mkMethods size

This is effectively a limited way to simulate ML modules.

An alternative to the Reader monad is Implicit Parameters.

These often get a bad press as the semantics can be subtle if you try to change the value of the parameter in a recursive call.

However they do have the advantage of avoiding the need to rewrite your code in monadic style, which is quite an invasive change that detracts from readability.

My view is that they are perfect in situations like yours where they will just be set once as sort of "configuration" information at the top-level of a chain of calls and not changed inside that chain.

They do still invade your type signatures if you have any, but will be inferred automatically like type classes if you don't give signatures.

Your code snippets above would look like this with implicit parameters if you use them for size everywhere:

type Size = Int

squares :: (?size :: Size) => [Square]
squares = [ (x,y) | x <- [1..?size],  
                    y <- [1..?size]]

reaching = losing \\\ setA

takeUDmir []     = []
takeUDmir (x:xs) = [x] ++ takeUDmir (xs \\\ mirUD x)

rotate s = concatMap mirUD (mirLR s)

It may be that in some of the functions, it's more appropriate to pass the value explicitly - it depends on how significant the size parameter is to that particular function.

To actually "instantiate" the parameter, use let:

let ?size = 5 in squares

Another way of solving this problem is to use the reflection library. This is definitely a sophisticated method and should be used with great care. There's a tutorial resource on it by Austin Seipp available at FP Complete that goes into its implementation, but that can be quite hairy.

The simplified form of reflection is the Given typeclass. To do that, we simply annotate our functions with given wherever we desire some global configured value.

meth1 = 1 + given
meth2 = given - 1

Like ImplicitParams we get notice of our use of Given in the type signatures.

meth1 :: (Num a, Given a) => a
meth2 :: (Num a, Given a) => a

This allows us to statically eliminate them and ensure that all proper configuration information has been passed using give

give :: a -> (Given a => r) -> r

So that if we have someComplexCalculation :: Given Config => Result we can solve that by providing a Config.

give config someComplexCalculation :: Result

and the types ensure that we didn't let this missing configuration propagate to the top.


The Reifies/reflect/reify machinery generalizes this property, but does so at a loss of some convenience and simplicity. It's needed if you must pass multiple configurations which overlap—using Given it'd be impossible for the system to know which givens should be replaced by the passed configuration under the overlapping region, Reifies solves that with an s parameter much like the ST monad's s parameter.

The killer application for Reifies however is passing runtime configured typeclasses to expressions. Normally you can't override typeclasses, but Reifies gives a trick way of doing that. See Austin Seipp's tutorial toward the bottom for more details about that.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top