来自http://learnyouahaskell.com/making-our-own-types-and-typeclasses
data Person = Person { name :: String
, age :: Int
} deriving (Show)
在一个真实的应用程序中,使用像String和Int这样的原语作为名称和年龄将构成原始的痴迷,代码气味。 (显然,出生日期优于国际时代,但让我们忽略它)相反,人们更喜欢像
这样的东西newtype Person = Person { name :: Name
, age :: Age
} deriving (Show)
在OO语言中,这看起来像
class Person {
Name name;
Age age;
Person(Name name, Age age){
if (name == null || age == null)
throw IllegalArgumentException();
this.name = name;
this.age = age;
}
}
class Name extends String {
Name(String name){
if (name == null || name.isEmpty() || name.length() > 100)
throw IllegalArgumentException();
super(name);
}
}
class Age extends Integer {
Age(Integer age){
if (age == null || age < 0)
throw IllegalArgumentException();
super(age);
}
}
但在惯用的最佳实践Haskell中如何实现同样的目标?
答案 0 :(得分:8)
使Name
抽象并提供智能构造函数。这意味着您不导出Name
数据构造函数,而是提供Maybe
- 返回构造函数:
module Data.Name
( Name -- note: only export type, not data constructor
, fromString
, toString
) where
newtype Name = Name String
fromString :: String -> Maybe Name
fromString n | null n = Nothing
| length n > 100 = Nothing
| otherwise = Just (Name n)
toString :: Name -> String
toString (Name n) = n
现在无法在此模块之外构造错误长度的Name
值。
对于Age
,您可以执行相同的操作,或使用Data.Word
中的类型,或使用以下效率低但保证非负的表示形式:
data Age = Zero | PlusOne Age
答案 1 :(得分:2)
这可能是某些语言中的代码嗅觉,但它通常不被认为是Haskell中的一种。您必须选择名称和出生日期某处的具体表示,Person
的数据类型声明可能是最佳选择。在Haskell中,保持其他代码不依赖于名称表示的常用方法是使Person
抽象。不要公开Person
构造函数,而是公开用于创建,修改和检查Person
值的函数。