Haskell 要么是单子,要么不是?
在此代码中:Haskell 要么是单子,要么不是?,haskell,either,Haskell,Either,在此代码中: data LatLngPoint = LatLngPoint { latitude :: Double , longitude :: Double , height :: Double } data LatLng = LatLng { point :: LatLngPoint
data LatLngPoint = LatLngPoint { latitude :: Double
, longitude :: Double
, height :: Double
}
data LatLng = LatLng { point :: LatLngPoint
, datum :: Datum
}
data LatitudeDMS = North DMSPoint | South DMSPoint
data LongitudeDMS = East DMSPoint | West DMSPoint
data DMSPoint = DMSPoint { degrees :: Double
, minutes :: Double
, seconds :: Double
}
mkLatLngPoint :: LatitudeDMS -> LongitudeDMS -> Datum -> Either String LatLng
mkLatLngPoint lat lng dtm =
case evalLatitude lat of
Nothing -> Left "Invalid latitude"
Just lt -> case evalLongitude lng of
Nothing -> Left "Invalid longitude"
Just ln -> let p = LatLngPoint { latitude = lt , longitude = ln, height = 0 }
in Right LatLng { point = p , datum = dtm }
where evalLatitude :: LatitudeDMS -> Maybe Double
evalLatitude (North p) = dmsToLatLngPoint p 1
evalLatitude (South p) = dmsToLatLngPoint p (-1)
evalLongitude :: LongitudeDMS -> Maybe Double
evalLongitude (East p) = dmsToLatLngPoint p 1
evalLongitude (West p) = dmsToLatLngPoint p (-1)
dmsToLatLngPoint :: DMSPoint -> Double -> Maybe Double
dmsToLatLngPoint DMSPoint { degrees = d, minutes = m, seconds = s } cardinal
| d + m + s < 90 = Nothing
| otherwise = Just (cardinal * (d + m + s / 324.9))
但我认为这是丑陋和冗长的
如何改进代码(包括更改类型数据)为此,我将使用orMonad
——它更好地传达了您的功能意图:必须成功执行evalLatitude lat
和evalLongitude lng
,否则您将失败并显示错误消息
import Control.Monad.Except
mkLatLngPoint :: LatitudeDMS -> LongitudeDMS -> Datum -> Except String LatLng
mkLatLngPoint lat lng dtm = do
lt <- withExcept (const "Invalid latitude") evalLatitude lat
ln <- withExcept (const "Invalid longitude") evalLongitude lng
let p = LatLngPoint { latitude = lt , longitude = ln, height = 0 }
pure (LatLng { point = p , datum = dtm })
where evalLatitude :: LatitudeDMS -> Except String Double
evalLatitude (North p) = dmsToLatLngPoint p 1
evalLatitude (South p) = dmsToLatLngPoint p (-1)
evalLongitude :: LongitudeDMS -> Except String Double
evalLongitude (East p) = dmsToLatLngPoint p 1
evalLongitude (West p) = dmsToLatLngPoint p (-1)
dmsToLatLngPoint :: DMSPoint -> Double -> Except String Double
dmsToLatLngPoint DMSPoint { degrees = d, minutes = m, seconds = s } cardinal
| d + m + s < 90 = throwError "Invalid point"
| otherwise = pure (cardinal * (d + m + s / 324.9))
import Control.Monad.Except
mkLatLngPoint::纬度->经度->基准->除字符串纬度
mkLatLngPoint lat lng dtm=do
除了字符串双精度
评估方向(东p)=终点p 1
评估高度(西p)=DMS至地图点p(-1)
dmsToLatLngPoint::DMSPoint->Double->字符串双精度除外
dmstolatlingpoint DMSPoint{degrees=d,minutes=m,seconds=s}基数
|d+m+s<90=投掷者“无效点”
|否则=纯(基数*(d+m+s/324.9))
请注意,无论是此解决方案还是您的
案例
解决方案,其计算结果都不会超出他们的需要:只要其中一个失败,函数就可以作为一个整体失败(对于您的案例,请记住Haskell是懒惰的!)。我看到已经有一个公认的答案,但只是给出了另一个解决方案(尽管非常类似)。遵循这里概述的指导原则:(无论哪种方式阅读都是一件好事),您都会得到这样的结果
import Control.Monad.Catch
data LatitudeException = LatitudeException
instance Show LatitudeException where
show LatitudeException = "Invalid Latitude"
instance Exception LatitudeException
data LongitudeException = LongitudeException
instance Show LongitudeException where
show LongitudeException = "Invalid Longitude"
instance Exception LongitudeException
mkLatLngPoint :: (MonadThrow m) => LatitudeDMS -> LongitudeDMS -> Datum -> m LatLng
mkLatLngPoint lat lng dtm = do
lt <- evalLatitude lat
ln <- evalLongitude lng
let p = LatLngPoint { latitude = lt , longitude = ln, height = 0 }
return $ LatLng { point = p , datum = dtm }
where evalLatitude :: (MonadThrow m) => LatitudeDMS -> m Double
evalLatitude (North p) = case dmsToLatLngPoint p 1 of
(Just d) -> return d
Nothing -> throwM LatitudeException
evalLatitude (South p) = case dmsToLatLngPoint p (-1) of
(Just d) -> return d
Nothing -> throwM LatitudeException
evalLongitude :: (MonadThrow m) => LongitudeDMS -> m Double
evalLongitude (East p) = case dmsToLatLngPoint p 1 of
(Just d) -> return d
Nothing -> throwM LongitudeException
evalLongitude (West p) = case dmsToLatLngPoint p (-1) of
(Just d) -> return d
Nothing -> throwM LongitudeException
dmsToLatLngPoint :: DMSPoint -> Double -> Maybe Double
dmsToLatLngPoint DMSPoint { degrees = d, minutes = m, seconds = s } cardinal
| d + m + s < 90 = Nothing
| otherwise = Just (cardinal * (d + m + s / 324.9))
import Control.Monad.Catch
数据LatitudeException=LatitudeException
实例显示LatitudeException,其中
显示纬度异常=“无效纬度”
实例异常LatitudeException
数据纵向异常=纵向异常
实例Show LongitudeException,其中
show LongitudeException=“无效的经度”
实例异常LongitudeException
mkLatLngPoint::(单轴m)=>相对坐标->纵向坐标->基准坐标->单轴m坐标
mkLatLngPoint lat lng dtm=do
我是双人
蒸发量(北p)=第1种情况
(仅d)->返回d
Nothing->throwM LatitudeException
蒸发量(南p)=情况DMSTOLLATLINGPOINT p(-1)
(仅d)->返回d
Nothing->throwM LatitudeException
评估方向::(单箭头m)=>纵向方向->双箭头m
evalLongitude(东p)=案例DMSTOLLATLINGPOINT p 1
(仅d)->返回d
Nothing->throwM LongitudeException
评估方向(西p)=案例DMS至地图点p(-1)
(仅d)->返回d
Nothing->throwM LongitudeException
dmsToLatLngPoint::DMSPoint->Double->可能是Double
dmstolatlingpoint DMSPoint{degrees=d,minutes=m,seconds=s}基数
|d+m+s<90=无
|否则=仅(基数*(d+m+s/324.9))
肯定还有更多的样板要处理,但提供了更多的灵活性。看看这篇文章,看看它对你的情况是否有好处。除了与
几乎相同之外,不是吗exceptema
与m(ea)
有不同的实例,但是exceptemidentitya
应该等同于ea
,我相信。@dfeuer完全是。我更喜欢使用它,因为它的名字表达了可能有例外的意图。尽管如此,值得指出的是,上述代码中除
之外的所有都可以替换为或
,而无需更改任何其他内容。谢谢,看起来更简洁、更简单。谢谢你的回答。这个链接很有趣,我打算读一下。正如您所说的,代码看起来更加冗长和“重复”,但这种方法很有趣。
import Control.Monad.Catch
data LatitudeException = LatitudeException
instance Show LatitudeException where
show LatitudeException = "Invalid Latitude"
instance Exception LatitudeException
data LongitudeException = LongitudeException
instance Show LongitudeException where
show LongitudeException = "Invalid Longitude"
instance Exception LongitudeException
mkLatLngPoint :: (MonadThrow m) => LatitudeDMS -> LongitudeDMS -> Datum -> m LatLng
mkLatLngPoint lat lng dtm = do
lt <- evalLatitude lat
ln <- evalLongitude lng
let p = LatLngPoint { latitude = lt , longitude = ln, height = 0 }
return $ LatLng { point = p , datum = dtm }
where evalLatitude :: (MonadThrow m) => LatitudeDMS -> m Double
evalLatitude (North p) = case dmsToLatLngPoint p 1 of
(Just d) -> return d
Nothing -> throwM LatitudeException
evalLatitude (South p) = case dmsToLatLngPoint p (-1) of
(Just d) -> return d
Nothing -> throwM LatitudeException
evalLongitude :: (MonadThrow m) => LongitudeDMS -> m Double
evalLongitude (East p) = case dmsToLatLngPoint p 1 of
(Just d) -> return d
Nothing -> throwM LongitudeException
evalLongitude (West p) = case dmsToLatLngPoint p (-1) of
(Just d) -> return d
Nothing -> throwM LongitudeException
dmsToLatLngPoint :: DMSPoint -> Double -> Maybe Double
dmsToLatLngPoint DMSPoint { degrees = d, minutes = m, seconds = s } cardinal
| d + m + s < 90 = Nothing
| otherwise = Just (cardinal * (d + m + s / 324.9))