1,004 karma · joined July 4, 2014
Imo inference is a bit overrated anyway. I find annotations to be very useful.
There's a series of steps here:
- "Static type annotations are too verbose, dynamic languages allow you to avoid the ceremony." - "But Hindler Milner type systems can infer the types, so you usually don't have to write the type annotations!" - "Writing type annotations is useful anyway, because it serves as valuable documentation, makes reasoning about the program easier, and results in more useful type errors."
Thinking about it more now, there's another step: - "If you have good type inference, you can use editor tooling (eg the LSP) to automatically insert the type signatures for you!"
I'd love to see something like Rosie take off as an alternative: https://rosie-lang.org/about/
Brilliant
I agree that there are many problems more naturally represented by procedural code, and as such it's important for every language, even functional languages, to have good constructs for representing a series of steps.
Luckily, Haskell has such a construct in do notation. It looks like this:
main = do
putStrLn "What's your name"
name <- getLine
putStrLn ("Hello, " ++ name)
I'm not familiar with MongoDB, but I found a haskell library for that and the example looks relatively straightforward to me, modulo possibly some unfamiliar syntax. It's a bit long, but it's doing more than just an insert. [1]I've heard the cake argument many times, and I agree that it's more naturally modelled as a series of steps. But I find it frustrating, because I've never heard a convincing argument for why the fact that one particular problem is more intuitively modelled as a sequence of steps, means all problems are.
For example, imagine if you were never allowed to declaratively specify a DOM again using HTML, but instead you had to construct it as a series of calls to element.appendChild(). Would that really make your life easier? There are many problems for which a declarative description, not a procedure, is much more straightforward to understand.
Another point is that you can also think of applying functions in succession ("function composition") as a series of steps! I find this pretty intuitive.
For example, here's a function which takes a list of words and shouts them. The steps are: First concatenate the words together, then capitalize the resulting string, then append an exclamation mark:
λ » shoutWords = unwords .> capitalize .> exclaim
λ » shoutWords ["i'm", "shouting"]
"I'M SHOUTING!"
I think you're right about needing to have a certain level of smarts to get Haskell, but I think this is a consequence of some more advanced Haskell features (Typeclasses + higher kinded polymorphism), rather than the functional paradigm itself. For example, I think Elm is probably very approachable for people with a wide variety of different levels of mathematical ability.I think it's very easy to assume that functional programming is inherently more difficult, when it might well be that all that's going on is that people are more used to procedural programming because that's what they were taught first. I really would encourage anyone who thinks FP is inherently difficult to have a crack at learning Elm.
[1] https://hackage.haskell.org/package/mongoDB-2.7.1.1/docs/Dat...
> That said, there are ways of dealing with this in languages like Python, they're just a little more verbose and you don't get the compiler checking that you've exhaustively handled all cases, for example. It won’t compile, it won’t accept that idea.
Something like that would be considered bad Haskell code, yes. Haskell is designed from the ground up to support the functional paradigm. When people try to use functional style in languages that were designed with primarily procedural style in mind, the result is often ugly and difficult to follow. You just don't have the same issue in a language that's designed from the beginning as a functional language.
Something that people tend to miss is that you can write code that very much looks like a lot of procedural code in haskell:
let
x = 1
y = x + 5
z = 10 + y + z
in x + y + z
You can have bindings, just like you might use variables in a (eg) python function. The only difference is that the bindings are immutable. That turns out to not be as restrictive as you might expect - a lot of good python code only assigns to a given variable once.I think it's a fair criticism to say that Haskell code can become opaque when it relies on more advanced language features and esoteric GHC extensions. The solution to this is to be disciplined and selective as a team about choosing which features you believe are worth their complexity/onboarding cost, and sticking to those choices.
Conversely, I find Haskell code that doesn't use those extra features to be much easier to understand than equivalent code in other languages, due to the extra guarantees I have about the code. For instance, I know that variables won't be mutated out from under me, things can't be null where they shouldn't be, and there are plenty of other assorted guarantees that come from having access to an expressive type system.
This doesn't negate the advantages of tracking effectful code in the type system, and there's still plenty of code that can be pure.
In my opinion, in order to justify instantiating an object, it should actually contain data, and that data should actually be accessed by code other than methods internal to that class. This rules out using an object to represent a procedure - regular functions subsume that functionality.
Before first class functions became commonplace, it did make sense to use objects as procedures in some OO languages, to allow you to pass them around as values. Since most languages these days do have first class functions, this has become less necessary.
If you want a class in order to to split the procedure into multiple methods with access to shared mutable state, that's totally fine - you can just use nested functions to do the same thing.
procedure = [
CreateSSHKeypairStep(),
GitCommitStep(),
WaitForBuildStep(),
RetrieveUserEmailStep(),
SendPrivateKeyStep(),
]
for step in procedure:
step.run(context)
Why not make them functions instead of classes. The URLs can be locals instead of class variables and the context argument becomes an argument to the function rather than a run method. procedure = [
createSSHKeypairStep,
gitCommitStep,
waitForBuildStep,
retrieveUserEmailStep,
sendPrivateKeyStep,
]
for step in procedure:
step(context)
The OO is just gratuitous. Personally I probably wouldn't even bother with the for loop/context and would just pass the data that's actually needed by each function into it manually. createSSHKeypairStep(username)
gitCommitStep(username)
waitForBuildStep()
email = retrieveUserEmailStep(username)
sendPrivateKeyStep(email)
I get that it feels more modular - "oh all I need to do to add a step is add it to the list!" But like, all you need to do to add a step is add a line of code. There's no difference.Much easier to see how the data is actually flowing that way. I found the fact that the context was mutated in a function it was passed into kind of gross. It's just globals with extra steps. Besides, this is less LOC anyway.
My guess is that most other tiling wms would have a straightforward way to float windows as well.
https://blog.codinghorror.com/falling-into-the-pit-of-succes...
I think it's really just a matter of prolonged exposure to make recursion seem intuitive.
Another is that in a pure language, you can look at a function and know for a fact that nothing you're seeing can ever be mutated. This eliminates a massive source of cognitive load when trying to read and understand code, as well as eliminating all sorts of bugs related to the program being in the wrong state. If you're trying to write functional code in a language not designed for it, you're missing out on those benefits.
Now, it's absolutely the case that the tradeoff may be worth making depending on the person and circumstance. But it does exist.
https://hackage.haskell.org/package/flow-1.0.22/docs/Flow.ht...
> Customizable cursor height (see :h NvuiCaretExtendTop and :h NvuiCaretExtendBottom)
Ex. :NvuiCaretExtendTop 200, :NvuiCaretExtendBottom 100 (see below)So many programmers have this obsession with interfaces having every available piece of information displayed all at once. I just don't understand the appeal. I find these sorts of interfaces confusing and overwhelming, I literally can't think straight when I look at them. I immediately forget what I was doing due to all the distractions.