proj-plbook-plChFuncLangs

Table of Contents for Programming Languages: a survey

Haskell

Because it is so well-liked, Haskell gets its own chapter.

See chapter on chapter on Haskell

Scala

Scala is a multiparadigm language, both OOP and functional.

Tours and tutorials:

Cons:

Best practices:

Tutorials and books:

Comparisons:

Features:

The AbsCell? class has an abstract type member T and an abstract value member init. Instances of that class can be created by implementing these abstract members with concrete definitions.

val cell = new AbsCell? { type T = Int; val init = 1 } cell.set(cell.get * 2)

The type of cell is AbsCell? { type T = Int }.

Path-dependent Types: You can also use AbsCell? without knowing the specific cell type:

def reset(c: AbsCell?): Unit = c.set(c.init);

Why does this work?

In general, such a type has the form x0...xn.t, where

Some notes on Scala 3 features:

example of scala 3 'givens' which are (part of) the successor to scala implicits:

https://alvinalexander.com/source-code/scala-3-dotty-complete-givens-using-example/

so afaict these are just similar to Haskell typeclass instances. I guess what scala is really contributing is that i think these are lexically scoped, rather than globals like (i think) in Haskell.

Extensions:

Retrospectives:

Implementations:

Opinions:

*> . Someone said out loud “what the hell is that?”. There was some other implicit magic going on that wasn’t immediately apparent. A CMD+B to traverse into the method yielded nothing in our IDE as it couldn’t find the symbol (IDE’s have improved here since). A quick googling of “<*>” yielded nothing as well....That blip turned a fix that should have taken minutes into a fix that took hours....(((another example of hard to read code))) https://gist.githubusercontent.com/jiminoc/ea827cfaf0bb8b07df6b/raw/e6402f48c2a97d537c7d80ea1284f9d3a380730d/sample.scala....." -- [13]

Scala gets a bad rap precisely because people are big fans of their own way of writing code, call it better, and think that anyone that wants something else is deluded. The Scala way is to say 'sure, you can do that too, to hell with purity'. I'd rather have power over purity any day.

reply

duaneb 36 minutes ago

> Scala's strength is precisely that it's a mutt: Want to write imperative code? Sure. Functional? No problem. How about a type system that far more featureful than Go? That works too.

This also essentially describes C++. This is also why both languages can be so terrible to use: every library has its own dialect.

reply

the_af 20 hours ago

Note: I use Scala in my day job. I consider it better than Java, but worse than other languages. I definitely agree that all languages accumulate compromises and inelegant hacks as they evolve. It's unavoidable.

That said, in my opinion Scala's blunders are many. You can find out about many of them from Paul Philips, ex committer of Scala, but if he sounds too bitter to you (he does to me; at this point he sounds like there is bad blood between him and Odersky), here are some of mine:

reply

airless_bar 20 hours ago

> all languages accumulate compromises and inelegant hacks as they evolve. It's unavoidable.

Scala devs deprecate and remove things that haven't worked out well. They have an established track record of making these migrations easier with each release.

> Null

Upcoming versions will make references non-nullable by default. If you want things to be nullable, you will have to opt-in explicitly with T

Null.

> - Type inference

Type inference is not a huge priority, because people think that it's a good thing that declarations require type annotations (just like it's recommended in Haskell, too). One of the last pain-points, higher-kinded unification of type constructors, has been fixed recently which will make type inference "just work" in exactly those cases where the lack of type inference was most annoying.

> - Tooling

I think tooling is pretty amazing. Sure, Java has still some advantages in some places. But the tooling is superior to almost all other languages out there. SBT is amazing. IDE support is getting vastly better, not only have Eclipse and IntelliJ? improved vastly, but also IDE support for Emacs, Vim, Sublime, etc. is coming along at a rapid rate, and it's just amazing to use. There are so many tools in the Scala ecosystem which just don't exist in this combination anywhere else.

airless_bar 19 hours ago

Alright. Sorry.

Regarding your question about nulls and Java:

I think that there is not much Scala can do here. All existing evidence from other languages that tried this shows that there is a large semantic gap between "nullable values" and "values of unknown nullability".

As Scala is much more independent of Java it is a much smaller issue, but improvements with Java interop require action from Java library authors first.

