Как написать семейство функций printf (отладочная печать и т. Д.) В Haskell - PullRequest
5 голосов
/ 05 марта 2012

Это проблема больше, чем полезная (я потратил на это несколько часов).Учитывая некоторые функции,

put_debug, put_err :: String -> IO ()
put_foo :: String -> StateT [String] m ()

Я хочу написать обобщенную функцию printf, вызвать ее gprint, чтобы я мог написать

pdebug = gprint put_debug
perr = gprint put_err
pfoo = gprint put_foo

, а затем использовать pdebug, perrи pfoo как printf, например,

pdebug "Hi"
pdebug "my value: %d" 1
pdebug "two values: %d, %d" 1 2

Мне не удается придумать достаточно общий класс.Мои попытки были такими: (для тех, кто знаком с Printf или подходом Олега к вариативным функциям)

class PrintfTyp r where
    type AppendArg r a :: *
    spr :: (String -> a) -> String -> [UPrintf] -> AppendArg r a

или

class PrintfTyp r where
    type KRetTyp r :: *
    spr :: (String -> KRetTyp r) -> String -> [UPrintf] -> r

Оба слишком сложны для написания базовых экземпляров:нет хорошего выбора для r для первого подхода (и его тип не отражается в неинъективном семействе индексированных типов AppendArg), а во втором подходе получается, что instance PrintfTyp a выглядит неправильно (совпадает со слишком многими типами).

Опять же, это просто сложная задача: делайте это, только если это весело.Хотя мне определенно было бы интересно узнать ответ.Спасибо !!

Ответы [ 3 ]

3 голосов
/ 05 марта 2012

Вот один подход, который пытается позволить существующему Text.Printf выполнить как можно большую часть работы.Во-первых, нам понадобятся некоторые расширения:

{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE FlexibleContexts #-}

-- To avoid having to write some type signatures.
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE ExtendedDefaultRules #-}

import Control.Monad.State
import Text.Printf

Идея состоит в том, чтобы вводить аргументы по одному в printf, чтобы получить отформатированный String, затем взять его и передать егодействие, которое нам дали в начале.

gprint :: GPrintType a => (String -> EndResult a) -> String -> a
gprint f s = gprint' f (printf s)

class PrintfType (Printf a) => GPrintType a where
  type Printf a :: *
  type EndResult a :: *
  gprint' :: (String -> EndResult a) -> Printf a -> a

Рекурсивный шаг принимает аргумент и передает его на вызов printf, который мы строим в g.

instance (PrintfArg a, GPrintType b) => GPrintType (a -> b) where
  type Printf (a -> b) = a -> Printf b
  type EndResult (a -> b) = EndResult b
  gprint' f g x = gprint' f (g x)

Базовые случаи просто передают полученную строку в f:

instance GPrintType (IO a) where
  type Printf (IO a) = String
  type EndResult (IO a) = IO a
  gprint' f x = f x

instance GPrintType (StateT s m a) where
  type Printf (StateT s m a) = String
  type EndResult (StateT s m a) = StateT s m a
  gprint' f x = f x

Вот тестовая программа, которую я использовал:

put_debug, put_err :: String -> IO ()
put_foo :: Monad m => String -> StateT [String] m ()

put_debug = putStrLn . ("DEBUG: " ++)
put_err   = putStrLn . ("ERR: " ++)
put_foo x = modify (++ [x])

pdebug = gprint put_debug
perr = gprint put_err
pfoo = gprint put_foo

main = do
  pdebug "Hi"
  pdebug "my value: %d" 1
  pdebug "two values: %d, %d" 1 2
  perr "ouch"
  execStateT (pfoo "one value: %d" 42) [] >>= print

И вывод:

DEBUG: Hi
DEBUG: my value: 1
DEBUG: two values: 1, 2
ERR: ouch
["one value: 42"]
1 голос
/ 05 марта 2012

Классы предназначены для отправки по типу.Таким образом, для put_foo архитектура Text.Printf уже удовлетворительная (хотя, к сожалению, она не экспортирует PrintfType).Например, кажется, что следующее работает хорошо:

{-# LANGUAGE TypeFamilies #-} -- for ~ syntax
import Control.Monad.State
import Data.Default

-- copy and paste source of Text.Printf here

put_foo :: String -> StateT [String] m ()
put_foo = undefined

instance (Default a, Monad m, s ~ [String]) => PrintfType (StateT s m a) where
    spr s us = put_foo (spr s us) >> return def

Для put_debug и put_err вы можете обобщить PrintfType таким же образом, как это делает HPrintfType, но взяв String -> IO ()функция вместо ручки.Тогда вы бы написали

pdebug  = funPrintf put_debug
perr    = funPrintf put_err
printf' = funPrintf putStr -- just for fun
pfoo    = printf
0 голосов
/ 05 марта 2012

Я не уверен, что компилятор сможет вывести это. Откуда он знает, что вы ожидаете, что строка будет напечатана в контексте монады StateT, в отличие от принятия другого аргумента в монаде (a ->).

Возможно, вам понадобится ввести способ показа проверки типов после завершения списка аргументов. Самый простой способ - просто обернуть его в функцию, так что вы пишете:

pdebug $ printf "%d %d %d" 1 2 3

И тогда pdebug может быть полиморфным в монаде.

Вы также можете использовать его, чтобы использовать терминатор, например:

data Kthx = Kthx
printf "%d %d %d" 1 2 3 Kthx

Но я не могу понять, как сейчас.

...