Live data from Hacker News

Ask HN: How to be fluent in functional language speak?

news.ycombinator.com

61–70 of 112 posts

Re: Ask HN: How to be fluent in functional language speak?

#61

Earlier quoted context omitted.

Because names don’t carry meaning, they’re pointers: https://www.parsonsmatt.org/2019/08/30/why_functor_doesnt_ma... “Functor,” points to a specific concept in Haskell and a slightly different one in Category Theory but otherwise it is fairly unambiguous given enough context. The concept not only refers to the type and the associated “map” operation but also the axioms of identity and composition and their properties…

Well, Functor is a bit of a misnomer; it really should be called TypeEndofunctor. The short form was really adopted as a mischievous pun on the use of "functor" in the OOP community.

I don't think TypeEndofunctor conveys any additional information when discussing a Haskell typeclass.

> The short form was really adopted as a mischievous pun on the use of "functor" in the OOP community.

Citation needed.

Re: Ask HN: How to be fluent in functional language speak?

#62
post #57
post #45

Earlier quoted context omitted.

I don’t think many people programming in dynamic languages would die on that hill. Avoiding types is kind of the allure, I think?

At the expense of having to write more tests that a type system could have solved at compile time, and likely catch more runtime logic bugs as well. For the record I am a convert. Types have made my developer life much happier, especially working with unfamiliar code across multiple projects/teams. When you treat your builds as long term proofs your confidence level increases dramatically.

I don’t disagree :)

Re: Ask HN: How to be fluent in functional language speak?

#63
post #48

Earlier quoted context omitted.

Not sure about Scheme, but OCaml's type system is not powerful/expressive enough that (to use the given example) abstracting over type constructors is likely to ever come up. (No HKTs in OCaml.) Also, OCaml uses a naming scheme for certain things that I haven't seen used elsewhere - e.g. what OCaml calls a "Functor" is a parameterized module, not closely related to Functors in other languages that use that abstractio…

OCaml functors come from Standard ML, which predates Haskell. And you can definitely use them to implement HKTs — they are comparable in expressiveness to single-parameter typeclasses.

I use OCaml at work ~every day, and HKTs (to the extent they can be expressed at all, using HOMs) are so unwieldy that I have never run across them in production code. In Haskell, they are both easy and routine. So I’m approximating this with “no HKTs in OCaml”.

Re: Ask HN: How to be fluent in functional language speak?

#64

Earlier quoted context omitted.

> We've "abstracted (the function map) over the type constructor (of the data structure being mapped over)". Then why didn't they just call those types "Mappables"?

Because names don’t carry meaning, they’re pointers: https://www.parsonsmatt.org/2019/08/30/why_functor_doesnt_ma... “Functor,” points to a specific concept in Haskell and a slightly different one in Category Theory but otherwise it is fairly unambiguous given enough context. The concept not only refers to the type and the associated “map” operation but also the axioms of identity and composition and their properties…

> Because names don’t carry meaning, they’re pointers:

That's been debated in philosophy for over 150 years, and there are many alternative theories.

I think a better point is that technical names don't matter because their formal meaning may not correspond with any word, and if there is an everyday word, it's probably slightly but importantly different.

Re: Ask HN: How to be fluent in functional language speak?

#65
> So how does a programmer from non-functional world become fluent in understanding sentence such as "abstraction over type constructors"?

Incoming essay...

