haskell - Advice on writing monadic signatures -


consider following example functions both add random value pure input:

addrand1 :: (monadrandom m) => m (int -> int)  addrand2 :: (monadrandom m) => int -> m int -- *can* written m (int -> int) 

it easy convert addrand1 function same signature addrand2, but not vice versa.

to me, provides strong evidence should write addrand1 on addrand2. in example, addrand1 has more truthful/general type, typically captures important abstractions in haskell.

while having "right" signature seems crucial aspect of functional programming, have lot of practical reasons why addrand2 might better signature, if could written addrand1s signature.

  1. with interfaces:

    class fakemonadrandom m   getrandom :: (random a, num a) => m   getrandomr1 :: (random a, num a) => (a,a) -> m   getrandomr2 :: (random a, num a) => m ((a,a) -> a) 

    suddenly getrandomr1 seems "more general" in sense permits more instances (that repeatedly call getrandom until result in range, example) compared getrandomr2, seems require sort of reduction technique.

  2. addrand2 easier write/read:

    addrand1 :: (monadrandom m) => m (int -> int) addrand1 =   x <- getrandom   return (+x) -- in general requires `return $ \a -> ...`  addrand2 :: (monadrandom m) => int -> m int addrand2 = (a+) <$> getrandom 
  3. addrand2 easier use:

    foo :: (monadrandom m) => m int foo =   x <- addrand1 <*> (pure 3) -- ugly syntax   f <- addrand1              -- or 2 step process: sequence function, apply   x' <- addrand2 3           -- easy!   return $ (f 3) + x + x' 
  4. addrand2 harder misuse: consider getrandomr :: (monadrandom m, random a) => (a,a) -> m a. given range, can sample repeatedly , different results, intend. however, if instead have getrandomr :: (monadrandom m, random a) => m ((a,a) -> a), might tempted write

    do   f <- getrandomr   return $ replicate 20 $ f (-10,10) 

    but surprised result!

i'm feeling conflicted how write monadic code. "version 2" seems better in many cases, came across example "version 1" signature required.*

what sort of factors should influence design decisions w.r.t. monadic signatures? there way reconcile apparently conflicting goals of "general signatures" , "natural, clean, easy-to-use, hard-to-misuse syntax"?

*: wrote function foo :: -> m b, worked fine (literally) many years. when tried incorporate new application (dsl hoas), discovered unable to, until realized foo rewritten have signature m (a -> b). new application possible.

this depends on multiple factors:

  • what signatures possible (here both).
  • what signatures convenient use.
  • or more generally, if want have general interface or dually general implementations.

the key understanding difference between int -> m int , m (int -> int) in former case, effect (m ...) can depend on input argument. example, if m io, have function launches n missiles, n function argument. on other hand, effect in m (int -> int) doesn't depend on - effect doesn't "see" argument of function returns.

coming case: take pure input, generate random number , add input. can see effect (generating random number) doesn't depend on input. why can have signature m (int -> int). if task instead generate n random numbers, example, signature int -> m [int] work, m (int -> [int]) wouldn't.

regarding usability, int -> m int more common in monadic context, because monadic combinators expect signatures of form a -> b -> ... -> m r. example, you'd write

getrandom >>= addrand2 

or

addrand2 =<< getrandom 

to add random number random number.

signatures m (int -> int) less common monads, used applicative functors (each monad applicative functor), effects can't depend on parameters. in particular, operator <*> work nicely here:

addrand1 <*> getrandom 

regarding generality, signature influences how difficult use or implement it. observed, addrand1 more general caller's perspective - can convert addrand2 if needed. on other hand, addrand2 less general, therefore easier implement. in case doesn't apply, in cases might happen it's possible implement signature m (int -> int), not int -> m int. reflected in type hierarchy - monads more specific applicative functors, means give more power user, "harder" implement - every monad applicative, not every applicative can made monad.


Comments

Popular posts from this blog

Command prompt result in label. Python 2.7 -

javascript - How do I use URL parameters to change link href on page? -

amazon web services - AWS Route53 Trying To Get Site To Resolve To www -