Is there a way to implicitly coerce a type to another wrapper type in haskell?

Viewed 92

I'm coding a simple function in Haskell for my university assignment - the function should take an integer n and make an ascii triangle with the bottom row being 2*n + 1 wide.

This is my implementation:

module Triangle where

data TArg = CallArg Int | RecursiveArg (Int, Int)
triangle :: TArg -> String

triangle (CallArg k) = triangle (RecursiveArg (k, k))
triangle (RecursiveArg (0, _)) = ""
triangle (RecursiveArg (k, q)) = triangle (RecursiveArg (k-1, q+1)) ++ spaces ++ stars ++ spaces ++ ['\n'] where
  spaces = replicate q ' '
  stars = replicate (k*2 - 1) '*'

Now, while it works - I don't like the necessity to specify the variant when calling the function, and would rather have some sort of coercion or a different way to specify two possible variations.

Thank you in advance

2 Answers

It looks like you're trying to set up a recursive function with a call wrapper because the recursive call requires a second argument. I don't think your approach of encapsulating the arguments in a data structure is a good one. A more common approach is to create two functions, an outer function that you call from outside the definition, and an inner function to call recursively. By using scoping, you can hide the recursive inner function from the outside world. To acheive this, I would rewrite your code as follows

module Triangle where

triangle :: Int -> String
triangle k = triangle' k k where
  triangle' :: Int -> Int -> String
  triangle' 0 _ = ""
  triangle' k q = 
    triangle' (k - 1) (q + 1) ++ spaces ++ stars ++ spaces ++ "\n" where
      spaces = replicate q ' '
      stars  = replicate (k * 2 - 1) '*'

Andrew Ray's answer is what is normally done to write a function that needs to use a different call signature in its recursive form than it does in its initial invocation.

But to complement that answer, what would you do if triangle had to exist as it is shown in the OP?1

If you always need to call it as triangle (CallArg x) for some x and wish you could just call triangle x instead, simply make your own wrapper function:

triangle' x = triangle (CallArg x)

-- or
triangle' =  triangle . CallArg

Now the original triangle gets to have its signature they way it likes (TArg -> String), and you get to call a function with the signature you wish it had (Int -> String). Everybod wins. And the wrapper is trivial, so even on a pure character-count basis using the wrapper is shorter once you need to call triangle' more than a handful of times.

If you wish to avoid the very tiny cost of having to call a different name than triangle, and this is a more realistic program where triangle is something you import from a different module, you can define your own wrapper module instead:

module MyTriangle ( triangle )
where

import qualified Original.Triangle as OT

triangle = OT.triangle . OT.TArg

Now the rest of your code only needs to change the import from import Original.Triangle ( triangle ) to import MyTriangle ( triangle ), and all the (presumeably many, if this is worth bothering with) places where you call triangle get to use the name and signature you prefer.

To generalise: any time a function you want to use several times seems like it has a more complex signature than you actually need because the extra structure and/or values it takes can be mechanically derived from what you want to give it, you can simply create a wrapper that does the translation. You don't need the language to "implicitly coerce" anything; you can say explicitly (but only in one place) how you want it to "coerce", give that a name, and then get the behaviour you want merely by using the name. That way you get concise code, and you avoid redundancy, and everything that's going on is explicit.

(The heart of the technique shown in Andrew Ray's answer is in fact is exactly this "translation wrapper" idea, just also moving the original function to be purely an internal detail if nobody external will ever need to call anything but the wrapper)


1 Say, because it's a third-party function you're just calling, and/or it's actually called sometimes with the case of the data structure, or whatever the reason.

Related