The approach of supplying external nullability meta data has largely been a failure, because

a) authors are not aware of the stated, external constraints, so they could break these assumptions in future releases without even knowing

b) it's really really hard to retrofit nullability guarantees into libraries which have been designed without this requirement in mind

c) the existing ecosystem is not very amenable to these guarantees, as nullability metadata would be largely limited to final classes and members, because everything else could be subclasses or overridden in third-party code, breaking these assumptions

As soon as Java library authors themselves start using @Nullable/@NonNullable? annotations everywhere, there is a good opportunity of reading these annotations and typing the values accordingly, but without it, benefits are very slim.

The planned changes are valuable for dealing with nullable types, but as mentioned "unknown nullability" needs a different solution, and I think it's currently not worth adding something to the languages as long as there is still hope for widespread adoption of nullability annotations.

reply

bad_user 17 hours ago

Btw, if you ask a C++ developer to describe OOP, he'll say it's the ability of having structs with a VTable attached. Most people think OOP is about state. That's not true, OOP is about single (or multi) dispatch and subtyping (having a vtable), hence it's not at odds with FP, unless you want it to be ;-)

And let me give an example: in IntelliJ? IDEA I can click on any identifier in any third-party dependency and it automatically downloads and shows me the source code and I can set breakpoints and debug any third-party dependencies that way. Such a simple thing, yet it's a tough act to beat. Try it out in Visual Studio sometimes.

reply

asragab 16 hours ago

What languages would you say have definitely better tooling: I'd say for sure the .NET languages, C++, Java, and Javascript but I'd put Scala just at a level below that, no? The tooling stories for Go, Rust, Haskell I don't think are as strong (yet), but that's definitely going to change though.

reply

airless_bar 16 hours ago

Not parent, but I would reduce it down to

Why not the others you mentioned?

reply

pjmlp 11 hours ago

reply

"

Opinioned comparisons:

Scala examples

" To give a feel for the language, here’s a Scala implementation ofnatural numbers that does not resort to a primitive number type.

trait Nat { def isZero: Boolean; def pred: Nat; def succ: Nat = new Succ(this); def + (x: Nat): Nat = if (x.isZero) this else succ + x.pred; def - (x: Nat): Nat = if (x.isZero) this else pred - x.pred; }

class Succ(n: Nat) extends Nat { def isZero: Boolean = false; def pred: Nat = n }

object Zero extends Nat { def isZero: Boolean = true; def pred: Nat = throw new Error("Zero.pred"); } " -- [26]

(S)ML

Pros:

Tutorials and books:

Comparisons and opinions:

Wishes:

Implementations:

Links:

Variants:

Retrospectives:

Ocaml

Tours and tutorials:

Features:

Library recommendations:

Retrospectives:

Updates:

Opinions:

Ocaml Opinions:

Ocaml Opinionated Comparisons:

> or <etc." -- l1x

Variants and implementations:

Charity

https://en.wikipedia.org/wiki/Charity_(programming_language)

F#

Features:

Tours and tutorials:

Recommended libraries and best practices:

Opinions:

Comparisons:

> or <etc." -- l1x

Variants and implementations

Iverson array languages

See also [69] for APL, A+, J, K, Q, some of which are sometimes called functional or function-level languages.

Elm

Tutorials:

Opinions:

    The only thing that seems missing from the alternatives is something like Elm’s Element static layout library, which I need for some of the things I’m doing. But it seems like a reasonable path forward might be to just write such a library or port Elm’s to whatever tech I end up using." -- https://pchiusano.github.io/2015-04-23/unison-update7.html 

Retrospectives:

Erlang

" Here is how I think Erlang works. I believe Akka is very similar.

Each process has a single mailbox. Messages are put into the receiver's mailbox by the sender, and fetched by the receiver using pattern matching. This matching process can change message ordering in the sense that the oldest message in a mailbox may not match, but a younger one does. In this case the younger one is consumed first. Other than that, message ordering is preserved. "-- [73]

Pros:

Cons:

Features:

Tutorials:

Overviews:

Books:

Best practices:

Respected exemplar code:

Notable distributed libraries:

Retrospectives:

Opinions:

