auto addone = [](auto x) { return x+1; };
That monstrosity at the top of the article is needlessly complex because C++11 didn't support generic lamdbas. addone1(x) = x+1
Completely generic with type inference, and compiles to optimal machine code. But it also suffers from the "latent type errors deferred to the user" that the article discusses. Calling it with a type that does not support adding 1 will raise an error for a non-existing "+" method: addone1("0")
ERROR: MethodError: no method matching +(::String, ::Int64)
This is pretty understandable in this case, but we may prefer annotating the addone function so that it can only be called with types that support adding 1. Using the (very conservative) notion that only numbers can have 1 added to them, we could use Julia's type hierarchy to restrict the types for which addone may be called: addone2(x::T) where T<:Number = x+1
Calling it with an incompatible type now properly points to the outer level: addone2("0")
ERROR: MethodError: no method matching addone2(::String)
But this type of dispatch restrictions rely on a type hierarchy, causing similar problems as class hierarchies in OO languages. For example, the user might have defined their own type that supports addition but not multiplication. Using addone on this type makes sense, but the type shouldn't be a subtype of Number. Such more flexible dispatch scenarios can be achieved with traits. Although Julia currently doesn't have direct language support for traits, they can be implemented inside the language, with macros for syntactic sugar. With traits, the previous example becomes: using SimpleTraits
@traitdef CanAddOne{T}
@traitimpl CanAddOne{T} <- issubtype(T,Number)
@traitfn addone3{T; CanAddOne{T}}(x::T) = x+1
Still relatively straight-forward to write and read, and the trait-restricted addone has the same performance as the original one. For incompatible types, the error message still points to the outer level: addone3("0")
ERROR: MethodError: no method matching addone3(::Type{SimpleTraits.Not{CanAddOne{String}}}, ::String)
Importantly, the user can extend the CanAddOne trait (with additional @traitimpl lines) to cover their own type, without being forced to make it a subtype of Number. add1 = (+1)
> add1 23
24
> add1 2.01
3.01
> add1 "0"
No instance found...
But that error is at compile time.My Lisp-fu isn't as strong as my C++-fu so someone correct me if I'm wrong but isn't the GC an intrinsic part of Lisp? Do more modern Lisps allow you to mark value types so you can control memory access patterns(which is where the true speed of C/C++ comes from).
Arrays are also available, including specialized versions that hold value types.
https://www.cs.cmu.edu/Groups/AI/html/cltl/clm/node15.html
You can also stack allocate if required, via dynamic-extent, http://clhs.lisp.se/Body/d_dynami.htm
Also not all Lisps have a tracing GC, some variants had a RC with tracing GC for collecting cycles.
RAII like patterns can be achieved via the with-.... functions, or macros.
I don't know the actual performance of commercial Lisps like Allegro Common Lisp and LispWorks, but I imagine it is quite good, given that they stay in business.
On the other had, given the amount of money spent in C and C++ optimizers vs the lack of industry wide adoption of Lisp, probably still not as good as current leading C++ compilers.
In TXR Lisp, RAII is supported thusly:
This is GC finalization of a struct:
This is the TXR Lisp interactive listener of TXR 172.
Use the :quit command or type Ctrl-D on empty line to exit.
1> (defstruct animal nil
(:fini (me) (put-line `@me says good-bye`)))
#<struct-type animal>
2> (progn (new animal) nil) ;; make animal without referencing from REPL
nil
3> (+ 2 2)
4
4> (sys:gc)
#S(animal) says good-bye
t
OK, now with-objects macro: 5> (with-objects ((a (new animal)))
(prinl a))
#S(animal)
#S(animal) says good-bye
#S(animal)
with-objects invokes finalizers explicitly, before objects become unreachable.Also, what if a constructor throws? Let's derive animal to dog which bails at `new` time:
6> (defstruct dog animal
(:fini (me) (put-line `@me: woof woof`))
(:postinit (me) (error "refuse to construct")))
#<struct-type dog>
7> (new dog)
#S(dog): woof woof
#S(dog) says good-bye
** refuse to construct
** during evaluation at expr-6:3 of form (error "refuse to construct")
The object instantiation logic catches exceptions and invokes finalizers on a partially constructed object (in the proper order as you can see: derived, then base).It might be good or just OK on modern hardware. I imagine they stayed in business for their IDE's, libraries, commercial support, and decent compilers. Batteries included. Some success stories make me think performance is really good, though.
http://www.lispworks.com/success-stories/netfonds-primetrade...
http://www.lispworks.com/success-stories/ral-siglab.html
I found those while looking for one about a real-time implementation for telecom or something. Franz's success stories are mostly about doing complicated stuff easier. There was one that looked performance-critical:
http://franz.com/success/customer_apps/finance/ravenpack.lht...
Performance is not one easy number. Applications have different performance requirements. In many benchmarks for typed or type inferred code SBCL tends to be slightly faster with less programming effort.
Fast can mean for example:
* fastest possible execution of optimized code
Then one might not care about code size, code safety, robustness, threading capability, interrupts, .
* fast execution of non-optimized robust, flexible, reflective, debuggable code.
Allegro CL and LispWorks integrate a lot of features. These features are provided over some amount of platforms, with only few restrictions. They provide relatively good performance of a native code compiler. Some robustness is needed for commercial applications, thus I would expect an advantage there.
On the other hand, the default in Lisp is always to let the compiler handle memory, but Common Lisp in particular gives the programmer a lot of flexibility and control over types, memory management, and other optimizations. It's been fighting the "Lisp is slow" stereotype for a long time, so there's been a lot of work done to optimize it and give the programmer optimization options.
For your specific question about memory access patterns, Common Lisp does allow some control over that via the "dynamic-extent" declaration: (declare (dynamic-extent variable-name)). It tells the compiler (or interpreter) that a variable in an inner scope (of a loop, for example) can be allocated once and the space reused each iteration instead of allocating fresh memory each time. It's not full blown C style control of memory, but it's similar and can have a big impact in some situations. The book "Common Lisp Recipes" has a section on it, and so does the hyperspec: http://www.lispworks.com/documentation/HyperSpec/Body/d_dyna...
The recently published "Common Lisp Recipes" is a great book that covers a ton of topics in this area. I think a lot of people would be surprised just how much control and flexibility is available in Common Lisp.
I wouldn't say Common Lisp is faster than C++ in general, and certainly not by default, but with a bit of work it's possible to get pretty close. Importantly, the optimized code will still look and feel like more or less idiomatic Lisp.
However, the set of problems I can tackle is going to be bounded by the escape-hatches that are available to me. Much like Rust has `unsafe {}`, being able to drop down to the bare metal is an important tool to be used at the appropriate time.
Is hoax a placeholder for "hype" or am I missing something?
Data structures and allocators, surely yes.
One writes ordinary generic functions, and then optimized typed methods in lisp like this:
(defgeneric xplusone (x) (1+ x))
(defmethod xplusone ((x integer)) (1+ x))
(defmethod xplusone ((x double-float)) (1+ x))
The sbcl compiler (called python) even creates the typed methods by itself, so mostly the defgeneric line is enough.
The type hints for args and return types are purely optional, as the compiler figures it out by itself.He is right that algorithms, methods, trump data structures, objects. You always write methods with specializations on objects. Not the other way round, classes with specific methods.
> The sbcl compiler (called python) even creates the typed methods by itself, so mostly the defgeneric line is enough.
Is this some new SBCL extension you are talking about? That `defgeneric` line is an error in CLOS.
Think of defgeneric as the function signature and defmethod as the template specialization. Not sure why you say this is an error in CLOS. Looks fine to me.
That said, most implementations try to auto-infer the generic function metaobject when you use defmethod without defgeneric. SBCL raises a warning.
Good reads on the topic:
http://www.gigamonkeys.com/book/object-reorientation-generic...
http://www.softwarepreservation.org/projects/LISP/parallel
Often parallel computers had also a Lisp implementation. Much more than listed above.
Parallel Computation and Computers for Artificial Intelligence http://link.springer.com/book/10.1007/978-1-4613-1989-4
Parallel Lisp: Languages and Systems http://link.springer.com/book/10.1007/BFb0024148
Parallel Symbolic Computing: Languages, Systems, and Applications http://link.springer.com/book/10.1007/BFb0018643/page/1
template<typename Scalar, typename Functor>
auto
DoSomething(Scalar value, std::size_t k) -> decltype(Functor(Scalar(0))
{
...
Functor(value / Scalar(k));
...
}
Even though it will probably only be used once. Simply because I don't know what types I am going to need yet... but I know it might need a few operators `+`, `-`, `*`, `/`. Once I figure that out, the code is ready to go and is as fast as anything hand written.If somebody could ever write a great 'Haskell for C++ MetaProgrammers' book describing how you are supposed to understand binary layout, IO, and wtf those hundreds of operators mean... you would probably have a bunch of programmers saying "Oh, I guess I know Haskell".
It's also no surprise that a bunch of the STL algorithms structures were easily made parallel in C++17. A lot of developers using the STL correctly could basically change a few lines of code and switch their program from sequential to parallel.
That may be a bug, not a feature. The Boost crowd won the battle, making extremely complex templates an essential part of the language. But they may have lost the war, as C++ loses market share.
LISP backed into typing, and it shows. Both typed variables and objects are painful in LISP. By the time LISP got both, the era of LISP was over. LISP is really dead now; there hasn't been a release of GNU Common LISP ("clisp") in 7 years.
Allegro CL 10.0 released on 2015-10-05
ABCL 1.4.0 released on 2016-10-08
CCL 1.11 released on 2015-11-06
ECL 16.1.3 released on 2016-12-19
Lispworks 7.0 released on 2015-05-05
MKCL 1.1.10 released on 2017-01-18
SBCL 1.3.15 released 2017-02-28a) Grateful for the extensive access to algorithms, lambdas, type inference;
b) Astounded at the complexity of template meta programming.
I imagine I'll get better at it with practice. But I'm operating at about 15% of the time efficiency of, say, Scala.
It sounds tongue in cheek, but I am serious. In the std library, boost and other sophisticated libraries there are tons of template shenanigans and there is little to be gain for most application developers. The single thing complex template meta-programs buy you is compile time evaluation for things in the middle of algorithms and classes.
Which is useful, but usually for things like deciding how many object should be allocated at once or how big a working set is to keep everything in cache. These are usually the performance optimizations you care about after all the algorithmic ones have been dealt with and often save only a constant number of instructions. When this seemingly insane level of optimization makes sense the author of a library will often accept the needed data as a parameter.
With constexpr things like lookup tables can easily be computed at compile with much more sane code.
Generally that is not required even in performance sensitive code. Perhaps a few conditionals a'la enable_if or direct use of SFINAE... but most everything else, not really.
GNU Common Lisp : https://www.gnu.org/software/gcl/
GNU CLISP : http://clisp.org
CLISP hasn't had a release in a few years, but the repository has a lot of activity these days:
https://sourceforge.net/p/clisp/clisp/ci/e4aba2ecdcf71968053...
> there hasn't been a release of GNU Common LISP ("clisp") in 7 years.
CLISP had a beta release a few days ago.
Just installed it on my Mac: this is GNU CLISP 2.49.50 from 2017-03-19.
$ clisp
i i i i i i i ooooo o ooooooo ooooo ooooo
I I I I I I I 8 8 8 8 8 o 8 8
I \ `+' / I 8 8 8 8 8 8
\ `-+-' / 8 8 8 ooooo 8oooo
`-__|__-' 8 8 8 8 8
| 8 o 8 8 o 8 8
------+------ ooooo 8oooooo ooo8ooo ooooo 8
Welcome to GNU CLISP 2.49.50 (2017-03-19) <http://clisp.org/>
Copyright (c) Bruno Haible, Michael Stoll 1992, 1993
Copyright (c) Bruno Haible, Marcus Daniels 1994-1997
Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998
Copyright (c) Bruno Haible, Sam Steingold 1999-2000
Copyright (c) Sam Steingold, Bruno Haible 2001-2010
Type :h and hit Enter for context help.
[1]> (lisp-implementation-version)
"2.49.50 (2017-03-19) (built 3699208367) (memory 3699208818)"
Doesn't look dead to me...GNU Common Lisp is currently a bit less active: http://git.savannah.gnu.org/cgit/gcl.git/log/
Other 'free/open source' implementations are currently more interesting, especially ECL, Clozure CL and SBCL.
I have on my 32bit ARM under Linux the following implementations: GCL, ECL, LispWorks, SBCL, CCL, ABCL. LispWorks, CCL and SBCL are native compilers. I'd say that's enough choice.
> LISP is really dead now
Not more than usual.
Current version: 2.49 (2010-07-07).
Two people are still making source checkins, but there hasn't been a new release since 2010. It's nice to know that someone is still working on it, but they're not making it to a release version.
Lisp might be dead, but this sentence is a non sequitur.