The author doesn't make any comment on dispatch let alone multiple dispatch.
They appear to be writing for the kind of quasi-schema'd information bureaucracy that's very important outside of SV. Lots of data in XML that never quite lines up with an object model.
The Haskellite approach would also say that "what operations are available on that thing" is the wrong way round; you define your operation, and the type signature tells you what kind of things will work with it.
Isn't the common case that you already have "the thing", you have a rough idea what should be done, and you want to see what operations/transformations are available? That's when autocomplete and a static type system comes in handy.
Some (more obscure) languages unify this, i.e. f(x) is the same as x.f(). I don't think there's a "wrong way around", the difference is a technicality.
I'm going to (very lightly) push back and say no, I don't think that's the way I want to program. Certainly I've seen functional systems where it's mostly about syntax, but I think there's also a difference here that is more than technicality.
I'm having a hard time thinking more concretely about exactly what it is, but intuitively the statement you made rings a few alarm bells for me.
If so, I'd point out that methods can just be pure functions and objects can be immutable.
Or, more controversially, partial application is just a lazy person's method definition.
This is true, but then you start wondering what the data hiding is for, especially if an object has getters for all its immutable members.
I think it's more about organization? And more about how often structs in my functional systems evolve over the course of a single function call. Maybe it's that when you're attaching methods to an object, you have a finite number of things you can do to that data.
With a struct, I'm always just one `map` (or even a mutation) away from being in a different format that can be consumed by different methods. Of course this can happen with classes as well -- you can have transformation methods, there's nothing technical stopping you from setting up a class system where you're jumping around that much. But I feel like it happens more when I'm doing functional programming.
An IDE that was focused on "what methods can consume this data" might not capture that -- I'm not saying it wouldn't occasionally be useful, it's just that "what methods can consume this" is not the first question I ask when I see a struct. All of the methods consume it, I can just transform the data or combine it with another struct to fit whatever format a method requires.
> Or, more controversially, partial application is just a lazy person's method definition.
I reasonably strongly agree with this.
I know we like to fight here, but I think both are valid approaches. They may correlate with top-down vs bottom-up design, or which side of the API you're designing. If you're consuming an API, yes autocomplete and suggestions are hugely helpful, as is a type system to prevent you passing garbage to the API and having to have it return errors.
The specific use case from the OP:
> Well a lot of work I do has to do with very messy business logic. Situations were a company wants to convert their receipt system, trading engine, or something of that nature into computer code. Often this data is very complex and hard to define. Situations where a importer may bring in 100 fields but we only care about 10. Thus it's often important to require that certain data match a model, but allow for extra data to flow through a system without much effort. In addition, the problem with nominal k/v types is that I often find myself converting between two types simply because function A requires a DBPerson, and function B requires a UIPerson. If the keys and values are the same, they should flow through, while still keeping me from saying PersonName = ProductID.
I can certainly see where they're coming from - they neither need nor want schema enforcement as they have no control over the input data. Most of their types relate to business semantics, but since the semantics are loose it's not suitable to ram everything into a Person object. And certainly in OO-heavy systems you do end up having to convert between similar-but-not-identical objects that were intended to represent the same thing but defined in different systems.
D has this (called Uniform Function Call Syntax (UFCS) ) and also has optional parenthesis for call with no more arguments which turns this:
send(getInvoice(calculatePrice(x)));
into
x.calculatePrice.getInvoice.send;
It also eliminates the need for extension methods since any function that takes any given type as its first argument is automatically callable as if it were a method of that type, this also applies to templates with the type of the first argument determined by a template parameter.
As for a "wrong way around", you can overdo it both ways, use your judgement. I prefer UFCS for more heavily nested calls which you get when you do a while lot of data transformations all in a row. Some times I use it for a single call but not very often.
Flow seems like an interesting package to check out if I ever go and start using Haskell seriously.
My favorite is Elm, which has |> and <| for application, and >> and << for composition, in the intuitive directions. It makes it easy and natural to eta-reduce/expand, when you want to.
Matlab! Which uses a pretty wild form of dynamic dispatch when the (more common) f(x) form is used:
> When MATLAB® invokes an ordinary method that has an argument list, it uses the following criteria to determine which method to call
> The class of the leftmost argument whose class is not specified as inferior to any other argument's class is chosen as the dominant class and its method is invoked.
> If this class does not define the called method, then a function with that name that is on the MATLAB path is invoked.
> If no such function exists, MATLAB issues an error indicating that the dominant class does not define the named method.
Also, it doesn't care at all if you pass in an array or a scalar (everything's an array). Methods must be written to handle a "this" of any size - even empty.
What an odd language.
> The Haskellite approach would also say that "what operations are available on that thing" is the wrong way round; you define your operation, and the type signature tells you what kind of things will work with it.
i'm not sure if i understand. using typeclasses i can define a Haskell function for summing collections:
sum :: (Foldable f, Monoid a) => f a -> a
sum x = foldr (<>) mempty x
and it'll work for any Foldable collection (supporting `reduce`) of Monoidal values (supporting "addition", or the `<>` operator) - lists of strings, trees of ints, etc. that sounds a lot like "define your operation, and the type signature tells you what kind of things will work with it"> The Haskellite approach that says "what operations are available on that thing" is the wrong way round
which left me pretty confused!
How does code completion work in Haskell IDEs then? It doesn’t seem that it would have a decent anchor.
Outside of the C++/Java/.Net world, names tend to be much smaller.
Anyway, it works like autocompletion works on any textual input, a couple of characters severely restrict the amount of words you are trying to write, and context guides on the rest.
Haskell in particular tends to exhibit local variables that are less than a couple of characters long, so autocomplete does not even make sense for those.
Globals tend to have wither short distinctive names when they are more generic, or long prefixed names when they are concrete. Either way, just completing over a dictionary gets results that are at least as good as you'll get on Java/.Net/C++ IDEs. Haskell also brings a lot of added context, but I don't know of any IDE that uses those for completion (and honestly, I don't miss it).
That has little to do with code completion, which is more about API discovery than saving on typing. It seems like many Haskell enthusiasts never figured that out, so they didn’t bother with it in tool development. Which is funny, given with all that extra strong typing, they might be able to come up with an experience that is much better than the other languages, but I guess not yet.
I'm hopeful that cool type-powered IDE features will start to arrive once haskell-ide-engine is a bit more stable. It's getting there!
There are some pretty awesome IDE ideas in Haskell-ish languages like Idris [1], too. There is some movement towards dependent types in Haskell, so it might get similar features one day.
List.
| length
| hd
| tl
...Case in point, the core toolchain (e.g. the compiler itself) supports "typed holes". Basically, you drop a placeholder somewhere in an expression and the compiler will infer and report the type of the sub-expression. There are also tools to search either within a program/library or e.g. the entirety of the Hackage package database for functions matching a certain type. It seems like a relatively short distance from there to having an IDE or code editor that can list possible expressions/functions that could satisfy a particular placeholder. And to me, having e.g. a list functions that transforms the input(s) I have to the output I need seems much better than e.g. a partial list of all the functions that relate to a particular type.
And maybe such a thing exists today, but I have had really bad luck trying to get Haskell tooling that works well and is easy to set up.
I don't get why this is important. If the original author, you had an idea of what you wanted to do with the thing. As a reader the thing is whatever supports the functions called on it in the body. Naming is important. Adding type annotation for humans is a good pattern and requiring it everywhere as part of language spec doesn't seem beneficial. Heck even Java now has var like js in method bodies.
I've always appreciated the advantages of code being legible as plain text files. But maybe it is just a little absurd when language design is shaped to fit the desire not to evolve the storage format and editing interface.
In fairness this sort of data is metadata that can be added at tooling time - there's no need to place it in the source itself.
Ruby and Python both started as scripting/glue languages meant for small programs. At that scale, it doesn't really matter.
When the codebases they were used for started getting bigger and started having longer histories and a need for refactoring, that's when people started asking for more structure.