2

I'm learning Haskell and writing a program to solve a toy problem. The program uses a parameter k that doesn't change while it is running, after the parameter has been read from a file. I'm very new to using pure functions, and I would like to write as many functions as pure ones as I can.

I have a data type Node, and functions to compare nodes, get the descendants of nodes, and more. Currently, all of these functions take a parameter k as an argument, such as

compare k node1 node2 = ...
desc k node = ...

and whenever I have to recursively call any of these within the function, I have to repeat the k parameter. It seems redundant, since k will never have a different value for these functions and since it makes the type signatures less readable, and I would like to refactor it out if possible.

Are there any strategies to do this with pure functions, or is it simply a limitation I'll have to deal with?

What I've thought of

Earlier I hard-coded k at the top level, and it seemed to work (I was able to use k in the functions without needing it as an explicit argument). But this obviously wasn't feasible once I needed to read input from file.

Another possible strategy would be to define all these functions in the main function, but this seems to be strongly discouraged in Haskell.

beardc
  • 20,283
  • 17
  • 76
  • 94
  • What do you need `k` for? Unless it is something such as a custom comparison function, a comparison between nodes that depends on anything but the nodes themselves sounds a little odd. – duplode Mar 22 '14 at 05:09
  • @duplode I use `k` to calculate the descendents of a node in `desc`, which I use in recursive calls to `compare`, if that makes sense – beardc Mar 22 '14 at 05:29
  • That may well make sense. The names made me think of contexts in which extra parameters affecting the results would sound suspicious (the `compare` standard function, nodes as in trees), but that may be just me. In any case, one nice thing about purity is that signatures tend to reflect better the actual dependencies (e.g. from the type of `desc` it is immediately obvious that the result may depend on the value of `k`). – duplode Mar 22 '14 at 06:00

4 Answers4

5

The usual Haskell approach would be to use the Reader monad. One way of thinking about Reader is that it provides computations with access to an environment. It can be defined as

newtype Reader r a = Reader { runReader :: r -> a }

So your functions would have the types

compare :: Node -> Node -> Reader k Ordering -- or Bool, or whatever the return value is

desc :: Node -> Reader k String -- again, guessing at the output type.

Within a Reader computation, use the function ask :: Reader r r to get access to the parameter.

At the top level, you can run a Reader computation with runReader theComputation env

This is often nicer than passing arguments explicitly. First, any function that doesn't need the environment can be written as a normal function without taking it as a parameter. If it calls another function that does use the environment, the monad will provide it automatically with no extra work on your part.

You can even define a type synonym,

type MyEnv = Reader Env

and use that for your functions type signatures. Then if you need to change the environment, you only need to change one type instead of changing all your type signatures.

The definition from standard libraries is a bit more complicated to handle monad transformers, but it works the same way as this simpler version.

John L
  • 27,937
  • 4
  • 73
  • 88
4

Ultimately you have to pass in the value of k everywhere it is needed, but there are some things you can do to avoid repeating it.

One thing you can do is to define convenience functions once the value of k is known:

myfunc = let k = ...
             compare' = compare k
             desc'    = desc k
         in ...
            (use compare' and desc' here)

Another approach is to use the Implicit Parameters extension. This involves defining compare and desc to take k as an implicit parameter:

{-# LANGUAGE ImplicitParameters #-}

compare :: (?k :: Int) => Node -> Node
compare n1 n2 = ... (can use ?k here) ...

desc :: (?k :: Int) => Node
desc = ... (can use ?k here) ...

myfunc = let ?k = ...
         in ... use compare and desc ...

Note that in either case you can't call compare or desc until you've defined what k is.

ErikR
  • 51,541
  • 9
  • 73
  • 124
  • +1, as much as some people might dislike it, a parameter (in the dynamic sense) was the first thing that came to my mind. – Wes Mar 22 '14 at 05:43
  • @Wes why is it disliked? Just not very Haskell-like? – beardc Mar 22 '14 at 05:50
  • @BirdJaguarIV dynamic binding is something you find in languages from the lisp family, it arguably doesn't really "fit in" with Haskell, but imo it has its uses. – Wes Mar 22 '14 at 05:51
0

This is how I like to structure recursive functions with values that don't change

map f xs = map' xs
  where map' (x:xs) = f x : map' xs
Charles Durham
  • 1,707
  • 11
  • 17
0

Two simple tricks with local function definitions that may be useful. First, you can make k implicit within your recursive definitions by just playing with scope:

desc :: Int -> Node -> [Node]
desc k node = desc' node
    where
    desc' node = -- Carry on; k is in scope now.

Second, if you are going to call your functions a lot of times with the same k within the same scope, you can use a local definition for the partially applied function:

main = do
    k <- getKFromFile
    -- etc.
    let desc' = desc k -- Partial application.
        descA = desc' nodeA
        descB = desc' nodeB
    print [descA, descB]

Proper implicit parameter passing is commonly done (or, arguably, simulated) with the Reader monad (see John L's answer), though that sounds kind of heavyweight for your use case.

duplode
  • 33,731
  • 7
  • 79
  • 150