避免在Haskell中进行原始的痴迷

fre*_*red 2 primitive haskell types

来自http://learnyouahaskell.com/making-our-own-types-and-typeclasses

data Person = Person { name :: String  
                     , age :: Int  
                     } deriving (Show)   
Run Code Online (Sandbox Code Playgroud)

在一个真实的应用程序中,使用像String和Int这样的原语作为名称和年龄将构成原始的痴迷,代码气味.(显然,出生日期优于国际时代,但让我们忽略它)相反,人们更喜欢像

newtype Person = Person { name :: Name  
                        , age :: Age  
                        } deriving (Show)   
Run Code Online (Sandbox Code Playgroud)

在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);
  }
}
Run Code Online (Sandbox Code Playgroud)

但在惯用的最佳实践Haskell中如何实现同样的目标?

rig*_*old 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
Run Code Online (Sandbox Code Playgroud)

现在不可能Name在该模块之外构造错误长度的值.

因为Age,你可以做同样的事情,或使用类型Data.Word,或使用以下低效但有保证的非负表示:

data Age = Zero | PlusOne Age
Run Code Online (Sandbox Code Playgroud)