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))
但我认为这是丑陋和冗长的

如何改进代码(包括更改类型数据)

为此,我将使用or
Monad
——它更好地传达了您的功能意图:必须成功执行
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))