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

wrap all validator functions in newtype and implement Alt for it

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