IME there are two kinds of programmers: ones who learned a single language deeply and have almost religious confidence that it's the best thing ever, even if they try to approach other languages with an open mind (i don't blame them, i was like that a couple decades ago, too) and some who understand that tools are just that - tools, which you should pick accordingly to the problem you want to solve, because the other way ends up in a tail-wags-dog situation. the blog post sounds like the author has realized that and started a journey from the first type to the second type, which is to be commended. good job.
I see what you are saying but I have also seen plenty of Python code written by what are clearly Java developers. And Framework collectors who have learned learn the very basics of Django before they moved onto something new - and hence written a load of overcomplicated crap that could have been done a lot cleaner if they had learned the framework in more depth. Learning some things in depth definitely has benefits.
Yours is concerned with code quality and maintainability, both generally benefit from idiomatic programming style which people new to the language rarely have. And you're 100% right.
BUT the parent post was talking about ideologies "the one true language" vs "hey, i can make this work anywhere"
the latter camp affects you in that many of them don't have the discipline or patience to really ingest the new language before spewing out code that "works" but sucks for maintainers.
All that recursion, lazy evaluation, monads, lambdas, virtual methods and coroutines. In the end, they are all stack manipulation and jump instructions. High level languages are just a more convenient way of writing assembly.
I like to take that approach when comparing languages. What a program might do in term of machine instructions, and see how I can make another language output similar instructions. The one that can do it the more naturally wins.
Well, in the end the universe dies in the heat death stage, and nothing matters.
But we're not "in the end", so this final analysis "it's all machine code anyway" is reductionistic.
It might be all machine code, but the abstraction at which we think and program matters, for the correctness, speed of writing, speed of understanding it later, and performance of the deliverable...
This seems like a fairly good indication there probably aren't really two kinds of programmers. Unless you experienced that particular change, from those particular initial conditions in some strikingly discrete fashion and also believe those are both universal.
one who understood recursion
and there were two types of programmers
one who did not understand recursionDo you want to use something that makes you miserable? Is any language including those designed by 3 year old 'just a tool' that is to be used despite obviously being shitty?
Never understood that argument.
In other words, some kinds of tools are better suited for a problem than others, but there exist tools that inferior even though they were designed to solve your original problem.
You should pick the best tool for the job. It might be unclear what that tool is and some tools are better than others at solving the same problem, even if they are designed to solve the same problem.
There isn't really an argument, just an observation that the most efficient way of thinking about programming is in data structures and algorithms which are mostly language independent. Nobody should be spending most of their time leveraging language features, so it isn't critical to use any particular language.
I have a large range of pens of different quality. I have a favourite pen. But I'm willing to use any pen if I need to write something down. My focus is on composing a message, not on worrying about the quality of my calligraphy or the occasional scratchy mark. If 'm going to be writing all day I'd rather have my favourite pen but any one will do.
If you know of a tool that is the wrong tool for every job, then yes, it’s still just a tool. Just use something else.
So yeah, just a tool.
The argument is that, even when you compare between very good tools, each one of them will make your life miserable when you apply it to a problem for which it is not designed, and for which a different tool is a much better choice.
I.e. there's no "one tool that is the best thing" that can be applied to all your tasks, because the tasks are widely different so tools are necessarily limited. That doesn't preclude some tools designed for the same type of task being clearly worse than others of the same kind (your argument).
First you can't always choose the stack you work with. You work with other people, who may have expertise in different languages. You may have to reuse existing code written in different languages. And it's rarely the case that some stack is better than another on every aspects, there is usually some form of compromises to be made.
"People fear what they don't understand and hate what they can't conquer." Andrew Smith
These kind of functional solutions are very cute mathematically, but...
I once wrote this way of generating an infinite list of primes in unlambda. That was kind of interesting to do.
That's not really true, though, because that filter is applied lazily, so it only get evaluated up until the Nth prime and no further.
The time complexity is less obvious, each prime adds an additional pass to evaluate, so that seems linear, but each new pass only applies to a list that already had all the previous passes filtered out, so that's not technically linear, but I find it hard to determine how much it actually is.
IEnumerable<int> filter(int p, IEnumerable<int> xs) {
foreach(var j in xs)
if (j % p > 0) yield return j;
}
IEnumerable<int> sieve(IEnumerable<int> s) {
var p = s.First();
yield return p;
foreach(var e in sieve(filter(p,s.Skip(1))))
yield return e;
}
var n = sieve(Enumerable.Range(2, 10000)).Skip(10).First();Cardano/ADA's core Ouroboros protocol was entirely written, with formal proofs, in Haskell. It is by far the most serious attempt at proof of stake in the crypto industry.
I think what you're really saying is, you won't find many jobs out there w/Haskell as a requirement... but I wouldn't go as far as saying Haskell has no purpose past teaching FP. It certainly is the poster child of FP, however it is not without practical/industrial use. You just have to look harder to see where it's being used.
This is where advocacy slips over the line into a kind of blind faith evangelism -- with an added pinch of pedantry peculiar to our field.
I will state without proof that every language ever invented is currently being used for something practical somewhere. E.g. someone has a useful shell utility they wrote in Brainfuck that they run every day and that they love. This does not mean that BF has 'practical application' in the usual sense of the phrase. In the same way, just because there is a real program written in Haskell somewhere does not mean it has practical application.
IMHO a language (or a tool in general) should have a community of practitioners that regularly think in and create with the tool, and a good signal that that is happening are the number of open-source releases in that language. I have never in my life installed a Haskell program, or known of anyone installing one. (The exception being this one Haskell fan at JPL who loved talking about the language and the lambda calculas, but to my knowledge, never wrote anything real in it, and he just installed learning toys, not utilities.)
All that said, there are some good examples of unpopular tools that enjoyed success later in life. Quaternions lost to vectors historically, but graphics programmers rediscovered their benefits and resurrected them. Maybe Haskell will have it's time, but that time is not now.
How is that in any way, shape, or form comparable to Brainfuck?!
How does this not qualify it as a language with practical applications? Frankly, your comment comes across as being based upon hearsay, instead of actual experience with the language and its community. You’re right that Haskell is not as popular as more mainstream languages, but that does not make it similar to Brainfuck.
[1] https://engineering.fb.com/security/fighting-spam-with-haske...
Seems like it would be so out of place in a real industry code base, like a infinite loop waiting to happen. There are always better more readable and maintainable ways to accomplish the same thing.
Rather than `state := null; while condition do: mutate-state-and-recompute-condition`, you can do `let loop(state) = if shouldContinue(condition) then loop(newState) else resultOfTheLoop`. Rely on the tail-call optimiser to compile this to a genuine imperative loop.
This looks very odd the first few times you see it, but it's much harder to get wrong.
In situations where you think auto-vectorisation might help, you definitely want to do it as iteration.
This is partly why I like the system of transformers that LINQ is built out of; you can specify your query in a natural nice functional manner, and then let the optimizer convert it into a fast query.
The reason one might want to use recursion is when you're working on a data structure that is recursive in shape, like trees. And trees are very common, the Hacker News comments are one such example.
The second rule of recursion is we do not talk about recursion.
The third rule of recursion is without a base case, you have no recursion.
The fourth rule of recursion is it breaks you in two or more pieces.
When I got to the 7 line snippet at the end I was blown away by the elegance, and the fact that I couldn't really understand exactly what each thing did. So I decided to spend a few hours going through it character by character and documented my process here [1] in case it was useful to anyone else.
I'll continue by reading a book on Haskell, but open to any advice from anyone on good ways to get into it!
[1] https://aurbano.eu/note/2019-12-18-journey-into-haskell/
Quite casual, just what I needed. Thanks for writing it up!
One blogging tip (note: the rest was perfect): if you would've chopped the following in 2 or 3 lines, then the casual style would be perfect.
> primes n = take n $ sieve [2..] where sieve (p:xs) = p : sieve [x | x <- xs, x `rem` p > 0] sieve [] = []
I feel that it'd be really hard to do though.
Here is a python version.
https://www.google.com/amp/s/www.geeksforgeeks.org/python-pr...
To me this post is saying that in some languages (in this case Haskell) there are ways of working that are hard to emulate in other languages (in this case C#). The post is talking through a specific example of this, and pointing out that if you only know one language (in this case C#) then you might be missing out on styles of thought that can help in solving certain problems.
The post isn't about one language being better or worse in absolute terms, it's about broadening your range as a programmer so you are aware of other techniques and styles of thought.
To go all "new age-y" it's about enlightenment. Usually Lisp is the language used to help programmers achieve enlightenment, but other options exist, such as fexl, Haskell, and others.
Seriously, many languages are "about the same," but some really do make you think differently. You can be an excellent and productive programmer without them, and the vast majority of programmers never try to step outside the bubble of Imperative & OO languages, but you will be missing out if you don't at some stage embrace one of these other "pure(ish) functional" languages that is genuinely different.
YMMV
Some reading:
* https://www.defmacro.org/ramblings/lisp.html
* https://stackoverflow.com/questions/2036244/whats-so-great-a...
Search for "Lisp Enlightenment" and cull mercilessly.
I’ve had a few similar experiences in my humble programming education.
The first was at uni, having to learn c, c++ and python in one semester, after only using java for the first two semesters. (And a tiny bit of php and visual basic before that)
The second was exposure to scheme/racket and real functional programming.
The third time was the most amazing mix of haskell, type theory, lambda calculus, logics, agda, category theory, proof theory, model theory and just theoretical computer science in general.
It leaves you with this wonderful and strange view of programming, without any of the concrete computational models.
static IEnumerable<int> infinite()
{
int x = 2;
while (true)
{
yield return x++;
}
}
static IEnumerable<int> primes(IEnumerable<int> l)
{
int head = l.First();
yield return head;
foreach (var x in primes(l.Skip(1).Where(x => x % head != 0)))
yield return x;
}
static void Main(string[] args)
{
System.Console.WriteLine(string.Join(",", primes(infinite()).Take(20).Select(x => x.ToString())));
}It's more of a concern with things like mutual recursion (multiplying your frame depth at each step, instead of just +1) or if your recursion depth is based on unbounded user input (eg iterating over an AST, or this code snippet)
https://www.cs.hmc.edu/~oneill/papers/Sieve-JFP.pdf
Melissa O'Neill shows that the typical FP sieve has worse big-O complexity than imperative.
https://wiki.haskell.org/Prime_numbers#Sieve_of_Eratosthenes
The Haskell wiki gives several sieve implementations and can't figure out the big-O complexity. (The imperative sieve is O(n log log n).)
nth :: Integral a => Int -> Maybe a
nth n | n < 1 = Nothing
| True = Just (primes !! (n-1))
primes :: Integral a => [a]
primes = sieve [2..]
where sieve (p:xs) = p : sieve [x | x <- xs, x `mod` p /= 0]
Note that this is not a genuine sieve [1] var allPossibleNumbers = Enumerable.Range(3, max-3);
var possiblePrime = allPossibleNumbers
.AsParallel()
.Where(n => Enumerable.Range(2, (int)Math.Sqrt(n))
.All(i => n % i != 0)
);
(from https://codereview.stackexchange.com/questions/6115/sieve-of... ) public static List<int> Sieve(int n)
{
bool[] arr = Enumerable.Range(0, n).Select(i => true).ToArray();
Enumerable
.Range(2, (int) Math.Sqrt(n) - 2)
.ToList()
.ForEach(i => { if (arr[i]) Enumerable.Range(0, (n - (i * i)) / i).ToList().ForEach(j => arr[j * i + (i * i)] = false); });
return arr.Select((b, i) => b ? i : 0).Where(i => i > 0).ToList();
}The unknown unknowns become known.
Stream.iterate(1, i -> i + 1).filter(i -> !IntStream.range(2, i).filter(v -> i % v == 0).findAny().isPresent()).skip(100000).findFirst();#!/usr/bin/env ruby require 'prime'
def find_prime(nth) Prime.lazy.drop(nth-1).first end