Pure functional programming is fundamentally about software components called "pure functions", or just "functions" for short. (I'm quoting the term because they're not the same as what are called functions in other languages. I'd call those other things "procedures" instead, but alas.) Other programming communities rally around components like "objects", and that's fine. The important thing is to pick something to break your system down into, and here, we're talking about "functions".

"Functions" are simpler kinds of components than objects or procedures: the ways in which they behave are much more limited, so they're easier to reason about. When a "function" is interacted with, it can't perform any side effects, so interacting with the "function" multiple times gives the same behavior every time. Procedures can manipulate global state, and objects can manipulate internal state, so reasoning about them takes more effort. You have to keep track of time: what happened before this interaction?

"Functions" have two interfaces: an input side and an output side. When a "function" receives a value on the input side, it will always emit a value on the output side. We can wire "functions" together by connecting the input of one to the output of another. The result is a system with a single free input side and a single free output side -- that is, it's also a "function". This makes it very easy to wire up lots of "functions".

Usually, a "function" has certain expectations of the input values that it receives, as well as some guarantees about the output values it emits. In a dynamically typed language, when those expectations are not met, a runtime error is emitted. This is fine -- it's just one way to handle failed expectations. But sometimes we can formalize those expectations statically, and let the compiler check up-front whether those expectations are met. This does add some complexity: you might not be able to wire up two "functions" if one can't satisfy the other's expectations. Statically-typed programmers have decided that they're willing to put up with that.

In a statically-typed world, the input and output interfaces of our "function" software components can be tagged with specifications. Functional languages are often judged by how expressive these specifications can be; a specification language like this is called a "type system". You can have type systems for components that are not "functions", but the rules of composition become more complex. (For "objects", class systems are quite popular.)

Most type systems allow you to break down an interface specification into smaller pieces, which themselves are valid specifications. That means we now have two kinds of components in our system: "functions", which exist at runtime, and types, which exist at compilation time. The rules for how types compose can be more complicated than those for "functions", but we're usually okay with that, as long as "functions" are kept simple.

Some type systems allow a value to satisfy multiple types. For example, class-based systems allow this via subtyping relations. Others might describe types as predicates (truth functions) on a pre-existing universe of values (e.g. TypeScript). Pure functional programming typically requires mutual exclusion: a value cannot be part of multiple types. We often say the the type defines its values because of this.

The low bar for a static type system is "algebraic types". This means that we have two ways to compose types, conventionally called "product" and "sum". Most type systems have products, but sums are historically rarer. Values of a product type `X * Y` look like `(x, y)`, and we can pull out either element of the pair. Values of a sum type `X + Y` look like either `Left x` or `Right y`, and we can ask which one it is and do something different depending on the result.

Java and C++ do not have sum types. You can approximate their specifications to some extent using the Visitor pattern or discriminated unions, but the implementations in these languages are verbose, and unpleasant to read and write. Rust and Haskell do have sum types, so specifications of that nature are used much more readily.

We can generalize further. We can think of the product and sum of types as "type functions", or "type constructors", that take a pair of types as input and produce a type as output. Many type systems allow you to define your own "type functions". In Java, these are generic classes: "List" is a type function from some type T to the type of lists containing that type. In C++, these are templates. But in most pure functional programming, static type functions look just like runtime value functions, since they behave just like them too. It's just a matter of when they're evaluated.

Once again, when you have functions, you often want to place demands on their inputs, and guarantees on their outputs. Type functions are no different. There are many possible ways to "type types", as it were, but a popular approach is by using "traits" or "typeclasses". Just as with types and values, a typeclass places requirements upon the types that inhabit it. (But a type can also satisfy multiple typeclasses.)

For instance, "Monoid" is a typeclass on types with two associated items: a "zero" value of that type, and a "concat" function that takes two values of the type and gives another value of that type. If you know that a type is a Monoid, then you know that these associated items are also available to you, regardless of what the actual type is. (For instance, you can write a "concatAll" function that iterates over a list of elements of a monoid and concatenates them together regardless of what the monoid type actually is.)

With typeclasses in mind, instead of taking a "list of X" or a "set of X", you might abstract over all such things as "collections of X". But you don't want to say what a collection is for every possible X. We need to abstract over type constructors. A "collection" is any type such that, given a type X, you get a type with certain associated items expected of all collections. Java approximates this with subtyping.

"Functor" is, in some ways, a generalization of "Collection". A type function that is a "Functor" is a type that, if you have a value of that type over Xs, and you have a function transforming Xs into Ys, then there must be a mechanism to get a value of that type over Ys instead. We call this mechanism "map", and it can be instantiated over any collection. Got a list of X and a function from X to Y? We've got a way to get a list of Y. Got a set of X and a function from X to Y? We've got a way to get a set of Y.

Because "set" and "list" are type constructors, we say that we are abstracting over type constructors to get a single statement about all such things. "For every Functor F, if I have an F containing Xs and a function transforming Xs to Ys, then I can get an F containing Ys."

Re: Ask HN: How to be fluent in functional language speak?

#66

First, you probably don't need to read academic articles to learn about functional programming. There's really nothing magical or complicated with functional programming. For instance, Scheme or OCaml are routinely used as beginner programming languages in schools around the world. You can start with the basic concepts and eventually build your way up to more abstract constructs when you realize you need them. But ev…

I'd go a step further and say trying to learn all the convoluted language and terminology actually does very little to understand the benefits of functional programming in the real world and it should best be avoided.

It's, in my opinion, better to just grab a functional language, built some software with it, and learn about the benefits and disadvantages of functional languages by interacting with them.

Re: Ask HN: How to be fluent in functional language speak?

#67

Earlier quoted context omitted.

> We've "abstracted (the function map) over the type constructor (of the data structure being mapped over)". Then why didn't they just call those types "Mappables"?

To be fair, the term "map" comes from math [0], and so at the time these functions were created, there weren't other good naming choices. Mappable and Functor are just about as opaque as each other if you don't know what mapping is. Though now that FP concepts are mainstream, I think there is a good case to offer some friendlier, more familiar names: Functor -> Mappable Applicative -> Pairable Monad -> Thenable [0]:…

Giving descriptive names just harms intuition building in the long run.

I've been writing Haskell for over 5 years and to this day when I see , , >>= in my code I don't substitute English words for them.

Re: Ask HN: How to be fluent in functional language speak?

#68
Keep trying to understand, asking questions, trying stuff first hand and eventually it'll come! The "Hask anything" sticky in /r/haskell is a friendly & pseudonymous way to ask questions too.

One thing I've noticed working in Haskell: Everyone is an expert in different things. Very rarely do I meet a Haskeller whose knowledge or interests subsumes another. That's part of the fun. Everybody doesn't know some terms!

Re: Ask HN: How to be fluent in functional language speak?

#69
I got comfortable with Lisp using http://www.4clojure.com/

Learning through games like this is a big help because they provide: 1. clear and approachable goals 2. clear feedback on whether you've achieved the goals

Maybe Clojure isn't what you're looking for but maybe there's a similar service for a language you'd like to learn.

Post reply on HN