I guess you’d want existentials like this:
myFunc :: forall a. Contraption a => a -> exists b. Result b /\ b
(using ASCII-ified notation from An Existential Crisis Resolved)
I guess you’d want existentials like this:
myFunc :: forall a. Contraption a => a -> exists b. Result b /\ b
(using ASCII-ified notation from An Existential Crisis Resolved)
Isn’t interface an OOP concept which should only be used as a last resort ?
I really don’t. I’ve researched this topic including existentials, and everything I found about it was just inappropritate
No. Interfaces is a universal concept of engineering (not only in software).
I’m just trying to understand what you mean. I don’t know enough about interfaces to understand what the difference is between your example and my example.
I’m afraid, any explanation will be just words (as it happened in the topic here: C++ 20’s concepts are typeclasses and make object-based polymorphism obsolete - shockingly!).
My general advice to all haskellers is to practice more languages, OOP included, and broaden their horizons. The notion of a programming interface is super important.
Ah, I see, because you want to be able to do something like
handleError :: ExceptT String App a -> App a
which doesn’t translate directly to MTL style because if you try something like
handleError :: (App m, Error String m, App m') => m a -> m' a
then it doesn’t work, and if you try something like
handleError :: App m => ExceptT String m a -> m a
then it’s too concrete?
That’s what I thought too, that’s why I suggested first-class existentials:
handleError :: (App m, Error String m) => m a -> exists m'. App m' /\ m' a
Or with some more impractical manual work you can do it in Haskell today:
data SomeApp a = forall m. App m => SomeApp (m a)
handleError :: (App m, Error String m) => m a -> SomeApp a
But apparently, that’s not it.
This is the right direction of reasoning. However, the problem is even more serious. I want to encapsulate the result completely so that the client code could work with it abstractly, and I don’t really want to carry all those type-level bits here and there (because it breaks the encapsulation and brings a lot of boilerplate).
So interfaces allow to have runtime polymorphism for input and output values. Type classes won’t allow that. Existentials do something, but yet, it is a crutch that doesn’t work as needed
I think it would help us if you could elaborate on why. I have not yet found a programming interface that polymorphic lambda calculus cannot represent so I would welcome being enlightened by you!
I fully agree with this. Type classes introduce a lot of complexity in the types. I’ve also seen this argument in Edward Yang’s thesis on Backpack:
From a code perspective, type class parametric code is often harder to use than monomorphic code. For an inexperienced Haskeller, the proliferation of constraints and type parameters in the type signatures of functions can make an otherwise straightforward API impenetrable:
(=~) :: (RegexMaker Regex CompOption ExecOption source, RegexContext Regex source1 target) => source1 -> source -> target -- from regex-posix-0.95.2
Furthermore, type classes work best when exactly a single type parameter is involved in instance resolution. If there aren’t any type parameters, you must introduce a proxy type to drive instance resolution; if there are multiple parameters [5], you often need to resolve ambiguity by introducing functional dependencies [12] or replacing parameters with associated types [24].
Edit: It was Yang’s thesis, not Kilpatrick’s.
I’ll see what I can do; it’s just not easy to explain. I have an article in Russian about why type classes and existentials can’t represent the notion of interface, but it’s outdated (year 2012?). Maybe I’ll write another one.
For now, I have an example that I feel should lead to the answer (but not completely sure - this needs to be researched). In my article Functional Declarative Design: A Comprehensive Methodology for Statically-Typed Functional Programming Languages, I have this task:
This task requires a return type polymorphism. It’s pretty easy (and clean!) to do it with Free monads. But FT/mtl/existentials will definitely require a lot of unwanted bits if even be able to solve this.
data CookingMethod next
= MakePizza (PizzaRecipe Pizza) (Pizza -> next)
| MakeSandwich (SandwichRecipe Sandwich) (Sandwich -> next)
| MakeRandomMeal (CookingMachine Meal -> next)
type CookingMachine a = Free CookingMethod a
sampleCookingMachine :: CookingMachine [Meal]
sampleCookingMachine = do
sandwich <- makeSandwich mySandwich
randomMeal <- makeRandomMeal
pure [sandwich, randomMeal]
That’ look for me like OOP shoe-horned into FP.
I see, so the key element here is something to do with CookingMethod
and CookingMachine
depending on each other?
It is not. It is a usual approach of information hiding and encapsulation.
Look, this anti-OOP bias makes you lack a lot of design tools and ideas. OOP is just another implementation of these tools and ideas, but the ideas themselves are more high-level, general, and universal.
I think your comment would be more helpful if you could explain why it’s bad to want (this aspect of) OOP. After all, if OOP is a good thing then shoehorning into FP is a good thing! And the fact that it needs to be shoehorned is a weakness of FP.
I’ll carefully answer “yes”, but again, I would first to develop my position better to answer definitely
I’m curious. What difference does it make using
data CookingMethod next
= MakePizza (PizzaRecipe Pizza) (Pizza -> next)
| MakeSandwich (SandwichRecipe Sandwich) (Sandwich -> next)
| MakeRandomMeal (CookingMachine Meal -> next)
type CookingMachine a = Free CookingMethod a
in preference to
data CookingMethod' next
= MakePizza (PizzaRecipe Pizza) (Pizza -> next)
| MakeSandwich (SandwichRecipe Sandwich) (Sandwich -> next)
type CookingMachine' a = Free CookingMethod' a
data CookingMethod next
= MakePizza (PizzaRecipe Pizza) (Pizza -> next)
| MakeSandwich (SandwichRecipe Sandwich) (Sandwich -> next)
| MakeRandomMeal (CookingMachine' Meal -> next)
type CookingMachine a = Free CookingMethod a
Does the former have some important property that the latter does not have?
What do you know about my lack of design tools and idea?
I’ve been doing OOP for 15 years and FO for 10. I moved from OOP to FP for a reason and I remember at the time trying to implement all those OOP concepts which I thought were general until I realized they were not needed (or best avoided if possible).
I know the concepts you are defending, i was defending them 10 years ago.
I hope we can avoid making personal comments and stick to objective facts. Some more examples of OOP things that can’t be done in FP would be useful, as would objective arguments about why you shouldn’t want to do those OOP things (if any).