유형 안전성과 관련하여 Haskell 유형 대 newtype
나는 Haskell에서 newtype
더 자주 비교된다는 것을 알고 data
있지만, 나는 기술적 인 문제 라기보다는 디자인 관점에서 더 많은 비교를하고 있습니다.
명령형 / OO 언어에는 반 패턴 " 원시 집착 "이 있습니다. 여기서 원시 유형의 다작 사용은 프로그램의 유형 안전성을 감소시키고 다른 목적으로 의도 된 동일한 유형 값의 우연히 상호 교환 성을 도입합니다. 예를 들어, 많은 것들이 문자열이 될 수 있지만, 컴파일러가 정적으로 우리가 이름을 의미하고 주소에서 도시를 의미하는 것을 알 수 있다면 좋을 것입니다.
그렇다면 Haskell 프로그래머는 newtype
프리미티브 값에 유형을 구별하기 위해 얼마나 자주 사용 합니까? 를 사용 type
하면 별칭 이 도입되고 프로그램의 가독성이 더 명확 해지지 만 실수로 값이 교환되는 것을 방지 할 수는 없습니다. 하스켈을 배우면서 유형 시스템이 내가 본 것만 큼 강력하다는 것을 알았습니다. 따라서 나는 이것이 자연스럽고 일반적인 관행이라고 생각하지만, newtype
이 관점에서 사용에 대한 논의를 많이 보지 못했습니다 .
물론 많은 프로그래머들은 다르게 일을하지만 이것은 하스켈에서 흔합니까?
newtypes의 주요 용도는 다음과 같습니다.
- 유형에 대한 대체 인스턴스를 정의합니다.
- 선적 서류 비치.
- 데이터 / 형식 정확성 보장.
저는 지금 newtypes를 광범위하게 사용하는 응용 프로그램을 작업 중입니다. newtypes
Haskell에서는 순전히 컴파일 타임 개념입니다. 예를 들어 아래의 unwrappers unFilename (Filename "x")
를 사용하여 "x"와 동일한 코드로 컴파일합니다. 런타임 적중이 전혀 없습니다. 와이 data
유형. 이것은 위에 나열된 목표를 달성하는 매우 좋은 방법입니다.
-- | A file name (not a file path).
newtype Filename = Filename { unFilename :: String }
deriving (Show,Eq)
실수로 이것을 파일 경로로 취급하고 싶지 않습니다. 파일 경로가 아닙니다. 데이터베이스 어딘가에 개념적 파일의 이름입니다.
알고리즘이 올바른 것을 참조하는 것은 매우 중요합니다. newtypes는이를 도와줍니다. 또한 보안을 위해 매우 중요합니다. 예를 들어 웹 애플리케이션에 파일 업로드를 고려합니다. 다음과 같은 유형이 있습니다.
-- | A sanitized (safe) filename.
newtype SanitizedFilename =
SanitizedFilename { unSafe :: String } deriving Show
-- | Unique, sanitized filename.
newtype UniqueFilename =
UniqueFilename { unUnique :: SanitizedFilename } deriving Show
-- | An uploaded file.
data File = File {
file_name :: String -- ^ Uploaded file.
,file_location :: UniqueFilename -- ^ Saved location.
,file_type :: String -- ^ File type.
} deriving (Show)
업로드 된 파일에서 파일 이름을 정리하는이 함수가 있다고 가정합니다.
-- | Sanitize a filename for saving to upload directory.
sanitizeFilename :: String -- ^ Arbitrary filename.
-> SanitizedFilename -- ^ Sanitized filename.
sanitizeFilename = SanitizedFilename . filter ok where
ok c = isDigit c || isLetter c || elem c "-_."
이제 고유 한 파일 이름을 생성합니다.
-- | Generate a unique filename.
uniqueFilename :: SanitizedFilename -- ^ Sanitized filename.
-> IO UniqueFilename -- ^ Unique filename.
임의의 파일 이름에서 고유 한 파일 이름을 생성하는 것은 위험하므로 먼저 삭제해야합니다. 마찬가지로, 고유 한 파일 이름은 항상 확장자에 의해 안전합니다. 이제 파일을 디스크에 저장하고 원하는 경우 해당 파일 이름을 데이터베이스에 저장할 수 있습니다.
그러나 많은 포장 / 포장을 풀어야하는 것도 짜증날 수 있습니다. 장기적으로 볼 때 특히 가치 불일치를 피하기 위해 그만한 가치가 있다고 생각합니다. ViewPatterns는 다음과 같은 도움이됩니다.
-- | Get the form fields for a form.
formFields :: ConferenceId -> Controller [Field]
formFields (unConferenceId -> cid) = getFields where
... code using cid ..
아마도 당신은 그것을 함수에서 풀어내는 것이 문제라고 말할 것입니다. 만약 당신 cid
이 함수에 잘못 전달한다면 ? 문제는 아니지만 회의 ID를 사용하는 모든 기능은 ConferenceId 유형을 사용합니다. 등장하는 것은 컴파일 타임에 강제되는 일종의 기능 대 기능 수준 계약 시스템입니다. 꽤 좋은. 그래서 예, 특히 큰 시스템에서 가능한 한 자주 사용합니다.
I think this is mostly a matter of the situation.
Consider pathnames. The standard prelude has "type FilePath = String" because, as a matter of convenience, you want to have access to all the string and list operations. If you had "newtype FilePath = FilePath String" then you would need filePathLength, filePathMap and so on, or else you would forever be using conversion functions.
On the other hand, consider SQL queries. SQL injection is a common security hole, so it makes sense to have something like
newtype Query = Query String
and then add extra functions that will convert a string into a query (or query fragment) by escaping quote characters, or fill in blanks in a template in the same way. That way you can't accidentally convert a user parameter into a query without going through the quote escaping function.
For simple X = Y
declarations, type
is documentation; newtype
is type checking; this is why newtype
is compared to data
.
I fairly frequently use newtype
for just the purpose you describe: ensuring that something which is stored (and often manipulated) in the same way as another type is not confused with something else. In that way it works just as a slightly more efficient data
declaration; there's no particular reason to chose one over the other. Note that with GHC's GeneralizedNewtypeDeriving
extension, for either you can automatically derive classes such as Num
, allowing your temperatures or yen to be added and subtracted just as you can with the Int
s or whatever lies beneath them. One wants to be a little bit careful with this, however; typically one doesn't multiply a temperature by another temperature!
For an idea of how often these things are used, In one reasonably large project I'm working on right now, I have about 122 uses of data
, 39 uses of newtype
, and 96 uses of type
.
But the ratio, as far as "simple" types are concerned, is a bit closer than that demonstrates, because 32 of those 96 uses of type
are actually aliases for function types, such as
type PlotDataGen t = PlotSeries t -> [String]
You'll note two extra complexities here: first, it's actually a function type, not just a simple X = Y
alias, and second that it's parameterized: PlotDataGen
is a type constructor that I apply to another type to create a new type, such as PlotDataGen (Int,Double)
. When you start doing this kind of thing, type
is no longer just documentation, but is actually a function, though at the type level rather than the data level.
newtype
is occasionally used where type
can't be, such as where a recursive type definition is necessary, but I find this to be reasonably rare. So it looks like, on this particular project at least, about 40% of my "primitive" type definitions are newtype
s and 60% are type
s. Several of the newtype
definitions used to be types, and were definitely converted for the exact reasons you mentioned.
So in short, yes, this is a frequent idiom.
I think it is quite common to use newtype
for type distinctions. In many cases this is because you want to give different type class instances, or to hide implementations, but simply wanting to protect against accidental conversions is also an obvious reason to do it.
참고URL : https://stackoverflow.com/questions/991467/haskell-type-vs-newtype-with-respect-to-type-safety
'Development Tip' 카테고리의 다른 글
멀티 스레딩 문제를 감지하고 디버깅하는 방법은 무엇입니까? (0) | 2020.11.24 |
---|---|
C #에서 한 번만 속성을 설정하는 방법이 있습니까? (0) | 2020.11.24 |
PostgreSQL에서 행이 업데이트 될 때 타임 스탬프 업데이트 (0) | 2020.11.24 |
SortedList를 사용하는 경우 (0) | 2020.11.24 |
목록의 요소를 별도의 블록으로 끌어서 놓기 (0) | 2020.11.24 |