3

I understand there are no "positional" arguments in the sense that all functions take only a single variable and return a function to operate on the remaining arguments, but here's what I mean I want to do:

Starting with some function I'm using to format lists with a prepended item, a separator, and a closing item.

Prelude> formatList start end sep xs = start ++ (intercalate . separator ( map show xs )) ++ end

Works like this:

Prelude Data.List> formatList "(" ")" "," [1..10]
"(1,2,3,4,5,6,7,8,9,10)"

Cool, same idea can be used for xml tags:

Prelude Data.List> formatList "<meow>" "</meow>" "" [1..10]
"<meow>12345678910</meow>"

In the spirit of reusing functions and terseness, let's also make it so we don't have to type the redundant part of the meow tag by making a function to produce the open and close from just the word "tag."

Prelude Data.List> tagger tag item = "<" ++ tag ++ ">" ++ item ++ "</" ++ tag ++ ">"
Prelude Data.List> tagger "div" "contents"
"<div>contents</div>"

So now make some tag-maker that will return a start and end I can make the second argument to my formatList function:

Prelude Data.List> tagMaker tag = ("<" ++ tag ++ ">", "</" ++ tag ++ ">")

Looks good:

Prelude Data.List> tagMaker "div"
("<div>","</div>")

Now try it. Actual behavior:

Prelude Data.List> formatList (tagMaker "div") "" [1..10]

<interactive>:49:13: error:
    • Couldn't match expected type ‘[Char]’
                  with actual type ‘([Char], [Char])’
    • In the first argument of ‘formatList’, namely ‘(tagMaker "div")’
      In the expression: formatList (tagMaker "div") "" [1 .. 10]
      In an equation for ‘it’:
          it = formatList (tagMaker "div") "" [1 .. 10]

Desired behavior:

formatList (tagMaker "div") "" [1..10]
"<div>12345678910</div>

What's the right way to make the tagMaker function be able to sub in for a function that expects to take its first value, then its second value? If this is completely non-idiomatic, what's the right idiom?

Mittenchops
  • 18,633
  • 33
  • 128
  • 246
  • This _might_ be related: [Feed elements of a tuple to a function as arguments in Haskell](https://stackoverflow.com/questions/5117948/feed-elements-of-a-tuple-to-a-function-as-arguments-in-haskell) – TrebledJ Jan 21 '19 at 03:09
  • 4
    To get between functions that take multiple curried arguments and functions that take tuples, use [`curry`](https://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#v:curry)/[`uncurry`](https://hackage.haskell.org/package/base-4.12.0.0/docs/Prelude.html#v:uncurry). It only works for pairs, though, triples and beyond you are out of luck and must use an explicit lambda. – luqui Jan 21 '19 at 03:38

2 Answers2

9

The usual trick for named arguments seems like it might be of interest.

data Formatter = Formatter { start, end, sep :: String }

formatList :: Show a => [a] -> Formatter -> String
formatList xs fmt = start fmt ++ intercalate (sep fmt) (map show xs) ++ end fmt

with :: Formatter
with = Formatter "" "" ""

Now your original calls look like this:

formatList [1..10] with { start = "(", end = ")", sep = "," }
formatList [1..10] with { start = "<meow>", end = "</meow>" }

You can make a formatter factory this way:

xmlTag :: String -> Formatter
xmlTag t = with { start = "<" ++ t ++ ">", end = "</" ++ t ++ ">" }

Usage looks like this:

formatList [1..10] (xmlTag "div")               -- use the default separator
formatList [1..10] (xmlTag "div") { sep = "," } -- specify a separator
Daniel Wagner
  • 145,880
  • 9
  • 220
  • 380
1

Just uncurry formatList so that it takes a 2-tuple as its first argument instead of two separate arguments.

> :t formatList
formatList :: Show a => String -> String -> String -> [a] -> String
> :t uncurry formatList
uncurry formatList :: Show a => (String, String) -> String -> [a] -> String
> (uncurry formatList) (tagMaker "div") "" [1..10]
"<div>12345678910</div>

However, I'd just be a little more explicit about using the return value of tagMaker. The above only works because tagMaker is supplying precisely the first two arguments to formatList.

> let (b,e) = tagMaker "div" in formatList b e "" [1..10]
"<div>12345678910</div>"
chepner
  • 497,756
  • 71
  • 530
  • 681