Could compiler lift primitives automatically to their monad context?

Hello all,
I am trying to figure out why does compiler need manual lifting from String to Monad String if it can detect that both work with String. We know that wraping String in pure is safe and it will result in Monad String. Why am I forced to do manual pure lifting instead of compiler doing it automatically ?

Lets say I have: (pseudo code)

transform :: String -> String
transform a = a

mtransform2 :: Maybe String -> String
mtransform2 (Just a) = a
mtransform2 Nothing = ""

transform >>> mtransform2 -- this will not work because String is not M String
-- there is no reason why concrete String type cannot be automatically 
-- changed to M String 

-- I am forced to do 
transform >>> pure >>> mtransform2

This logic can be applied to other monads too

why I cannot plug any String
to any function which has m String

func :: String -> String 
func a = a 

func1 :: Array String -> String
func1 x:xs = x
func1 [] = ''

func2 :: Effect String -> String 
func2  (Effect a) = a -- ?? dunno how to get value from this
func2 _ = '' 

func3 :: Either String -> String 
func3 (Right a) = a
func3 (Left _) = ''

-- so I want this 
func "string"
func1 "string" -- this should work
func1 ["string"] -- as well as this should work
func2 "string"
func3 "string" 
-- compiler knows context and 
-- can use pure when it needs to automatically 

etc. You get the point.

Now, why am I forced to create func
if I only want to create any of other Monad version of the function ?

So my question is. Why does compiler create such strict rules when it can perform lifting by itself.

Part of the pervasive issue with implicit type casting is that it makes so much sense when you’re designing it and then quickly becomes the feature that causes the most bugs in your language.

For example, most JavaScript and TypeScript linters don’t allow == (They insist on ===) because the implicit type conversions that the first equality check does ended up causing more headaches then it solved.

A separate issue is that this isn’t an abstraction that a compiler can perform for you to ease the cognitive load for a piece of code. You still have to know/understand what it means to lift a value into a monad. Monads don’t have an ‘unwrap’ method. String and M String aren’t the same type. You will still need to know the type, only now you’ll also need to know when the compiler behaves in such a way that you don’t care about the type.

For example: once you lift a value into a monad, you can no longer treat it as the same type. Lifting a String into a List yields a List String of length 1, no matter how long that String was.

You can use Type-Classes without understanding dictionary passing or how it works underneath. You can’t really have auto-lifting without still understanding the what/how/why/and when of what it’s doing.

It also doesn’t save you too much on ergonomics. Type-Classes make lifting into a Monad easy and consistent. It’s an few extra characters once in a while.

I don’t work on the compiler, so these are just guesses / my two cents :slight_smile:

dupl :: forall a. a -> Array a
dupl a = [a, a]

func :: Int -> Array (Array Int)
func x = dupl x -- error!

Where would you want the compiler to insert the pure in func? dupl (pure x) and pure (dupl x) are different values.


None of your functions have m of type as input argument, so I am bit confused.

dupl :: forall a. Array a -> a
dupl a:xs = a

func :: Array Int -> Int
func x = dupl x
-- ^ currently works
func2 :: Int -> Int
func2 x = dupl x  
-- ^ my idea  and question why we don't support that
func2 x = dupl (pure x) -- compiler version would end up being 

I am not saying we need to support this, more asking if there is technical / other reason why it is bad idea.

Does this clear things up?

takesMOfArrayOrIsItArrayOfM :: Array (Array Int) -> String
takesMOfArrayOrIsItArrayOfM _ = "doesn't matter"

dupl :: forall a. a -> Array a
dupl a = [a, a]

func x :: Int -> String
func x = takesMOfArrayOrIsItArrayOfM (dupl x) -- error

I’m trying to gesture at the technical reason with these examples: it’s very difficult, if not impossible, to reconcile implicit coercion with Hindley-Milner type inference, without either introducing ambiguous cases (one of which I’m trying to demonstrate here) or violating some principle we hold dear, like coherence or referential transparency. The specific examples you’re giving are all harmless, but try generalizing them to a universal rule and you’ll quickly start to see the pitfalls. Again, I’m only highlighting the simplest one of them here.