Type inference issue

Hello,

While working on some logic on my current project, I came across a weird situation, where I have two functions with the same signature, one existing outside the context of a third function (“filterOutside”), and the second within that same context (“filterWithParam”).
The issue is as follows. When using the first example, using the outside function in the filter, I can correctly use it on both TextMaps as the the signature of the “filterForKei” function remains generic. In the second example, using the param function in the filter, I cannot use the “filterForKey” function in both TextMaps, as the function signature infers the first TextMap.
I would like to know what is causing this, as it is a bit confusing.

Thank you!

filterOutside : Text -> Bool
filterOutside _ = True

testFunc : (Text -> Bool) -> Text
testFunc filterWithParam = ""
    where
        textMap1 : TextMap Int        = fromList [("Test", 1)]
        textMap2 : TextMap (Int, Int) = fromList [("Test", (1, 2))]

        -- Filter using outside function
        filterForKei = filterWithKey (const . filterOutside)
        filtered1    = filterForKei textMap1
        filtered2    = filterForKei textMap2

        -- Filter using param function
        filterForKey = filterWithKey (const . filterWithParam)
        filtered3    = filterForKey textMap1
        filtered4    = filterForKey textMap2
2 Likes

This is due to a Haskell extension called MonoLocalBinds which is turned on by default in Daml.

By default, Haskell generalizes bindings that look like function (the fact that it doesn’t generalize non-functions is what is called the MonomorphismRestriction).

MonoLocalBinds restricts this a bit further. You can read the details in the linked documentation but let me summarize it for your example:

  1. filterForKei has only top-level definitions as free variables which are closed so it is generalized.
  2. filterForkey has filterWithParam as a free variable which is not closed so it is not generalized.

This is perhaps somewhat unintuitive. I’d generally recommend that whenever you run into confusing type inference issues, you add some more type signatures which are often sufficient to resolve the issue, e.g., if you add the type signature

filterForkey : TextMap a -> TextMap a

the issue disappears.

2 Likes

Actually in my testing I did have the signature as you described, but it didn’t solve the issue for me, so I didn’t think to included in the thread. But I’ll have a read and find some other way to resolve the issue.
Thank you!

1 Like

Here is a full example with the type signature that compiles:

module A where

import DA.TextMap

filterOutside : Text -> Bool
filterOutside _ = True

testFunc : (Text -> Bool) -> Text
testFunc filterWithParam = ""
    where
        textMap1 : TextMap Int        = fromList [("Test", 1)]
        textMap2 : TextMap (Int, Int) = fromList [("Test", (1, 2))]

        -- Filter using outside function
        filterForKei = filterWithKey (const . filterOutside)
        filtered1    = filterForKei textMap1
        filtered2    = filterForKei textMap2

        -- Filter using param function
        filterForKey : TextMap a -> TextMap a
        filterForKey = filterWithKey (const . filterWithParam)
        filtered3    = filterForKey textMap1
        filtered4    = filterForKey textMap2
1 Like

Ah, so you include an extra param to “absorb” the the future type on function usage, that is indeed peculiar.
Thanks again.

1 Like

Not sure what you mean by that. I didn’t modify the function at all. I just added a type signature to filterForKey

1 Like

Nevermind, in your compiling example you changed the “testFunc” signature, and I thought that was the reason why it was working. But now I see that you added the function signature to the line above.
The reason I said it didn’t work is beacuse I tried to do the same, but in an inline fashion, like so

filterForkey : TextMap a -> TextMap a = filterWithKey (const . filterWithParam)

and it that doesn’t compile.
Regardless, thanks for your help.

1 Like

Oh sorry the extra argument was by accident, I’ve updated the example above.

2 Likes

I sometimes try that too, out of habit from other languages (Scala I guess?), but that is not actually valid Daml syntax: type signatures have to go on separate lines.

It is valid Daml but not valid Haskell. We call that feature “return type signatures”. However it is not equvalent to a top-level signature.

let a : t = x

is syntax sugar for

let a = x : t

That is not sufficient to generalize the binding a which is why it doesn’t work in your example.

3 Likes