在 Haskell 中避免原始痴迷
Avoiding primitive obsession in Haskell
来自http://learnyouahaskell.com/making-our-own-types-and-typeclasses
data Person = Person { name :: String
, age :: Int
} deriving (Show)
在实际应用程序中,使用像 String 和 Int 这样的原语来表示姓名和年龄会构成对原语的痴迷,一种代码味道。 (同样显然 Date born 比 Int age 更可取,但让我们忽略它)相反,人们更喜欢
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?
使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
这在某些语言中可能是代码异味,但在 Haskell 中通常不被认为是一种代码异味。您必须在 某处 选择名称和出生日期的具体表示,而 Person
的数据类型声明可能是最好的选择。在 Haskell 中,防止其他代码依赖名称表示的通常方法是使 Person
抽象。不要公开 Person
构造函数,而是公开用于创建、修改和检查 Person
值的函数。
来自http://learnyouahaskell.com/making-our-own-types-and-typeclasses
data Person = Person { name :: String
, age :: Int
} deriving (Show)
在实际应用程序中,使用像 String 和 Int 这样的原语来表示姓名和年龄会构成对原语的痴迷,一种代码味道。 (同样显然 Date born 比 Int age 更可取,但让我们忽略它)相反,人们更喜欢
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?
使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
这在某些语言中可能是代码异味,但在 Haskell 中通常不被认为是一种代码异味。您必须在 某处 选择名称和出生日期的具体表示,而 Person
的数据类型声明可能是最好的选择。在 Haskell 中,防止其他代码依赖名称表示的通常方法是使 Person
抽象。不要公开 Person
构造函数,而是公开用于创建、修改和检查 Person
值的函数。