Intelligent Systems [Hewitt 2019], the following extensions

to Erlang are needed:

• Automatic reclamation of processes. For example,

Erlang uses Process Identifiers (PIDs) to communicate between

processes. However, a process that has a process identifier

(say ProcessIdentifier?) of a process with which it

communicates can launch a denial of service attack to kill

that process using exit(ProcessIdentifier?, kill). An Erlang

process can be orphaned if its Process Identifier (PID)

becomes inaccessible. By default, the send operation in

Erlang always succeeds (even if the target is a non-existing process).

• Strong parameterized types with no type Any.

• Region of mutual exclusion for an Erlang process.

An Erlang process does not have a region of mutual exclusion,

which can lead to internal conflicts within processes.

• Behavior change using variables in a region of mutual

exclusion instead of having to create auxiliary processes to

hold state with callbacks. [Swain 2014] In particular,

o Better support for holes in the region of mutual exclusion

of an Actor implementation. For example, in Erlang it is

necessary for application programmers to explicitly code a

request handler for each re-entry into the region of mutual

exclusion potentially enabling cyberattacks from outside the

process.

o Better ways to upgrade Actors in place without losing work in progress. " Carl Hewitt

Comparision opinions:

Types

[109]:

[110]:

Erlang: Examples

A message queue:

-module(messagequeue).
-export([new/0]).
-define(TIMEOUT, 300000). 

new() ->
  queue([]).

queue([]) ->
  receive 
    {get, PidReturn} ->
      returnnextadd(PidReturn);
    {add, Message} ->
      queue([Message]);
    message_received ->
      queue([]);
    empty_queue ->
      exit(self())
    after ?TIMEOUT ->
      exit(self())
  end;

queue(Messages) ->
  receive 
    {get, PidReturn} ->
      [Next | _] = Messages,
      PidReturn ! {ok, Next},
      queue(Messages);
    {add, Message} ->
      queue(Messages ++ [Message]);
    message_received ->
      [_ | Remaining] = Messages,
      queue(Remaining);
    empty_queue ->
      exit(self())
    after ?TIMEOUT ->
      exit(self())
  end.

returnnextadd(Pid) ->
  receive
    {get, PidReturn} ->
      Pid ! {error, new_client},
      returnnextadd(PidReturn);
    {add, Message} ->
      Pid ! {ok, Message},
      queue([Message]);
    message_received ->
      queue([]);
    empty_queue ->
      Pid ! {error, empty_queue},
      exit(self())
    after ?TIMEOUT ->
      exit(self())
  end.

-- from [111] (Apache License, Version 2.0)

(syntax: "sending a message is "address ! message"" [112])

Erlang Internals

"An Erlang process is lightweight compared to operating systems threads and processes. A newly spawned Erlang process uses 309 words of memory...The size includes 233 words for the heap area (which includes the stack). The garbage collector will increase the heap as needed." [113]

Erlang is the culmination of twenty-five years of correct design decisions in the language and platform. ...

Take garbage collection. When it's time to collect garbage in other languages, the entire system has to stop while the garbage collector runs. This approach is perfectly fine if your computer program is supposed to run once, write some output, and then quit. But in long-running applications, such as desktop, mobile, or server programs, this strategy results in occasionally frozen UIs and slow response times. Erlang programs, on the other hand, can have thousands of independent heaps which are garbage-collected separately; in this way, the performance penalty of garbage collection is spread out over time, and so a long-running application will not mysteriously stop responding from time to time while the garbage collector runs. ... Or take string concatenation. If you pop open the implementation of string concatenation in Perl, Ruby, or JavaScript?, you are certain to find an if statement, a realloc, and a memcpy. That is, when you concatenate two strings, the first string is grown to make room for the second, and then the second is copied into the first. This approach has worked for decades and is the “obvious” thing to do. Erlang's approach is non-obvious, and, I believe, correct. In the usual case, Erlang does not use a contiguous chunk of memory to represent a sequence of bytes. Instead, it something called an “I/O list” — a nested list of non-contiguous chunks of memory. The result is that concatenating two strings (I/O lists) takes O(1) time in Erlang, compared O(N) time in other languages. This is why template rendering in Ruby, Python, etc. is slow, but very fast in Erlang. " [114]

