Why so?
Why so?
There's nothing wrong with any of that. As a professional, you want to be good in multiple paradigms. The problem is that you're mixing them all up, whether you realize it or not (Most of the time, programmers don't realize it) And now, every piece of code can go down two entirely separate tracks as you suss out your architecture. Not only is that not going to work, it's going to make debugging and maintenance a nightmare.
This is very, very similar to how good C++ shops work. You've got to tightly limit the way you solve problems in order for all of the programmers involved to be to reason about the code.
F# code, from my somewhat limited experience, seems to almost always be organized logically (top to bottom, types separated from functions) using well-established patterns. Obviously if you decide to write a bunch of classes and mutating code in F# when more idiomatic approaches would work, it’s going to get hairy.
I feel like I’m missing your point.
That is just a curried function with three arguments. The bigger problem is that programmers still have to understand how Hindley-Milner type inference works to figure out type errors. This gets worse when you add in type classes, dependent types and (Yog-Sothoth help you) lenses.
Your point is still true. For any language, it is important to have an IDE ecosystem in place. The tooling must enable developers to move beyond fighting the unruly or boring parts of the language and instead leverage it to work on the domain.
let createMemoizer f =
let cache = Dictionary<_,_>()
let f' = fun x ->
match cache.TryGetValue(x) with
| (true,value) -> value
| (false, _) ->
let result = f x
do cache.[x] <- result
result
f'
The signature is ```createMemoizer (f:'a->'b) -> 'a->'b```, which is not very telling, especially if it is returned by yet another function, so that even the createMemoizer name is lost. In classic OO, I'd just get a Memoizer object that carries vital documenation and what not with itself, as it is passed around. It's no even that much longer, it's just not as elegant to use. class Memoizer<TINPUT,TOUTPUT> {
Dictionary<T,TOUTPUT> cache = new Dictionary<T,TOUTPUT>();
Func<TINPUT,TOUTPUT> f;
/* ... */
public TOUTPUT Invoke(TINPUT parameter) {
if (!cache.TryGetValue(parameter, out var result)) {
result = f(parameter);
cache[parameter] = result;
}
return result;
}
}