Haskell中在新类型(“包装类型”)上构建函数的惯用方法是什么?
让Haskell中在新类型(“包装类型”)上构建函数的惯用方法是什么?,haskell,Haskell,让StringWrapper1和StringWrapper2成为两种类型的字符串(即newtype-StringWrapper1=StringWrapper1-string和newtype-StringWrapper2=StringWrapper2) 现在假设我们正在尝试从StringWrapper1到StringWrapper2构建一个函数 funcWrapper :: StringWrapper1 -> StringWrapper2 一方面,我们想明确表示,我们传递给这个函数的是一个
StringWrapper1
和StringWrapper2
成为两种类型的字符串(即newtype-StringWrapper1=StringWrapper1-string
和newtype-StringWrapper2=StringWrapper2
)
现在假设我们正在尝试从StringWrapper1
到StringWrapper2
构建一个函数
funcWrapper :: StringWrapper1 -> StringWrapper2
一方面,我们想明确表示,我们传递给这个函数的是一个StringWrapper1
,因此我们不想仅仅将StringWrapper1
视为String
的类型同义词(我自己的经验可以证明,这会导致bug)。另一方面,在概念上构建函数时,我们仍然在某种程度上考虑String
s。然后我们要做的是首先构建func
,它不需要我们不断地包装和展开类型:
func :: String -> String
然后,我们使用func
构建funcWrapper
:
funcWrapper :: StringWrapper1 -> StringWrapper2
funcWrapper (StringWrapper1 str) = StringWrapper2 (func str)
问题:这是惯用语吗?经常用
func
和funcWrapper
复制每个函数似乎很尴尬。Haskell是否提供了我所缺少的其他方法?或者我应该只使用类型同义词吗?为什么不编写一个可以包装其他函数的函数呢
wrap :: (String -> String) -> StringWrapper1 -> StringWrapper2
wrap f (StringWrapper1 str) = StringWrapper2 (f str)
这会将任何
String->String
提升到StringWrapper1->StringWrapper2
首先,您应该考虑leftaroundabout的注释,并确保newytpes实际上是有意义的。这就是说,这种包装和拆开包装确实是日常用品,但你可以让它更方便。一种方法是利用字符串包装器是单态函子(而不是多态的函子
s),因此您可以编写映射函数,例如:
mapWrapper1 :: (String -> String) -> StringWrapper1 -> StringWrapper1
mapWrapper1 f (StringWrapper1 str) = StringWrapper1 (f str)
mapWrapper2 :: (String -> String) -> StringWrapper2 -> StringWrapper2
mapWrapper2 f (StringWrapper2 str) = StringWrapper2 (f str)
这种模式的一个众所周知的推广是mono可遍历包中的类
在两个包装器之间定义转换函数也很容易(用花哨的行话来说,我们会说这是两个函子之间的自然转换):
(rewrap1as2
可以从Data.concure
简单地实现为concure
。有关详细信息,请参阅。)
然后,可以根据以下更基本的函数定义中的wrap
:
mapAndRewrap1as2 :: (String -> String) -> StringWrapper1 -> StringWrapper2
mapAndRewrap1as2 f = rewrap1as2 . mapWrapper1 f
如果你想要更不冗长的东西,你可能会喜欢这个软件包,或者。然而,这可能值得一个单独的答案。正如其他人所说,你应该确保这是你真正想要做的事情(参见leftaroundabout的评论)。如果是,则可以使用在具有相同运行时表示形式的类型之间进行转换:
func :: String -> String
func = ...
...
funcWrapper :: StringWrapper1 -> StringWrapper2
funcWrapper = coerce func
使用该包,可以编写如下内容
{-# language DeriveGeneric #-}
module Main where
import GHC.Generics
import Control.Newtype (Newtype,over)
newtype StringWrapper1 = StringWrapper1 String deriving Generic
instance Newtype StringWrapper1
newtype StringWrapper2 = StringWrapper2 String deriving Generic
instance Newtype StringWrapper2
func :: String -> String
func = undefined
funcWrapper :: StringWrapper1 -> StringWrapper2
funcWrapper = over StringWrapper1 func
我不会定义包装器函数,而是在每个站点上使用。这很优雅;但是,您仍然需要为每种函数类型创建这些包装函数。如果
StringWrapper1
被嵌入到记录数据结构中呢?似乎仍然有很多重复。但最后:你文章中的解决方案是惯用的Haskell方法吗?@George注意到,“创建这些包装函数”相当于使用wrap f
,而我本来会使用f
。无需为所有此类函数提供具有自己标识符的定义。我只需要定义普通的f
,并让调用者在需要时使用wrap f
。没有重复。如果您在概念上考虑字符串,那么为什么需要类型级别的区别?另一种类型的要点是,即使实际实现是一个字符串,您也会考虑一些概念上不同的东西。我认为generalizednewtypedering
通常比使用强制
之类的方法(这里的答案似乎重点关注)更好,但这要视情况而定。这似乎是最优雅的。是否不赞成使用它?@George不,如果这是您真正想要做的,我从来没有见过它不赞成(通常您希望一个新类型在某个时候在外部是“不透明的”。通常在其定义的模块之外)<代码>强制的开销也为零(包装/展开解决方案的情况并非如此),因为它在编译过程中被消除。请注意,强制
与不安全
有很大不同<代码>强制总是安全的。包装和解包解决方案的开销也为零。@BenjaminHodgson啊,没错。如果您有类似于map(\(StringWrapper1str)->str)包装器的东西,那么开销差异就会发挥作用。
{-# language DeriveGeneric #-}
module Main where
import GHC.Generics
import Control.Newtype (Newtype,over)
newtype StringWrapper1 = StringWrapper1 String deriving Generic
instance Newtype StringWrapper1
newtype StringWrapper2 = StringWrapper2 String deriving Generic
instance Newtype StringWrapper2
func :: String -> String
func = undefined
funcWrapper :: StringWrapper1 -> StringWrapper2
funcWrapper = over StringWrapper1 func