Erlang implementations

Erlang variants

Links:

Elixir

Targets the Erlang VM (BEAM).

Elixir Tutorials and books

And Elixir frameworks:

Elixir Anti-features

Elixir Opinions

"Not that you should do it too much since traversing arrays (lists) is expensive, but http://elixir-lang.org/docs/stable/elixir/Enum.html#at/3 gives you the ability to grab arbitrarily." [123]
tail recursion) and hashes (nothing like hash) to make something "work" but the pipe operators and pattern matching are addictive." [122]
>. It works just like the unix pipeline and shuffles the return value of function A into the argument of function B. Instead of writing b(a()) you can write a()> b(). There are many more things I like, for example pattern matching, the built in Stream module, extensibility via macros and protocols, documentation in markdown format, shared nothing concurrent programming (actor model) and much more." [124]

Elixir Comparisons

Elixir BEAM vs JVM opinionated comparisons

Elixir Notes

Elixir Gotchas

" This is more for people looking at erlang/elixir than a critique of the blogpost or a suggestion for a change.

> Within Elixir, there is no operator overloading, which can seem confusing at first if you want to use a + to concatenate two strings. In Elixir you would use <> instead.

When this popped up, it reminded me of something people try to do often and then have issues with performance. You probably do not want to concatenate strings.

"Yes I do" you'll first think, but actually erlang has a neat commonly used thing to help here.

Let's say you're doing some templating on a web-page. You want to return "Welcome back username!". First pass (a while since I wrote erlang so forgive syntax errors):

    welcome(Username) -> 
        "Welcome back " ++ Username ++ "!"

Now it's going to have to construct each string, then create a new string with all three. More creation & copying means things get slower.

Instead, many of the functions you'd use to write files or return things over a connection will let you pass in a list of strings instead.

    welcome(Username) -> 
        ["Welcome back ", Username, "!"]

Now it's not copying things, which is good. But then we want to put the welcome message into another block with their unread messages.

    full_greeting(Username) ->
        welcome(Username) ++ unread_messages()

More appending than is good here, concatenating lists is going to take time. Of course, we could put it all in one function, but then we lose re-usability in the templates and have horrible massive functions. While this is a simple example, I hope you can picture a larger case where you'd want to split up the various sections.

Anyway, there's a better way of doing this. The functions that take lists of strings actually take lists of strings or other lists. So we can just do this:

    full_greeting(Username) ->
        [welcome(Username), unread_messages()]

You can keep going, nesting this as much as you want. This saves a lot of copying, allows you to split things up and avoids having to keep flattening a structure.

So, for people about to get started, try not to concatenate your strings, you can probably save yourself and your computer some time.

For more info on this, you want to search for "IO Lists" or "Deep IO Lists". " -- IanCal?, [158]

Elixir libraries and best practices

Alice

https://en.wikipedia.org/wiki/Alice_%28programming_language%29

?also said to have some constraint programming?

Oz

https://en.wikipedia.org/wiki/Oz_%28programming_language%29

?also said to have some some logic programming?

Tutorials:

Oz Kernel language: http://mozart2.org/mozart-v1/doc-1.4.0/tutorial/node1.html#chapter.introduction section 1.2

Other functional toy languages

POPLMark's FSub


Supervision trees

" In Erlang, if a process faults and it has a supervisor, the supervisor gets a message that one of its children died with some additional metadata, it can then log the issue, restart a child, … This also led to an interesting "let it crash" philosophy (since Erlang processes have isolated private heaps a process dying also cleans up any potential data corruption in the process's private state, this is often considered a feature, but at its core it mostly avoids processes dying unnoticed.

Incidentally, supervision is actually the result of two individual features: linking and trapping. Linking means that when one process dies with an abnormal status (any reason other than regular process exit), all process linked to it will also die (the EXIT signal gets propagated through the link), which is repeated until all linked processes are killed, trapping lets a process handle the incoming exit signal in code rather than automatically suicide

Finally, because supervisors and supervision trees are so common the Erlang standard library provides generic behaviours which let developers declaratively set up which strategies they want in their supervisor, the maximum restart frequency (beyond which the supervisor consider the system's broken and shuts down), ... " [159]