small medium large xlarge

Confluence-profile_pragsmall
12 Jan 2018, 01:02
Mark Elston (8 posts)

First off, thanks for this outstanding book. I have read your posts on F#FFAP on domain modeling and this book is an invaluable ‘deep dive’ into the concepts.

One thing I noticed that caused me some confusion is the names of the error types. For example, you have:

type PlaceOrderError =
| Validation of ValidationError
| Pricing of PricingError
| RemoteService of RemoteServiceError

and later you have the following:

address
|> adaptedService
|> Result.mapError RemoteService

The use of RemoteService here is a bit confusing when reading the code. It seems to be the name of a service but it is really the name of a type of error. Is this a common idiom? I would have thought that all errors would have some variant of Error in the name somewhere.

Mark

Generic-user-small
12 Jan 2018, 09:33
Scott Wlaschin (27 posts)

Yes, it is a bit confusing :)

In the section on “Working with F# Types” in chapter 4. I said: “A choice type is constructed by using any one of the case labels as a constructor function, with the associated information passed in as a parameter”

In this case RemoteService is the name of one of the cases of the PlaceOrderError choice type. So to create that case, you use RemoteService as the constructor function and pass the associated data which in this case is the details of the error, an instance of RemoteServiceError. To be clear, RemoteService is NOT a type itself, just a constructor function, that’s why it doesn’t end in Error.

Here’s the clever part – because RemoteService is a constructor FUNCTION, you can use it to convert a lowly RemoteServiceError into a “higher” PlaceOrderError just like any normal function call. In particular you can pass it into to functions such as map, bind, etc.

Hope this helps!

Confluence-profile_pragsmall
12 Jan 2018, 20:13
Mark Elston (8 posts)

Thanks, Scott. It does help. I keep forgetting that items like RemoteService aren’t really types but functions that construct objects of a given type. This is, at least in part, due to the fact that they are not declared like functions.

BTW, I have read about Haskell Type Constructors before but I never really thought of them as functions in the sense of them taking arguments and returning objects of a given type. I always thought of them as ‘sub-types’ of the primary type. From your book I see that isn’t (exactly) the case - though there is some kind of structural identity associated with the type constructor kept with the object created to allow for pattern matching later on.

But, the naming of these functions in this example is still a bit off-putting to me. When I read the line

|> Result.mapError RemoteService

I get the immediate impression of ‘calling’ (or using) a remote service, not creating an error type. I have to go back and look at the definition of RemoteService to remember it is creating a kind of PlaceOrderError. The two names don’t seem to have anything to do with one another. That is the real source of confusion for me. RemoteService is a word describing something that does something (including, possibly, generating an error) while RemoteServiceError is a type of error - most likely from a remote service. :)

So, using a function named RemoteService doesn’t imply creating an error type to me.

In fact, even with the potential confusion created by shadowing, I might have considered defining PlaceOrderError something like this:

type PlaceOrderError =
| ValidationError of ValidationError
| PricingError of PricingError
| RemoteServiceError of RemoteServiceError

This isn’t ideal either since you can now have confusion in your code as to which RemoteServiceError you are referring to. But the line:

|> Result.mapError RemoteServiceError

is more descriptive of what is being done, here. At least it is to me.

Generic-user-small
12 Jan 2018, 20:36
Scott Wlaschin (27 posts)

You’re right! I probably could have named it a bit better – I should have talked to a domain expert! Anyway, I’m glad you’re working through it OK. Thanks!

Confluence-profile_pragsmall
13 Jan 2018, 21:32
Mark Elston (8 posts)

I just went back to “Learn You a Haskell…” (LYAH) to reread the section on types - especially on type constructors - and realized my earlier (2nd) comment was incorrect - or, at least poorly worded. In the definition:

type PlaceOrderError =
| Validation of ValidationError
| Pricing of PricingError
| RemoteService of RemoteServiceError

PlaceOrderError is a Type and the elements Validation, Pricing, and RemoteService are value constructors (at least according to LYAH). In my comment I was equating them with Type Constructors - which they are not.

I’m not sure if you can call PlaceOrderError a Type Constructor or not as it doesn’t take any parameters. If it were generic then it probably would fall into the category of a Type Constructor as it takes a type parameter to create a type as a result. Like Option<int>.

It doesn’t really matter for the topic under discussion but I hate leaving wrong or ambiguous comments laying around.

You must be logged in to comment