Alt for monadic function is missing

Hi,

I would like to have a set of composable form validators and almost get it.

import Prelude
import Data.Either
import Data.Maybe
import Control.Alt

validateInt :: String -> Either String Int
validateInt i = note ("Expected Int but got " <> i) $ Int.fromString i

validatePositive :: Int -> Either String Int
validatePositive i = 
  if i < 0 
  then Left $ show i <> " is negative" 
  else Right i

mapStrConst :: String -> a -> String -> Either String a
mapStrConst expected rv got
  | expected == got = Right rv
  | otherwise = Left $ "Expected [" <> expected <> "] bot got [" <> got <> "]"
  
validateAge = 
  mapStrConst "forever" (-1) <|> (validateInt >=> validatePositive)

there only issue is there is no Alt instance for (->) (m b)

I think I implemented the instance:

instance altFun :: (Alt m) => Alt ((->) (m b)) where
  alt f g = \a -> f a <|> g a

BUT PureScript doesn’t allow orphan instances!!!

So there is dilemma:

  1. wrap all validator functions in newtype and implement Alt for it
  2. define class AltNoFunctor validator function type

Option 1:

import Data.Bifunctor

newtype Convertor a b = Convertor (a -> (Either String b))

instance convertorFunctor :: Functor (Convertor c) where
  map f (Convertor x) = Convertor $ \a -> rmap f (x a)
  
instance altConvertor :: Alt (Convertor a) where
  alt (Convertor f) (Convertor g) = Convertor \a -> f a <|> g a
  
runConvertor :: forall a b. a -> Convertor a b -> Either String b
runConvertor i (Convertor c) = c i

Option 2.

class AltNoFunctor :: (Type -> Type) -> Constraint
class AltNoFunctor f where
  altNoF :: forall a. f a -> f a -> f a

type Convertor b a = a -> Either String b

instance altNoFunctorEitherString :: AltNoFunctor (Convertor b) where
  altNoF f g = \x -> f x <|> g x

but type is parially applied

option 3: just function:

type Convertor b a = a -> Either String b

altConvertor ::
  forall a b.
  Convertor b a ->
  Convertor b a ->
  Convertor b a
altConvertor f g = \x -> f x <|> g x

Option 3 seems to be the easiest → concise , but it smell as a hack

Option 4:

validateAge = 
  lift2 (<|>) (mapStrConst "forever" (-1)) (validateInt >=> validatePositive)

Or, if you prefer, Option 1 is available to you through Control.Monad.Reader:

validateAge = runReader $
  (asks $ mapStrConst "forever" (-1)) <|> (asks $ validateInt >=> validatePositive)
2 Likes