我定义了一个包含单个字段的自定义数据类型:
import Data.Set (Set) data GraphEdge a = GraphEdge (Set a)
定义我自己的类型感觉在语义上更正确,但它导致我的函数中有很多样板.任何时候我想使用内置Set
函数,我必须打开内部集合,然后重新包装它:
import Data.Set (map) modifyItemsSomehow :: Ord a => GraphEdge a -> GraphEdge a modifyItemsSomehow (GraphEdge items) = GraphEdge $ Set.map someFunction items
这可以通过使其成为记录来略微改善,例如
import Data.Set (Set, map) data GraphEdge a = GraphEdge { unGraphEdge :: Set a } modifyItemsSomehow = GraphEdge . map someFunction . unGraphEdge
但这仍然远非理想.在处理由单个字段组成的用户定义数据类型时,处理此类样板的最惯用方法是什么?
在其他任何事情之前,您应该确保使用newtype
单字段单构造函数类型.data
引入运行时开销和额外的懒惰,并阻止我们使用以下两种技术.
首先,您可以GeneralizedNewtypeDeriving
尽可能使用:
{-# LANGUAGE GeneralizedNewtypeDeriving #-} newtype Foo a = Foo a deriving (Eq, Show, Ord, Num) foo :: Foo Int foo = 0 bar :: Foo Int bar = foo * 120
其次,您可以使用coerce
通常在newtype包装之间进行转换:
import Data.Coerce newtype Foo a = Foo a newtype Bar a = Bar a a :: [(Foo (Bar Int), Foo ())] a = [(Foo (Bar 0), Foo ())] b :: [(Int, ())] b = coerce a
第三,你可以使用iso
-s from lens
来简单地在newtype构造函数之上/之下移动操作.
{-# LANGUAGE TemplateHaskell #-} import Data.Set (Set) import qualified Data.Set as Set import Control.Lens newtype GraphEdge a = GraphEdge (Set a) makePrisms ''GraphEdge modifyItemsSomehow :: Ord a => GraphEdge a -> GraphEdge a modifyItemsSomehow = _GraphEdge %~ Set.map someFunction