top | item 33226221

(no title)

wallscratch | 3 years ago

Could someone explain why refactoring is so much easier in functional languages?

discuss

order

tadfisher|3 years ago

I don't think it's a universal property of functional languages. Haskell is also strongly (excessively) typed, down to the level of modeling computation itself, and it's lazy.

See, when you are defining a Haskell program, you are conceptually creating a tree of thunks that eventually get executed by the GHC runtime. Those thunks are typed, meaning they have typed inputs and outputs, and are either side-effect-free or are defined in a context that controls their side effects (e.g. the IO monad).

So you can change the definition of types willy-nilly and either get a working compiled program or some error output that tells you exactly what is broken or doesn't make sense to GHC's model of your proposed computation. Because computation itself is typed, you have a stronger guarantee that it will work as expected when executed by the GHC runtime. Because side effects are controlled, you are forced by the type checker to handle runtime errors (or crash).

At least that's how I understand it as someone who works with gifted Haskell engineers, but exists very much on the periphery of understanding.

foldr|3 years ago

> Because side effects are controlled, you are forced by the type checker to handle runtime errors (or crash).

This is generally true in idiomatic Haskell code, but in fact even pure functions in Haskell can throw runtime exceptions, and you are not forced to handle these.

lmm|3 years ago

A functional language is fundamentally one where the same inputs always produce the same outputs. So you can e.g. change the order of two function calls and be confident that that's not going to change the behaviour, without even running it. In a language with pervasive state mutation, essentially any change you make to the code might change what the program does, so you have to test every little thing. https://www.lihaoyi.com/post/WhatsFunctionalProgrammingAllAb...

fprotthetarball|3 years ago

Type systems of functional languages are generally capable of representing more. You can have the type system validate application state at compile time for you, for example.

If it compiled before and worked and your refactored version also compiles, chances are you didn't break anything.

fho|3 years ago

> If it compiled before and worked and your refactored version also compiles, chances are you didn't break anything.

And I would say that if somebody never worked with Haskell (or some other language with a strong type system like Idris) they can bit fantom what is possible to encode in the type system.

jim-jim-jim|3 years ago

One practical example: if you have a union of A|B, and you decide to add a third shape C to it, your program won't compile until logic for C is written in all the places where A and B are already being matched against. Refactoring often means changing data models, then letting the errors walk you through the actual implementation details.

garethrowlands|3 years ago

Yeah that's how it's supposed to work. Historically, Haskell didn't always make incomplete patterns an error, even now the checker isn't perfect, and even if it was people can still put wildcard patterns.

jeofken|3 years ago

With mutable state every function has an implicit dependency on other stuff. Without mutable state your function depends on its arguments, and produces only a return value. No moving parts or implicit dependencies = you can cut and paste stuff around the code base like there is no tomorrow.

parenthesis|3 years ago

Because it is more difficult to make a change that affects other code without a change in types occurring, which will make compilation fail until all affected code is updated.

garethrowlands|3 years ago

Though Haskell has the option to defer type errors to runtime, making them just warnings at compile time.

It means you can run your unit tests without having to change everything everywhere all at once.

The flag is -fdefer-type-errors

sterlind|3 years ago

In addition to the type system, I'd also add purity. If you have a function f :: a -> b, there is literally no way for f to read anything besides a, or affect anything besides returning b (aside from unsafePerformIO, which you can ban from your code.) so if you want to refactor f, you know exactly from the call site everything that needs to be updated.

all state is factored, so it can easily be refactored.

WastingMyTime89|3 years ago

It’s mostly the type system. Ocaml is the same. Static typing, type inference and the easiness of introducing complex types really help when it comes to fitting the code together. Beginners tend to think that "if it compiles it works" and it feels that way sometimes but you lose the hubris once you are bitten by a bug complicated enough to pass though.

goto11|3 years ago

Perhaps it is easier because Peyton Jones is a world class expert in Haskell and have 30 years experience refactoring it?

In my experience the ease of refactoring is more depending on the quality of the code you are refactoring than the language. That said, a strong type system helps avoiding stupid mistakes and Haskell have a very strong type system.

fho|3 years ago

I guess it is a bit if both. You can of course write fast and loose Haskell code, but best practices will probably prevent you from doing that.

agumonkey|3 years ago

almost no, if at all, state

you can unplug, replug stuff at will