【发布时间】:2018-09-04 15:40:17
【问题描述】:
我正在尝试在 Haskell 中将 Pascal 风格的 write 过程实现为多变量函数。这是一个具有单态结果类型的简化版本(在这种情况下为IO),效果很好:
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}
module Main where
import Control.Monad.IO.Class
import Control.Monad.Trans.Reader
import System.IO
class WriteParams a where
writeParams :: IO () -> a
instance (a ~ ()) => WriteParams (IO a) where
writeParams = id
instance (Show a, WriteParams r) => WriteParams (a -> r) where
writeParams m a = writeParams (m >> putStr (show a ++ " "))
write :: WriteParams params => params
write = writeParams (return ())
test :: IO ()
test = do
write 123
write ('a', 'z') True
但是,当将结果类型更改为多态类型时,为了在具有 MonadIO 实例的不同 monad 中使用该函数,我遇到了重叠或无法确定的实例。具体来说,以前版本中的 a ~ () 技巧不再起作用。最好的方法是以下,但需要大量类型注释:
class WriteParams' m a where
writeParams' :: m () -> a
instance (MonadIO m, m ~ m') => WriteParams' m (m' ()) where
writeParams' m = m
instance (MonadIO m, Show a, WriteParams' m r) => WriteParams' m (a -> r) where
writeParams' m a = writeParams' (m >> liftIO (putStr $ show a ++ " "))
write' :: forall m params . (MonadIO m, WriteParams' m params) => params
write' = writeParams' (return () :: m ())
test' :: IO ()
test' = do
write' 123 () :: IO ()
flip runReaderT () $ do
write' 45 ('a', 'z') :: ReaderT () IO ()
write' True
有没有办法让这个例子工作而不必到处添加类型注释并仍然保持结果类型多态?
【问题讨论】:
-
你看过
Text.Printf(除非我误解你在这里尝试做的事情,确实是这个吗?) -
我怀疑如果你想要良好的类型推断,你将不得不为每个现有的
MonadIO实例创建一个WriteParams实例——或者至少为每个MonadIO实例创建一个实例你关心使用。您可以通过提供默认值来减少繁琐,这样您只需要编写例如instance (MonadIO m, a ~ ()) => WriteParams (ReaderT r m a)并且不需要给where块,但是...是的,有点烦人。 -
@Cubic 我的用例比
printf更复杂,我只是在示例中对其进行了一些简化,以便它们看起来相似 -
@DanielWagner 这是一个非常简单但有效的想法,我没有想到。不幸的是,它似乎并没有解决 Li-yao Xia 在他们的回答中提到的
test的泛化问题。 -
我不确定你的意思。我添加了一个充实我的评论的答案;也许你可以用一个不适用于该解决方案的具体代码 sn-p 来反对。
标签: haskell polyvariadic