유용성 / 유지 보수성을 위해 더 나은 디자인이 더 나은 것이 무엇인지, 그리고 커뮤니티와 잘 어울리는 것이 더 좋은지 궁금합니다.
주어진 데이터 모델 :
type Name = String
data Amount = Out | Some | Enough | Plenty deriving (Show, Eq)
data Container = Container Name deriving (Show, Eq)
data Category = Category Name deriving (Show, Eq)
data Store = Store Name [Category] deriving (Show, Eq)
data Item = Item Name Container Category Amount Store deriving Show
instance Eq (Item) where
(==) i1 i2 = (getItemName i1) == (getItemName i2)
data User = User Name [Container] [Category] [Store] [Item] deriving Show
instance Eq (User) where
(==) u1 u2 = (getName u1) == (getName u2)
예를 들어 항목이나 상점 등을 추가하여 사용자를 변환하기 위해 모나 딕 함수를 구현할 수 있지만 유효하지 않은 사용자가 생길 수 있으므로 모나드 함수는 사용자가 만들고 생성하는 사용자를 확인해야합니다.
그래서 나는 단지 :
- 오류 모나드에 싸서 모나드 함수가 유효성 검사를 실행하도록하십시오.
- 오류 모나드에 싸서 소비자가 적절한 오류 응답을 던지는 순서로 모나드 유효성 검사 함수를 바인딩하도록합니다 (따라서 유효하지 않은 사용자 개체를 확인하지 않고 선택할 수 있습니다)
- 실제로 사용자의 바인드 인스턴스에 빌드하여 모든 바인드마다 자동으로 유효성 검사를 실행하는 자체 오류 모나드를 효과적으로 만듭니다.
세 가지 접근 방식 각각에 대한 긍정과 부정을 볼 수 있지만이 시나리오에서 커뮤니티가 더 일반적으로 수행하는 작업을 알고 싶습니다.
따라서 코드 용어에서 옵션 1과 같습니다.
addStore s (User n1 c1 c2 s1 i1) = validate $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ someUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"]
옵션 2 :
addStore s (User n1 c1 c2 s1 i1) = Right $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ Right someUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"] >>= validate
-- in this choice, the validation could be pushed off to last possible moment (like inside updateUsersTable before db gets updated)
옵션 3 :
data ValidUser u = ValidUser u | InvalidUser u
instance Monad ValidUser where
(>>=) (ValidUser u) f = case return u of (ValidUser x) -> return f x; (InvalidUser y) -> return y
(>>=) (InvalidUser u) f = InvalidUser u
return u = validate u
addStore (Store s, User u, ValidUser vu) => s -> u -> vu
addStore s (User n1 c1 c2 s1 i1) = return $ User n1 c1 c2 (s:s1) i1
updateUsersTable $ someValidUser >>= addStore $ Store "yay" ["category that doesnt exist, invalid argh"]
답변
주먹 나 자신에게 묻습니다 : 잘못된 User
코드 버그가 있거나 정상적으로 발생할 수있는 상황 (예 : 누군가가 응용 프로그램에 잘못된 입력을 입력 한 경우)이 있습니다. 버그 인 경우 스마트 생성자를 사용 하거나보다 정교한 유형을 만드는 등의 일이 발생하지 않도록 노력하고 있습니다 .
유효한 시나리오 인 경우 런타임 중 일부 오류 처리가 적합합니다. 그런 다음 묻습니다. a User
가 유효하지 않다는 것이 실제로 무엇을 의미 합니까?
- 무효 인 경우
User
일부 코드가 실패 할 수 있습니까? 코드의 일부User
가 항상 유효 하다는 사실에 의존 합니까? - 아니면 나중에 수정해야하지만 계산 중에 아무것도 깨지지 않는 불일치가 있음을 의미합니까?
그것이 1이라면, 분명히 어떤 종류의 에러 모나드 (표준 또는 자신의)를 사용해야합니다. 그렇지 않으면 코드가 제대로 작동한다는 보장을 잃을 것입니다.
자신의 모나드를 만들거나 모나드 변압기 스택을 사용하는 것도 또 다른 문제입니다. 아마도 도움이 될 것입니다. 누군가 야생에서 모나드 트랜스포머를 만난 적이 있습니까? .
업데이트 : 확장 된 옵션 살펴보기 :
-
가장 좋은 방법으로 보입니다. 아마도 실제로 안전하기 위해서는 생성자를 숨기고
User
잘못된 인스턴스를 만들 수없는 몇 가지 함수 만 내보내는 것이 좋습니다. 이렇게하면 언제라도 올바르게 처리되는지 확인할 수 있습니다. 예를 들어,를 만드는 일반적인 함수는User
다음과 같습니다.user :: ... -> Either YourErrorType User -- more generic: user :: (MonadError YourErrorType m) ... -> m User -- Or if you actually don't need to differentiate errors: user :: ... -> Maybe User -- or more generic: user :: (MonadPlus m) ... -> m User -- etc.
많은 도서관은 예를 들어, 비슷한 appropach을
Map
,Set
또는Seq
그들의 불변 순종하지 않는 구조를 만들 수는 없습니다 있도록 기본이되는 구현을 숨 깁니다. -
검증을 최후까지 연기하고
Right ...
어디에서나 사용 하는 경우 모나드가 더 이상 필요하지 않습니다. 순수한 계산을 수행하고 마지막에 가능한 오류를 해결할 수 있습니다. IMHO이 접근 방식은 계산이 빨리 중단되지 않았기 때문에 유효하지 않은 사용자 값으로 인해 다른 곳에 유효하지 않은 데이터가있을 수 있으므로 매우 위험합니다. 그리고 다른 방법이 사용자를 업데이트하여 다시 유효하게되면 어딘가에 유효하지 않은 데이터가 있고 알지 못합니다. -
여기에는 몇 가지 문제가 있습니다.
- 가장 중요한 것은 모나드가뿐만 아니라 모든 유형 매개 변수를 수용해야한다는 것
User
입니다. 따라서에 대한 제한없이validate
유형을 가져야합니다 . 따라서 완전히 polymorhpic해야 하기 때문에의 입력을 검증하는 모나드를 작성할 수 없습니다 .u -> ValidUser u
u
return
return
-
다음으로, 내가 이해하지 못하는 것은
case return u of
의 정의에서 일치한다는 것 입니다>>=
. 주된 요점은ValidUser
유효 값과 유효하지 않은 값을 구별하는 것이므로 모나드는 이것이 항상 사실인지 확인해야합니다. 간단하게(>>=) (ValidUser u) f = f u (>>=) (InvalidUser u) f = InvalidUser u
그리고 이것은 이미 매우 비슷
Either
합니다. - 가장 중요한 것은 모나드가뿐만 아니라 모든 유형 매개 변수를 수용해야한다는 것
일반적으로 사용자 정의 모나드를 사용하는 경우에만
- 필요한 기능을 제공하는 기존 모나드는 없습니다. 기존 모나드는 일반적으로 많은 지원 기능을 가지고 있으며 더 중요한 것은 모나드 변환기를 가지고있어 모나드 스택으로 구성 할 수 있습니다.
- 또는 모나드 스택으로 설명하기에는 너무 복잡한 모나드가 필요한 경우.