在以下代码中
module Main where
import Control.Monad.State
import Control.Applicative
type Code = String
toSth :: Read a => State [Code] a
toSth = state $ \(c:cs) -> ((read c), cs)
codes = ["12", "True", ""]
data Tick = Tick {n :: Int, bid :: Bool} deriving (Show)
res = runState (pure Tick <*> toSth <*> toSth) codes
main = print res
我得到正确的结果
(Tick {n = 12, bid = True},[""])
但是我的问题是重复的
pure Tick <*> toSth <*> toSth
也就是说,如果记录有100个字段,那么我必须写<*> toSth
100次,看起来不像Haskell。
是否有办法在foldl
上<*>
?我知道标准foldl :: Foldable t => (b -> a -> b) -> b -> t a -> b
在这里不起作用,因为累加器类型在每次迭代中都会改变。
非常感谢!
答案 0 :(得分:7)
这可以通过一些高级generics库(例如generics-sop)来完成。
泛型库将数据类型与某种“统一”表示形式相互转换。这些库还提供创建或修改此类表示的功能。我们可以处理表示形式,然后再转换回原始数据类型。
{-# language DeriveGeneric, TypeApplications #-}
import qualified GHC.Generics
import Generics.SOP (Generic,to,SOP(SOP),NS(Z),hsequence,hcpure)
import Data.Proxy
data Tick = Tick {n :: Int, bid :: Bool} deriving (Show,GHC.Generics.Generic)
instance Generic Tick -- this Generic is from generics-sop
res :: (Tick, [Code])
res =
let tickAction :: State [Code] Tick
tickAction = to . SOP . Z <$> hsequence (hcpure (Proxy @Read) toSth)
in runState tickAction codes
hcpure
使用有效的函数(此处为toSth
)创建一个n-ary product,该函数知道如何创建产品的每个成员。我们必须传递带有约束的Proxy
才能说服编译器。结果是一个产品,其中每个组件都包装在State
中。
hsequence
类似于sequenceA
,但对于n元产品,每个组件具有不同的类型。结果类似:Applicative
被“向外拉”。
SOP
和Z
是包装产品的构造函数,让我们调用to
以获得原始Tick
类型的值。
res
这个更通用的签名,以处理作为Generics.SOP.Generic
实例的任何单个构造函数记录:
{-# language DataKinds #-}
res :: (Generic r, Generics.SOP.Code r ~ '[ xs ], Generics.SOP.All Read xs) => (r,[Code])
res =
let tickAction = to . SOP . Z <$> hsequence (hcpure (Proxy @Read) toSth)
in runState tickAction codes