Saurabh Nanda
Saurabh Nanda

Reputation: 6793

How to write a multi-arity function (variable number of arguments) in Haskell

I've been breaking my head trying to write a multi-arity function on the lines of Development.Shake.cmd, but even after 8 different attempts across 3 days, I have failed to get this to compile.

Here's what I've managed to get done:

{-# LANGUAGE ExtendedDefaultRules #-}

--
-- this is for an i18n system integrated with Lucid
-- where I want the `t_` function to work with a variable number of arguments
--

module Try4 where

import Lucid (HtmlT, div_, toHtmlRaw)
import Control.Monad.Trans (lift)
import Data.Text as T

default (Text)


newtype Translation = Translation { rawTranslation :: Text }

class (Monad m) => HasI18n m where
  fetchTranslation :: Text -> m Translation

class (HasI18n m) => InterpolationValue m a where
  -- the reason why this function is not pure, is because it is possible
  -- for the conversion function to depend on the monadic context. For example,
  -- it is not necessary that date/time values are always converted to text using
  -- the same format. The format to be used could be part of the monadic context.
  interpolationVal :: a -> m Text

class Interpolate arg result where
  t_ :: arg -> result

instance (HasI18n m, a ~ ()) => Interpolate Text (HtmlT m a) where
  t_ k = do
    x <- lift $ fetchTranslation k
    toHtmlRaw $ rawTranslation x

instance (HasI18n m, InterpolationValue m v, a ~ ()) => (Interpolate Text ((Text, v) -> HtmlT m a)) where
  t_ k (i, v) = do
    x <- lift $ fetchTranslation k
    val <- lift $ interpolationVal v
    toHtmlRaw $ applySingleInterpolation (rawTranslation x) (i, val)

instance (HasI18n m) => InterpolationValue m Text where
  interpolationVal = pure

instance (HasI18n m) => InterpolationValue m Int where
  interpolationVal = pure . T.pack . show

applySingleInterpolation :: Text -> (Text, Text) -> Text
applySingleInterpolation memo (k, v) = T.replace ("%{" <> k <> "}") v memo

myHtml :: (HasI18n m) => HtmlT m ()
myHtml = div_ $ do
  div_ $ t_ "some.i18n.key"
  div_ $ t_ "some.18n.key" ("interpolation1", "some-string")

And finally, here's where I'm stuck. How do I write the (recursive) type-class instance to get the following to compile? No matter what I try, I cannot get the m of InterpolationValue m a and HtmlT m a to unify. Somehow, because of the Interpolate arg result, GHC seems to get confused and infers a different m1 and m2.

t_ "some.18n.key" 
   ("interpoliation1", "some-string") 
   ("interpolation2", 10 :: Int)
   ("interpolation3", fromGregorian 2020 1 1)

Upvotes: 0

Views: 285

Answers (1)

Neil Mitchell
Neil Mitchell

Reputation: 9250

I suggest starting from copying Text.Printf and incrementally modifying it til you get what you want. That's the pure source from which the Shake version was copied. The important instances are:

instance (PrintfArg a, PrintfType r) => PrintfType (a -> r) where
instance (a ~ ()) => PrintfType (IO a) where

That's one inductive instance, and one terminal instance.

Upvotes: 0

Related Questions