diff --git a/ucl/ast.go b/ucl/ast.go
index 3b567d9..54aaa64 100644
--- a/ucl/ast.go
+++ b/ucl/ast.go
@@ -148,7 +148,7 @@ var scanner = lexer.MustStateful(lexer.Rules{
 		{"NL", `[;\n][; \n\t]*`, nil},
 		{"PIPE", `\|`, nil},
 		{"EQ", `=`, nil},
-		{"Ident", `[-]*[a-zA-Z_][\w-!?]*`, nil},
+		{"Ident", `[-!?]*[a-zA-Z_!?-][\w-!?]*`, nil},
 	},
 	"String": {
 		{"Escaped", `\\.`, nil},
diff --git a/ucl/builtins.go b/ucl/builtins.go
index ddb065a..cc946b3 100644
--- a/ucl/builtins.go
+++ b/ucl/builtins.go
@@ -188,7 +188,7 @@ func eqBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
 	l := args.args[0]
 	r := args.args[1]
 
-	return BoolObject(objectsEqual(l, r)), nil
+	return BoolObject(ObjectsEqual(l, r)), nil
 }
 
 func neBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
@@ -199,7 +199,7 @@ func neBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
 	l := args.args[0]
 	r := args.args[1]
 
-	return BoolObject(!objectsEqual(l, r)), nil
+	return BoolObject(!ObjectsEqual(l, r)), nil
 }
 
 func ltBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
@@ -223,7 +223,7 @@ func leBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
 	if err != nil {
 		return nil, err
 	}
-	return BoolObject(isLess || objectsEqual(args.args[0], args.args[1])), nil
+	return BoolObject(isLess || ObjectsEqual(args.args[0], args.args[1])), nil
 }
 
 func gtBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
@@ -247,7 +247,7 @@ func geBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
 	if err != nil {
 		return nil, err
 	}
-	return BoolObject(isGreater || objectsEqual(args.args[0], args.args[1])), nil
+	return BoolObject(isGreater || ObjectsEqual(args.args[0], args.args[1])), nil
 }
 
 func andBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
@@ -286,7 +286,7 @@ func notBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
 
 var errObjectsNotEqual = errors.New("objects not equal")
 
