Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generating functions from a recursive type

Tags:

haskell

Suppose in my application I have a set of functions that have very regular implementations (like specific logging functions). I have a type

data ShowFns = 
  { showFn1 :: Int -> Bool -> Double -> String
  , showFn2 :: Double -> Char -> String
  }

Both of these could be implemented simply as

showFn1' :: Int -> Bool -> Double -> String
showFn1' a b c = show a <> " " <> show b <> " " <> show c

showFn2' :: Double -> Char -> String
showFn2' a b = show a <> " " <> show b

fnCollection :: ShowFns
fnCollection = ShowFns showFn1' showFn2'

However, it seems like this repetitive pattern could be derived from a recursive type where all the leaf types have Show instances.

I would rather write:

fnCollection :: ShowFns
fnCollection = ShowFns toShowFn toShowFn

I have a sense this is possible because this is sorta how the Servant machinery works but servant is a bit more complicated and I haven't been able to replicate that machinery down to my simpler example. I have tried a few formulations with open type families but I can't seem to get the type system to reconcile the recursive case ShowMe and the base case ().

like image 693
Asa Avatar asked Dec 18 '22 17:12

Asa


1 Answers

The standard trick for implementing variadic functions looks like this:

{-# LANGUAGE FlexibleInstances #-}
class ShowMe a where showMeRaw :: [String] -> a
instance ShowMe String where showMeRaw = unwords . reverse
instance (Show a, ShowMe b) => ShowMe (a -> b) where
    showMeRaw ss a = showMeRaw (show a : ss)

One then normally adds a single top-level helper:

showMe :: ShowMe a => a
showMe = showMeRaw []

Then your showFun1' and showFun2' would both be just calls to showMe. Try it out in ghci:

> (showMe :: Int -> Bool -> Double -> String) 3 True 2.5
"3 True 2.5"
> (showMe :: Double -> Char -> String) 2.5 'a'
"2.5 'a'"
like image 57
Daniel Wagner Avatar answered Jan 06 '23 12:01

Daniel Wagner