5 ms·
> 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 sign
by gridlockd 7y ago
> 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.
- danShumway 7y ago> 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? 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.
- gridlockd 7y agoIs it because you associate methods with statefulness and because you believe statefulness is bad? 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.
- pjc50 7y ago> methods can just be pure functions and objects can be immutable. 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.
- danShumway 7y agoNo, I've recently started to mentally separate "functional" and "pure", even though they're often correlated. I've built functional systems that mutate data, and I've built traditionally object-oriented systems that always return new objects on every function call. 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.
- pjc50 7y ago> 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. 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.
- pwm 7y agoLuckily Haskell is great at being a dynamically typed language as well :) You can read all your messy real-world input into some data structure of Dynamic-s annotated by their raw input types and then interpret that low level representation into your richly typed domain. Best of both worlds.
- nicwilson 7y agoThe difference not just a technicality it results in a large difference in readability as it linearises nested calls i.e. it transforms it to a functional pipeline (a la unix pipes). 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.
- Symmetry 7y agoAnd the weird thing is that it's almost trivial to create an equivalent of (.) in Haskell that's ordered in the more natural forward way and I really wish it was a standard. Though (.) itself does get rid of all those parenthesis.
- echaozh 7y agoAnd reversing the order is also easy. F# has operators going both ways.
- jose_zap 7y agoThat would probably be the (&) operator, which is In the standard library
- Symmetry 7y agoI was thinking more about composition than application. Thankfully, I just remembered that you can use Hoogle to search by type signature so I popped in (a -> b) -> (b -> c) -> a -> c and it directed me to (.>) which is exactly what I was thinking of in the flow package. Flow seems like an interesting package to check out if I ever go and start using Haskell seriously.
- etbebl 7y ago> 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. 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.