-func objectsEqual(l, r Object) bool {
+func ObjectsEqual(l, r Object) bool {
 	if l == nil || r == nil {
 		return l == nil && r == nil
 	}
@@ -314,7 +314,7 @@ func objectsEqual(l, r Object) bool {
 			return false
 		}
 		for i := 0; i < lv.Len(); i++ {
-			if !objectsEqual(lv.Index(i), rv.Index(i)) {
+			if !ObjectsEqual(lv.Index(i), rv.Index(i)) {
 				return false
 			}
 		}
@@ -332,7 +332,7 @@ func objectsEqual(l, r Object) bool {
 			rkv := rv.Value(k)
 			if rkv == nil {
 				return errObjectsNotEqual
-			} else if !objectsEqual(lkv, rkv) {
+			} else if !ObjectsEqual(lkv, rkv) {
 				return errObjectsNotEqual
 			}
 			return nil
@@ -370,6 +370,18 @@ func strBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
 	return StringObject(args.args[0].String()), nil
 }
 
+func notNilBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
+	if err := args.expectArgn(1); err != nil {
+		return nil, err
+	}
+
+	if args.args[0] == nil {
+		return BoolObject(false), nil
+	}
+
+	return BoolObject(true), nil
+}
+
 func intBuiltin(ctx context.Context, args invocationArgs) (Object, error) {
 	if err := args.expectArgn(1); err != nil {
 		return nil, err
diff --git a/ucl/builtins/lists.go b/ucl/builtins/lists.go
index f81f3e2..4e70095 100644
--- a/ucl/builtins/lists.go
+++ b/ucl/builtins/lists.go
@@ -11,6 +11,7 @@ func Lists() ucl.Module {
 		Name: "lists",
 		Builtins: map[string]ucl.BuiltinHandler{
 			"first": listFirst,
+			"uniq":  listUniq,
 		},
 	}
 }
@@ -58,3 +59,69 @@ func listFirst(ctx context.Context, args ucl.CallArgs) (any, error) {
 
 	return newList, nil
 }
+
+func eachListOrIterItem(ctx context.Context, o ucl.Object, f func(int, ucl.Object) error) error {
+	switch t := o.(type) {
+	case ucl.Listable:
+		for i := 0; i < t.Len(); i++ {
+			if err := f(i, t.Index(i)); err != nil {
+				return err
+			}
+		}
+		return nil
+	case ucl.Iterable:
+		idx := 0
+		for t.HasNext() {
+			v, err := t.Next(ctx)
+			if err != nil {
+				return err
+			}
+			if err := f(idx, v); err != nil {
+				return err
+			}
+			idx++
+		}
+	}
+	return errors.New("expected listable")
+}
+
+type uniqKey struct {
+	sVal string
+	iVal int
+}
+
+func listUniq(ctx context.Context, args ucl.CallArgs) (any, error) {
+	var (
+		what ucl.Object
+	)
+
+	if err := args.Bind(&what); err != nil {
+		return nil, err
+	}
+
+	seen := make(map[uniqKey]bool)
+	found := ucl.NewListObject()
+
+	if err := eachListOrIterItem(ctx, what, func(idx int, v ucl.Object) error {
+		var key uniqKey
+		switch v := v.(type) {
+		case ucl.StringObject:
+			key = uniqKey{sVal: string(v)}
+		case ucl.IntObject:
+			key = uniqKey{iVal: int(v)}
+		default:
+			return errors.New("expected string or int")
+		}
+
+		if !seen[key] {
+			seen[key] = true
+			found.Append(v)
+		}
+
+		return nil
+	}); err != nil {
+		return nil, err
+	}
+
+	return found, nil
+}
diff --git a/ucl/builtins/lists_test.go b/ucl/builtins/lists_test.go
index 526176e..72ccf65 100644
--- a/ucl/builtins/lists_test.go
+++ b/ucl/builtins/lists_test.go
@@ -45,6 +45,37 @@ func TestLists_First(t *testing.T) {
 	}
 }
 
+func TestLists_Uniq(t *testing.T) {
+	tests := []struct {
+		desc    string
+		eval    string
+		want    any
+		wantErr bool
+	}{
+		{desc: "uniq 1", eval: `lists:uniq [a a a a b b b c c c]`, want: []any{"a", "b", "c"}},
+		{desc: "uniq 2", eval: `lists:uniq [1 2 1 3 2 4 2 5 3]`, want: []any{1, 2, 3, 4, 5}},
+		{desc: "uniq 3", eval: `lists:uniq [1 a 2 b 3 b 2 a 1]5`, want: []any{1, "a", 2, "b", 3}},
+
+		{desc: "uniq err 1", eval: `lists:uniq [[1 2 3] [a:2] ()]`, wantErr: true},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.desc, func(t *testing.T) {
+			inst := ucl.New(
+				ucl.WithModule(builtins.Itrs()),
+				ucl.WithModule(builtins.Lists()),
+			)
+			res, err := inst.EvalString(context.Background(), tt.eval)
+			if tt.wantErr {
+				assert.Error(t, err)
+			} else {
+				assert.NoError(t, err)
+				assert.Equal(t, tt.want, res)
+			}
+		})
+	}
+}
+
 func uclListOf(args ...any) *ucl.ListObject {
 	newList := ucl.NewListObject()
 	for _, arg := range args {
diff --git a/ucl/builtins/strs.go b/ucl/builtins/strs.go
index 03801d0..281dd4c 100644
--- a/ucl/builtins/strs.go
+++ b/ucl/builtins/strs.go
@@ -103,7 +103,7 @@ func join(ctx context.Context, args ucl.CallArgs) (any, error) {
 			if i > 0 {
 				sb.WriteString(tok)
 			}
-			sb.WriteString(t.Index(i).String())
+			sb.WriteString(ucl.ObjectToString(t.Index(i)))
 		}
 		return sb.String(), nil
 	case ucl.Iterable:
@@ -120,7 +120,7 @@ func join(ctx context.Context, args ucl.CallArgs) (any, error) {
 			} else {
 				first = false
 			}
-			sb.WriteString(v.String())
+			sb.WriteString(ucl.ObjectToString(v))
 		}
 
 		return sb.String(), nil
diff --git a/ucl/builtins/strs_test.go b/ucl/builtins/strs_test.go
index c38065b..6dc5436 100644
--- a/ucl/builtins/strs_test.go
+++ b/ucl/builtins/strs_test.go
@@ -183,6 +183,7 @@ func TestStrs_Join(t *testing.T) {
 		{desc: "join 3", eval: `strs:join [a b c] ""`, want: "abc"},
 		{desc: "join 4", eval: `strs:join [a b c]`, want: "abc"},
 		{desc: "join 5", eval: `strs:join (itrs:from [a b c]) ","`, want: "a,b,c"},
+		{desc: "join 6", eval: `strs:join [a () c () e]`, want: "ace"},
 	}
 
 	for _, tt := range tests {
diff --git a/ucl/inst.go b/ucl/inst.go
index 074433e..de58436 100644
--- a/ucl/inst.go
+++ b/ucl/inst.go
@@ -81,6 +81,8 @@ func New(opts ...InstOption) *Inst {
 	rootEC.addCmd("str", invokableFunc(strBuiltin))
 	rootEC.addCmd("int", invokableFunc(intBuiltin))
 
+	rootEC.addCmd("!nil", invokableFunc(notNilBuiltin))
+
 	rootEC.addCmd("add", invokableFunc(addBuiltin))
 	rootEC.addCmd("sub", invokableFunc(subBuiltin))
 	rootEC.addCmd("mup", invokableFunc(mupBuiltin))
diff --git a/ucl/inst_test.go b/ucl/inst_test.go
index 7a9149a..e2dbf60 100644
--- a/ucl/inst_test.go
+++ b/ucl/inst_test.go
@@ -21,7 +21,7 @@ func TestInst_Eval(t *testing.T) {
 		{desc: "simple string", expr: `firstarg "hello"`, want: "hello"},
 		{desc: "simple int 1", expr: `firstarg 123`, want: 123},
 		{desc: "simple int 2", expr: `firstarg -234`, want: -234},
-		{desc: "simple ident", expr: `firstarg a-test`, want: "a-test"},
+		{desc: "simple ident 1", expr: `firstarg a-test`, want: "a-test"},
 
 		// String interpolation
 		{desc: "interpolate string 1", expr: `$what = "world" ; firstarg "hello $what"`, want: "hello world"},
diff --git a/ucl/objs.go b/ucl/objs.go
index 816bbaf..981f7ab 100644
--- a/ucl/objs.go
+++ b/ucl/objs.go
@@ -680,3 +680,10 @@ func isBreakErr(err error) bool {
 
 	return errors.As(err, &errBreak{}) || errors.As(err, &errReturn{}) || errors.Is(err, ErrHalt)
 }
+
+func ObjectToString(obj Object) string {
+	if obj == nil {
+		return ""
+	}
+	return obj.String()
+}
diff --git a/ucl/testbuiltins_test.go b/ucl/testbuiltins_test.go
index 9ac1d61..9361ae4 100644
--- a/ucl/testbuiltins_test.go
+++ b/ucl/testbuiltins_test.go
@@ -1749,6 +1749,44 @@ func TestBuiltins_Cat(t *testing.T) {
 	}
 }
 
+func TestBuiltins_NotNil(t *testing.T) {
+	tests := []struct {
+		desc string
+		expr string
+		want any
+	}{
+		{desc: "not nil 1", expr: `!nil "hello"`, want: true},
+		{desc: "not nil 2", expr: `!nil ""`, want: true},
+		{desc: "not nil 3", expr: `!nil 4`, want: true},
+		{desc: "not nil 4", expr: `!nil 0`, want: true},
+		{desc: "not nil 5", expr: `!nil $true`, want: true},
+		{desc: "not nil 6", expr: `!nil $false`, want: true},
+		{desc: "not nil 7", expr: `!nil [1 2 3]`, want: true},
+		{desc: "not nil 8", expr: `!nil []`, want: true},
+		{desc: "not nil 9", expr: `!nil [a:1 b:21]`, want: true},
+		{desc: "not nil 10", expr: `!nil [:]`, want: true},
+
+		{desc: "not nil 11", expr: `!nil ()`, want: false},
+
+		{desc: "not nil 12", expr: `[1 () 2 () 3] | filter !nil | reduce "" { |x a| "$a $x" }`, want: " 1 2 3"},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.desc, func(t *testing.T) {
+			ctx := context.Background()
+			outW := bytes.NewBuffer(nil)
+
+			inst := New(WithOut(outW), WithTestBuiltin())
+			inst.SetVar("true", true)
+			inst.SetVar("false", false)
+
+			eqRes, err := inst.EvalString(ctx, tt.expr)
+			assert.NoError(t, err)
+			assert.Equal(t, tt.want, eqRes)
+		})
+	}
+}
+
 func evalAndDisplay(ctx context.Context, inst *Inst, expr string) error {
 	res, err := inst.eval(ctx, strings.NewReader(expr), evalOptions{})
 	if err != nil {