在Haskell中使用newtypes(“包装类型”)构建函数的惯用方法是什么?

时间:2016-10-31 15:54:10

标签: haskell

StringWrapper1StringWrapper2成为包裹字符串的两种类型(即newtype StringWrapper1 = StringWrapper1 Stringnewtype StringWrapper2 = StringWrapper2)。

现在假设我们正在尝试从StringWrapper1StringWrapper2制作一个函数。

funcWrapper :: StringWrapper1 -> StringWrapper2

一方面,我们希望明确表示我们传入此函数的内容是StringWrapper1,因此我们不希望仅将StringWrapper1视为{{的同义词同义词1}}(这会导致错误,正如我自己的经验可以证明的那样)。另一方面,当从概念上构建函数时,我们仍然在某种程度上考虑String s。我们想要做的是首先构建String,这不是我们经常包装和解包类型:

func

然后,我们使用func :: String -> String 来构建func

funcWrapper

问题/疑问:这是惯用的吗?使用funcWrapper :: StringWrapper1 -> StringWrapper2 funcWrapper (StringWrapper1 str) = StringWrapper2 (func str) func不断复制每个函数似乎很尴尬。 Haskell是否提供了其他一些我不知道的方法?或者我应该只使用类型同义词?

4 个答案:

答案 0 :(得分:5)

正如其他人所说,你应该确保这是你想要做的事情(参见左下角的评论)。如果是,则可以使用coerce from the standard library在具有相同运行时表示的类型之间进行转换:

func :: String -> String
func = ...

...

funcWrapper :: StringWrapper1 -> StringWrapper2
funcWrapper = coerce func

答案 1 :(得分:3)

首先,你应该考虑leftaroundabout的评论,并确保newytpes真正有意义。也就是说,这种包装和展开确实是日常用品,但你可以使它更方便。一种方法是利用字符串包装器是单形仿函数(与Functor 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-traversable 包的MonoFunctor类。

在两个包装器之间定义转换函数也很容易(用花哨的术语,我们可以说这是两个函子之间的自然转换):

rewrap1as2 :: StringWrapper1 -> StringWrapper2
rewrap1as2 (StringWrapper1 str) = StringWrapper2 str

rewrap1as2可以简单地从coerce Data.Coerce实施。有关详细信息,请参阅David Young's answer。)

然后可以根据这些更基本的函数来定义user2297560's answer中的wrap

mapAndRewrap1as2 :: (String -> String) -> StringWrapper1 -> StringWrapper2
mapAndRewrap1as2 f = rewrap1as2 . mapWrapper1 f

如果您想要更简洁的内容,您可能会感谢newtype包或equivalent Isos provided by lens。然而,这可能值得一个单独的答案。

答案 2 :(得分:2)

为什么不编写一个包装任何其他函数的函数?

wrap :: (String -> String) -> StringWrapper1 -> StringWrapper2
wrap f (StringWrapper1 str) = StringWrapper2 (f str)

这会将String -> String提升为StringWrapper1 -> StringWrapper2

答案 3 :(得分:1)

使用newtype-generics包,可以编写类似

的内容
{-# 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

我不会定义包装函数,而是在每个站点使用over