Julia is my language of choice for writing compilers.

Quasiquoting means that codegen is as easy as string interpolation is in other languages.

quote
let
$(index_inits...)
$(results_inits...)
if $(reduce((a,b) -> :($a && $b), true, index_checks))
let
$(var_inits...) # declare vars local in here so they can't shadow relation names
$body
end
end
tuple($(results...))
end
end

Great introspection into the inference and compilation pipeline, directly from the repl.

julia> function double(xs)
[2*x for x in xs]
end
double (generic function with 1 method)

julia> double([1,2,3])
3-element Array{Int64,1}:
2
4
6

julia> @code_warntype double([1,2,3])
Variables:
xs::Array{Int64,1}
#s1::Int64
#s2::Int64
#s3::Int64
x::Int64
#s4::Int64

Body:
begin # none, line 2:
GenSym(1) = (Base.arraylen)(xs::Array{Int64,1})::Int64
0:
GenSym(3) = (top(ccall))(:jl_alloc_array_1d,(top(apply_type))(Base.Array,Int64,1)::Type{Array{Int64,1}},(top(svec))(Base.Any,Base.Int)::SimpleVector,Array{Int64,1},0,GenSym(1),0)::Array{Int64,1}
#s1 = 1
#s2 = 1
#s3 = 0
unless (Base.box)(Base.Bool,(Base.not_int)(#s3::Int64 === GenSym(1)::Bool)) goto 2
3:
#s3 = (Base.box)(Base.Int,(Base.add_int)(#s3::Int64,1))
GenSym(9) = (Base.arrayref)(xs::Array{Int64,1},#s2::Int64)::Int64
GenSym(10) = (Base.box)(Base.Int,(Base.add_int)(#s2::Int64,1))
#s4 = 1
GenSym(11) = GenSym(9)
GenSym(12) = (Base.box)(Base.Int,(Base.add_int)(1,1))
x = GenSym(11)
#s4 = GenSym(12)
GenSym(13) = GenSym(10)
GenSym(14) = (Base.box)(Base.Int,(Base.add_int)(2,1))
#s2 = GenSym(13)
#s4 = GenSym(14)
GenSym(4) = (Base.box)(Int64,(Base.mul_int)(2,x::Int64))
$(Expr(:type_goto, 0, GenSym(4)))
$(Expr(:boundscheck, false))
(Base.arrayset)(GenSym(3),GenSym(4),#s1::Int64)::Array{Int64,1}
$(Expr(:boundscheck, :(Main.pop)))
#s1 = (Base.box)(Base.Int,(Base.add_int)(#s1::Int64,1))
4:
unless (Base.box)(Base.Bool,(Base.not_int)((Base.box)(Base.Bool,(Base.not_int)(#s3::Int64 === GenSym(1)::Bool)))) goto 3
2:
1:
return GenSym(3)
end::Array{Int64,1}

julia> @code_llvm double([1,2,3])

define %jl_value_t* @julia_double_21481(%jl_value_t*, %jl_value_t**, i32) {
top:
%3 = alloca [4 x %jl_value_t*], align 8
%.sub = getelementptr inbounds [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 0
%4 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 2
%5 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 3
%6 = bitcast [4 x %jl_value_t*]* %3 to i64*
store i64 4, i64* %6, align 8
%7 = getelementptr [4 x %jl_value_t*], [4 x %jl_value_t*]* %3, i64 0, i64 1
%8 = load i64, i64* bitcast (%jl_value_t*** @jl_pgcstack to i64*), align 8
%9 = bitcast %jl_value_t** %7 to i64*
store i64 %8, i64* %9, align 8
store %jl_value_t** %.sub, %jl_value_t*** @jl_pgcstack, align 8
store %jl_value_t* null, %jl_value_t** %4, align 8
store %jl_value_t* null, %jl_value_t** %5, align 8
%10 = load %jl_value_t*, %jl_value_t** %1, align 8
%11 = getelementptr inbounds %jl_value_t, %jl_value_t* %10, i64 1
%12 = bitcast %jl_value_t* %11 to i64*
%13 = load i64, i64* %12, align 8
store %jl_value_t* inttoptr (i64 140140655870352 to %jl_value_t*), %jl_value_t** %5, align 8
%14 = call %jl_value_t* inttoptr (i64 140149385306688 to %jl_value_t* (%jl_value_t*, i64)*)(%jl_value_t* inttoptr (i64 140140655870352 to %jl_value_t*), i64 inreg %13)
store %jl_value_t* %14, %jl_value_t** %4, align 8
%15 = icmp eq i64 %13, 0
br i1 %15, label %L4, label %L1.preheader

L1.preheader: ; preds = %top
%16 = load i64, i64* %12, align 8
%17 = bitcast %jl_value_t* %10 to i64**
%18 = bitcast %jl_value_t* %14 to i64**
br label %L1

L1: ; preds = %idxend, %L1.preheader
%"#s3.0" = phi i64 [ %22, %idxend ], [ 0, %L1.preheader ]
%"#s2.0" = phi i64 [ %26, %idxend ], [ 1, %L1.preheader ]
%19 = add i64 %"#s2.0", -1
%20 = icmp ult i64 %19, %16
br i1 %20, label %idxend, label %oob

oob: ; preds = %L1
%21 = alloca i64, align 8
store i64 %"#s2.0", i64* %21, align 8
call void @jl_bounds_error_ints(%jl_value_t* %10, i64* nonnull %21, i64 1)
unreachable

idxend: ; preds = %L1
%22 = add i64 %"#s3.0", 1
%23 = load i64*, i64** %17, align 8
%24 = getelementptr i64, i64* %23, i64 %19
%25 = load i64, i64* %24, align 8
%26 = add i64 %"#s2.0", 1
%27 = shl i64 %25, 1
%28 = load i64*, i64** %18, align 8
%29 = getelementptr i64, i64* %28, i64 %19
store i64 %27, i64* %29, align 8
%30 = icmp eq i64 %22, %13
br i1 %30, label %L4.loopexit, label %L1

L4.loopexit: ; preds = %idxend
br label %L4

L4: ; preds = %L4.loopexit, %top
%31 = load i64, i64* %9, align 8
store i64 %31, i64* bitcast (%jl_value_t*** @jl_pgcstack to i64*), align 8
ret %jl_value_t* %14
}

julia> @code_native double([1,2,3])
L128:L159:L177:	.text
pushq	%rbp
movq	%rsp, %rbp
pushq	%r15
pushq	%r14
pushq	%rbx
subq	$40, %rsp
movabsq	$jl_alloc_array_1d, %rax
movabsq	$140140655870352, %rdi # imm = 0x7F750A030190
movq	$4, -56(%rbp)
movabsq	$jl_pgcstack, %r15
movq	(%r15), %rcx
movq	%rcx, -48(%rbp)
leaq	-56(%rbp), %rcx
movq	%rcx, (%r15)
movq	$0, -40(%rbp)
movq	$0, -32(%rbp)
movq	(%rsi), %r14
movq	8(%r14), %rbx
movq	%rdi, -32(%rbp)
movq	%rbx, %rsi
callq	*%rax
movq	%rax, -40(%rbp)
cmpq	$0, %rbx
je	L159
xorl	%ecx, %ecx
movq	8(%r14), %rdx
nopw	%cs:(%rax,%rax)
cmpq	%rdx, %rcx
jae	L177
movq	(%r14), %rsi
movq	(%rsi,%rcx,8), %rsi
shlq	$1, %rsi
movq	(%rax), %rdi
movq	%rsi, (%rdi,%rcx,8)
incq	%rcx
cmpq	%rcx, %rbx
jne	L128
movq	-48(%rbp), %rcx
movq	%rcx, (%r15)
leaq	-24(%rbp), %rsp
popq	%rbx
popq	%r14
popq	%r15
popq	%rbp
retq
movq	%rsp, %rsi
addq	$-16, %rsi
movq	%rsi, %rsp
addq	$1, %rcx
movq	%rcx, (%rsi)
movabsq	$jl_bounds_error_ints, %rax
movl	$1, %edx
movq	%r14, %rdi
callq	*%rax

It catches type errors early, thanks to the typed multiple dispatch.

julia> xs = []
0-element Array{Any,1}

julia> push!(xs, 42)
1-element Array{Any,1}:
42

julia> push!(xs, "foo")
2-element Array{Any,1}:
42
"foo"

julia> ys = Int64[]
0-element Array{Int64,1}

julia> push!(ys, 42)
1-element Array{Int64,1}:
42

julia> push!(ys, "foo")
ERROR: MethodError: `convert` has no method matching convert(::Type{Int64}, ::ASCIIString)
This may have arisen from a call to the constructor Int64(...),
since type constructors fall back to convert methods.
Closest candidates are:
call{T}(::Type{T}, ::Any)
convert(::Type{Int64}, ::Int8)
convert(::Type{Int64}, ::UInt8)
...
in push! at ./array.jl:432

Plus, I only have to think in one language, but I can write sloppy dynamic heap-allocating-everywhere code in the compiler and with just a bit of thinking emit zero-allocation statically-dispatched code in the output.