为清晰起见,制作新类型/数据是否不好?

Bry*_*and 17 haskell type-safety newtype type-alias

我想知道做这样的事情是否不好?

data Alignment = LeftAl | CenterAl | RightAl
type Delimiter = Char
type Width     = Int

setW :: Width -> Alignment -> Delimiter -> String -> String
Run Code Online (Sandbox Code Playgroud)

而不是像这样:

setW :: Int -> Char -> Char -> String -> String
Run Code Online (Sandbox Code Playgroud)

我确实知道,有效地重构这些类型什么也没做,只是占用几行以换取更清晰的代码。但是,如果我将类型Delimiter用于多种功能,那么对于导入此模块或稍后阅读代码的人来说,这将更加清晰。

我是Haskell的新手,所以我不知道这种类型的好方法是什么。如果这不是一个好主意,或者有一些可以提高清晰度的方法是可取的,那将是什么?

She*_*rsh 20

您使用的是类型别名,它们对代码可读性的帮助很小。但是,最好使用newtype而不是type更好的类型安全性。像这样:

data Alignment = LeftAl | CenterAl | RightAl
newtype Delimiter = Delimiter { unDelimiter :: Char }
newtype Width     = Width { unWidth :: Int }

setW :: Width -> Alignment -> Delimiter -> String -> String
Run Code Online (Sandbox Code Playgroud)

您将处理的额外包装和展开newtype。但是该代码在进行进一步重构时会更强大。该样式指南建议type仅用于特殊的多态类型。

  • 值得指出的是,对`unDelimiter`和`unWidth`的调用是“零成本”的,因为它们仅是类型检查所必需的。在运行时没有实际的调用。 (8认同)
  • @chepner`X`至少部分是出于历史原因而存在。几年前,“ Data.Void”不在* base *中,因此使用* Void`必须依赖* void *包。在某一点。* pipes *通过滚动自己的依赖消除了依赖。当“ Data.Void”成为基础时,“ X”成为同义词。 (4认同)
  • 当值得使用类型别名*时,可能还值得一提,我认为这是给monad堆栈之类的东西起一个简短的名字(例如,类型App = ReaderT SomeEnvironment IO())。 (3认同)
  • 也就是说,我的一般观点是,初学者不应该使用类型同义词。 (2认同)

Mar*_*ann 14

我不会考虑这种不好的形式,但是显然,我并不代表整个Haskell社区。据我所知,语言功能是为特定目的而存在的:使代码更易于阅读。

可以在各种“核心”库中找到使用类型别名的示例。例如,Read该类定义此方法:

readList :: ReadS [a]
Run Code Online (Sandbox Code Playgroud)

ReadS类型仅仅是一个类型别名

type ReadS a = String -> [(a, String)]
Run Code Online (Sandbox Code Playgroud)

另一个示例是中的Forest类型Data.Tree

type Forest a = [Tree a]
Run Code Online (Sandbox Code Playgroud)

正如Shersh指出的,您还可以在newtype声明中包装新类型。如果您需要以某种方式(例如,使用智能构造函数)以某种方式约束原始类型,或者想要在不创建孤立实例的Arbitrary情况下向该类型添加功能(通常的示例是将QuickCheck 实例定义为不包含该类型的类型),则通常这很有用否则会出现这种情况)。

  • 我支持你的意见。即使`type`defs没有提供任何额外的typechecker保证`newtype`一样,它们还是一种快速整理签名的有用方法。这在原型设计阶段特别有用–无需编写任何样板代码即可快速汇总所需的类型(以及最终可能确实不需要的类型)。但是,因为它已经是一个可区分的_name_了,所以您以后可以在编译器上轻松地将定义更改为适当的“ newtype”,这将使修改代码变得容易。 (6认同)

dan*_*iaz 10

使用newtype-创建具有与基础类型相同的表示形式但不能替代基础类型的新类型-被认为是好的形式。这是避免原始痴迷的廉价方法,并且对于Haskell尤其有用,因为在Haskell中,函数参数的名称在签名中不可见。

新类型也可以是悬挂有用的类型类实例的地方。

鉴于新类型在Haskell中无处不在,随着时间的流逝,该语言获得了一些操纵它们的工具和习惯用法:

  • 可强制的“魔术”类型类,当newtype构造函数在作用域内时,它可以简化新类型及其基础类型之间的转换。通常在函数实现中避免样板很有用。

    ghci> coerce (Sum (5::Int)) :: Int

    ghci> coerce [Sum (5::Int)] :: [Int]

    ghci> coerce ((+) :: Int -> Int -> Int) :: Identity Int -> Identity Int -> Identity Int

  • ala。一个惯用语(在各种程序包中实现),简化了我们可能希望与之类的函数一起使用的新类型的选择foldMap

    ala Sum foldMap [1,2,3,4 :: Int] :: Int

  • GeneralizedNewtypeDeriving。基于基础类型中可用实例的自动派生新类型实例的扩展。

  • DerivingVia一个更通用的扩展,用于基于某些其他具有相同基础类型的新类型中可用的实例,为您的新类型自动派生实例。


dup*_*ode 6

需要注意的重要一件事是,Alignmentvs Char不仅仅是一个清晰性问题,而是正确性之一。您的Alignment类型表示一个事实,即只有三个有效的对齐方式,而不管有多少居民Char。通过使用它,您可以避免使用无效的值和运算带来麻烦,并且还可以通过启用警告功能,使GHC告知您有关不完整的模式匹配的信息。

至于同义词,意见不一。就我个人而言,我认为type小类型的同义词Int可以通过使您针对严格相同的事物跟踪不同的名称来增加认知负荷。就是说,leftaboutabout的意义很重要,因为这种同义词在解决方案原型设计的早期阶段很有用,当您不必担心要为您的域采用的具体表示形式的细节时对象。

(值得一提的是,这里的说法type基本上并不适用newtype。但是用例却有所不同:用例虽然type只是为同一事物引入了不同的名称,但实际上却newtype引入了另一事物。这可能是一个出奇的强大举动。 -请参阅danidiaz的答案以进行进一步讨论。)