1

I'm trying to get a function working which unwraps an envelope type, applies a function to the content and returns an envelope type. Sort of like the bind function of a burrito.

type Envelope<'a> = { Content : 'a ; Errors : string list } let (!>) f e = let {Content=content:'a; Errors=errors} = e match errors with | [] -> e : Envelope<'a> | _ -> f content : Envelope<'b> 

The error is:

This construct causes code to be less generic than indicated by the type annotations. The type variable 'a has been constrained to be type ''b'.

I have a "feeling" why it's wrong, sometimes I'm returning an Envelope<'a> and the other times I'm returning an Envelope<'b>.

How can I get this to work? I'm trying to make it "work" like I would a bind function on, for example, an Option type:

let (>>=) f o = match o with | Some v -> f v | None -> None 
3
  • It looks like you want the F# Result<'TSuccess, 'TError> type (built in to F# since F# 4.1). See github.com/fsharp/fslang-design/blob/master/FSharp-4.1/… for more details. In your case, I think 'TError would be string list. Commented Nov 26, 2018 at 9:03
  • @rmunn That would change my type from a record to a du, the idea of the Envelope is that I pass this around, do operations on the content en at the end of my composition use this Envelope to send the right response to the browser. Just returning the Error (string list) would remove the "knowledge" of what I'm doing. Commented Nov 26, 2018 at 9:35
  • Are you trying to map the errors to a valid value? Is this an equivalent to Option.defaultWith. Commented Nov 26, 2018 at 15:07

2 Answers 2

3

The problem is that both cases of the match should return the same type, otherwise it doesn't make sense in the type system.

You need to construct a new envelope, but I imagine the problem is that you don't want to compute f if there are errors, so a hacky way of doing that would be:

type Envelope<'a> = { Content : 'a ; Errors : string list } let (!>) f e = let {Content=content:'a; Errors=errors} = e match errors with | [] -> {Content = Unchecked.defaultof<_>; Errors = e.Errors } : Envelope<'b> | _ -> f content : Envelope<'b> 

But that's not what you want, since you will lose the content.

The proper way would be to use a Discriminated Union, instead of a record, but I think what you want is to apply the compensation function all the time there are errors, so in that case, your compensation function can't be polymorphic, therefore the original error message.

Sign up to request clarification or add additional context in comments.

5 Comments

Thank you for the comment, I feared as much. Might there be a way to "trick" the compiler in thinking 'a is coerced into a 'b? There should be no reason to think that 'a is a different type from 'b. (So, coerce e :> Envelope<'b> or something silly like that...)
Why do you want to coerce? Beware that the compiler is not pointing a technical problem, it's a logical problem. Your function should return apples, not apples and sometimes oranges, that makes no sense.
When you put it that way, you are right, it makes no sense. I'm trying to look at the little part which they have in common, the Envelope. I guess there is nothing more to it, will have to rewrite... PS, if you're interested, in C# I'd write it like: ugly dynamic solution
edited my original question with a sort of answer. Wrapping the Envelope up in a DU seems to do the trick...
Yes, but when you unwrap the DU you'll be forced to deal with the different types. Also this will limit you to use the compensation only once. My strong feeling is that there is nothing wrong in having the compensation function defined as f : 't -> 't
0

Seems to me you are either trying to map the error or get a default value in case of errors. Here are several options

type Envelope<'a> = { Content : 'a Errors : string list } /// (unit -> 'a) -> Envelope<'a> -> 'a let defaultWith f e = match e.Errors with | [] -> e.Content | _ -> f() 

This one gets the value and if there is an error then invokes the function to get a default value. It is equivalent to Option.defaultWith. It doesn't return an Envelope.

This next one lets you map the errors, only if there are errors:

/// (string list -> string list) -> Envelope<'a> -> Envelope<'a> let mapErrors f e = match e.Errors with | [] -> e | _ -> { e with Errors = f e.Errors } 

These two, on the other hand, let you map the whole envelope if there are errors. You can create many variations of the same theme I just left 2 for illustration:

/// ('a -> Envelope<'a>) -> Envelope<'a> -> Envelope<'a> let mapIfErrors f e = match e.Errors with | [] -> e | _ -> f e.Content /// ('a -> string list -> Envelope<'a>) -> Envelope<'a> -> Envelope<'a> let mapIfErrors2 f e = match e.Errors with | [] -> e | _ -> f e.Content e.Errors 

2 Comments

Thank you for the message, this is really useful, but I want to have a function which goes from 'a -> Envelope<'b> and pass in an Envelope<'a>. This way I can chain the functions which transform the data and manage the errors which are generated.
Give me a specific example of how you would use it.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.