jfmengels/elm-review-simplify/2.1.3

NOTE: the latest version is 2.1.15

Simplify

Reports when an expression can be simplified.

🔧 Running with --fix will automatically remove all the reported errors.

config =
    [ Simplify.rule Simplify.defaults
    ]

Rule to simplify Elm code.

Configuration for this rule. Create a new one with defaults and use ignoreCaseOfForTypes to alter it.

Default configuration for this rule.

The rule aims tries to improve the code through simplifications that don't impact the behavior. An exception to this are when the presence of NaN values

Use expectNaN if you want to opt out of changes that can impact the behaviour of your code if you expect to work with NaN values.

Use ignoreCaseOfForTypes if you want to prevent simplifying case expressions that work on custom types defined in dependencies.

config =
    [ Simplify.rule Simplify.defaults
    ]


-- or
config =
    [ Simplify.defaults
        |> Simplify.expectNaN
        |> Simplify.ignoreCaseOfForTypes [ "Module.Name.Type" ]
        |> Simplify.rule
    ]

Usually, elm-review-simplify will only suggest simplifications that are safe to apply without risk of changing the original behavior. However, when encountering NaN values, some simplifications can actually impact behavior.

For instance, the following expression will evaluate to True:

x == x
--> True

However, if x is NaN or a value containing NaN then the expression will evaluate to False:

-- given x = NaN
x == x
--> False


-- given x = { a = ( NaN, 0 ) }
x == x
--> False

Given the potential presence of NaN, some simplifications become unsafe to apply:

  • x == x to True
  • List.member x [ x ] to True
  • n * 0 to 0

This special value is hard to recreate in Elm code both intentionally and unintentionally, and it's therefore unlikely to be found in your application, which is why the rule applies these simplifications by defaults.

If you somehow expect to create and encounter NaN values in your codebase, then you can use this function to disable these simplifications altogether.

config =
    [ Simplify.defaults
        |> Simplify.expectNaN
        |> Simplify.rule
    ]

Ignore some reports about types from dependencies used in case expressions.

This rule simplifies the following construct:

module Module.Name exposing (..)


case value of
    Just _ -> x
    Nothing -> x
--> x

(Since v2.0.19) it will not try to simplify the case expression when some of the patterns references custom types constructors defined in the project. It will only do so for custom types that are defined in dependencies (including elm/core).

If you do happen to want to disable this simplification for a type Module.Name.Type, you can configure the rule like this:

config =
    [ Simplify.defaults
        |> Simplify.ignoreCaseOfForTypes [ "Module.Name.Type" ]
        |> Simplify.rule
    ]

I personally don't recommend to use this function too much, because this could be a sign of premature abstraction, and because I think that often You Aren't Gonna Need this code.

Please let me know by opening an issue if you do use this function, I am very curious to know;

Try it out

You can try this rule out by running the following command:

elm-review --template jfmengels/elm-review-simplify/example --rules Simplify

Simplifications

Below is the list of all kinds of simplifications this rule applies.

Booleans

x || True
--> True


x || False
--> x


x && True
--> x


x && False
--> False


not True
--> False


not (not x)
--> x


-- for `<`, `>`, `<=`, `>=`, `==` and `/=`
not (a < b)
--> a >= b

Comparisons

x == True
--> x


x /= False
--> x


not x == not y
--> x == y


anything == anything
--> True


anything /= anything
--> False


{ r | a = 1 } == { r | a = 2 }
--> False

If expressions

if True then x else y
--> x


if False then x else y
--> y


if condition then x else x
--> x


if condition then True else False
--> condition


if condition then False else True
--> not condition


a =
    if condition then
        if not condition then
            1
        else
            2
    else
        3
--> if condition then 2 else 3

Case expressions

case condition of
    True -> x
    False -> y
--> if condition then x else y


case condition of
    False -> y
    True -> x
--> if not condition then x else y


-- only when no variables are introduced in the pattern
-- and no custom types defined in the project are referenced
case value of
    Just _ -> x
    Nothing -> x
--> x

Destructuring using case expressions

case value of
    ( x, y ) ->
        x + y


-->
let
    ( x, y ) =
        value
in
x + y

Let expressions

let
    a =
        1
in
let
    b =
        1
in
a + b


-->
let
    a =
        1


    b =
        1
in
a + b

Record updates

{ a | b = a.b }
--> a


{ a | b = a.b, c = 1 }
--> { a | c = 1 }

Field access

{ a = b }.a
--> b


{ a | b = c }.b
--> c


{ a | b = c }.d
--> a.d


(let a = b in c).d
--> let a = b in c.d


(Record first second).first
--> first

Basics functions

identity x
--> x


f >> identity
--> f


always x y
--> x


f >> always x
--> always x


toFloat 1
--> 1


round 1
--> 1


ceiling 1
--> 1


floor 1
--> 1


truncate 1
--> 1


round (toFloat n) -- same for ceiling, floor and truncate
--> n

Lambdas

(\_ -> x) data
--> x


(\() y -> x) ()
--> (\y -> x)


(\_ y -> x) data
--> (\y -> x)

Operators

(++) a b
--> a ++ b


a |> f >> g
--> a |> f |> g

Numbers

n + 0
--> n


n - 0
--> n


0 - n
--> -n


n * 1
--> n


0 // n
--> 0


n // 0
--> 0


n // 1
--> n


n / 1
--> n


0 / n
--> 0


-(-n)
--> n


negate (negate n)
--> n


n - n
--> 0

Tuples

Tuple.pair a b
--> ( a, b )


Tuple.first ( a, b )
--> a


Tuple.first (Tuple.mapSecond changeFirst tuple)
--> Tuple.first tuple


Tuple.first (Tuple.mapBoth changeFirst changeSecond tuple)
--> Tuple.first (Tuple.mapFirst changeFirst tuple)


Tuple.second ( a, b )
--> b


Tuple.second (Tuple.mapFirst changeSecond tuple)
--> Tuple.second tuple


Tuple.second (Tuple.mapBoth changeFirst changeSecond tuple)
--> Tuple.second (Tuple.mapSecond changeSecond tuple)

Strings

"a" ++ ""
--> "a"


String.fromList []
--> ""


String.fromList [ a ]
--> String.fromChar a


String.fromList (String.toList str)
--> str


String.toList (String.fromList list)
--> list


String.isEmpty ""
--> True


String.isEmpty "a"
--> False


String.concat []
--> ""


String.concat [ string ]
--> string


String.concat [ hello, "", world ]
--> String.concat [ hello, world ]


String.concat [ "a", String.concat [ b, c ], d ]
--> String.concat [ "a", b, c, d ]


String.concat (List.repeat n str)
--> String.repeat n str


String.concat (List.intersperse str strings)
--> String.join str strings


String.append "" str
--> str


String.append (String.fromList [ a, b ]) (String.fromList [ c, d ])
--> String.fromList [ a, b, c, d ]


String.join str []
--> ""


String.join "" list
--> String.concat list


String.length "abc"
--> 3


String.repeat n ""
--> ""


String.repeat 0 str
--> ""


String.repeat 1 str
--> str


String.replace x y ""
--> ""


String.replace x x z
--> z


String.replace "x" "y" "z"
--> "z" -- only when resulting string is unchanged


String.words ""
--> []


String.lines ""
--> []


String.reverse ""
--> ""


String.reverse (String.fromChar a)
--> String.fromChar a


String.reverse (String.reverse str)
--> str


String.slice n n str
--> ""


String.slice n 0 str
--> ""


String.slice a z ""
--> ""


String.left 0 str
--> ""


String.left -1 str
--> ""


String.left n ""
--> ""


String.right 0 str
--> ""


String.right -1 str
--> ""


String.right n ""
--> ""


String.slice 2 1 str
--> ""


String.slice -1 -2 str
--> ""


-- The following simplifications for String.foldl also work for String.foldr
String.foldl f initial ""
--> initial


String.foldl (\_ soFar -> soFar) initial string
--> initial

Maybes

Maybe.map identity x
--> x


Maybe.map f Nothing
--> Nothing


Maybe.map f (Just x)
--> Just (f x)


-- the following simplifications for map2 work for all Maybe.mapN
Maybe.map2 f firstMaybe Nothing
--> Nothing


Maybe.map2 f (Just a) (Just b)
--> Just (f a b)


Maybe.andThen f Nothing
--> Nothing


Maybe.andThen (always Nothing) x
--> Nothing


Maybe.andThen (\a -> Just b) x
--> Maybe.map (\a -> b) x


Maybe.andThen (\a -> if condition a then Just b else Just c) x
--> Maybe.map (\a -> if condition a then b else c) x


Maybe.andThen f (Just x)
--> f x


Maybe.withDefault x Nothing
--> x


Maybe.withDefault x (Just y)
--> y

Results

Result.map identity x
--> x


Result.map f (Err x)
--> Err x


Result.map f (Ok x)
--> Ok (f x)


-- the following simplifications for map3 work for all Result.mapN
Result.map3 f (Ok a) (Ok b) (Ok c)
--> Ok (f a b c)


Result.map3 f (Ok a) (Err x) thirdResult
--> Err x


Result.map3 f firstResult (Err x) thirdResult
--> Result.map2 f firstResult (Err x)


Result.mapError identity x
--> x


Result.mapError f (Ok x)
--> Ok x


Result.mapError f (Err x)
--> Err (f x)


Result.andThen f (Err x)
--> Err x


Result.andThen f (Ok x)
--> f x


Result.andThen (\a -> Ok b) x
--> Result.map (\a -> b) x


Result.withDefault x (Err y)
--> x


Result.withDefault x (Ok y)
--> y


Result.fromMaybe x (Just a)
--> Ok a


Result.fromMaybe x Nothing
--> Err x


Result.toMaybe (Ok x)
--> Just x


Result.toMaybe (Err e)
--> Nothing

Lists

a :: []
--> [ a ]


a :: [ b ]
--> [ a, b ]


[ a ] ++ list
--> a :: list


[] ++ list
--> list


[ a, b ] ++ [ c ]
--> [ a, b, c ]


List.append [] ys
--> ys


List.append [ a, b ] [ c ]
--> [ a, b, c ]


List.head []
--> Nothing


List.head (a :: bToZ)
--> Just a


List.tail []
--> Nothing


List.tail (a :: bToZ)
--> Just bToZ


List.member a []
--> False


List.member a [ a, b, c ]
--> True


List.member a ([a, b] ++ list)
--> True


List.member a [ b ]
--> a == b


List.map f [] -- same for most List functions like List.filter, List.filterMap, ...
--> []


List.map identity list
--> list


List.map f [ a ]
--> [ f a ]


List.filter (always True) list
--> list


List.filter (always False) list
--> []


List.filterMap Just list
--> list


List.filterMap (\a -> if condition a then Just b else Just c) list
--> List.map (\a -> if condition a then b else c) list


List.filterMap (always Nothing) list
--> []


List.filterMap identity (List.map f list)
--> List.filterMap f list


List.filterMap identity [ Just x, Just y ]
--> [ x, y ]


List.filterMap identity [ a, Nothing, b ]
--> List.filterMap identity [ a, b ]


List.concat [ [ a, b ], [ c ] ]
--> [ a, b, c ]


List.concat [ a, [ 1 ], [ 2 ] ]
--> List.concat [ a, [ 1, 2 ] ]


List.concat [ a, [], b ]
--> List.concat [ a, b ]


List.concat [ a, List.concat [ b, c ], d ]
--> List.concat [ a, b, c, d ]


List.concatMap identity list
--> List.concat list


List.concatMap (\a -> [ b ]) list
--> List.map (\a -> b) list


List.concatMap f [ x ]
--> f x


List.concatMap (always []) list
--> []


List.concat (List.map f list)
--> List.concatMap f list


List.indexedMap (\_ value -> f value) list
--> List.map (\value -> f value) list


List.intersperse a []
--> []


List.intersperse s [ a ]
--> [ a ]


List.isEmpty []
--> True


List.isEmpty [ a ]
--> False


List.isEmpty (x :: xs)
--> False


List.sum []
--> 0


List.sum [ a ]
--> a


List.sum [ a, 0, b ]
--> List.sum [ a, b ]


-- when `expectNaN` is enabled
List.sum [ a, 0 / 0, b ]
--> 0 / 0


List.product []
--> 1


List.product [ a ]
--> a


List.product [ a, 1, b ]
--> List.product [ a, b ]


--  when `expectNaN` is not enabled
List.product [ a, 0, b ]
--> 0


-- when `expectNaN` is enabled
List.product [ a, 0 / 0, b ]
--> 0 / 0


List.minimum []
--> Nothing


List.minimum [ a ]
--> Just a


List.maximum []
--> Nothing


List.maximum [ a ]
--> Just a


-- The following simplifications for List.foldl also work for List.foldr
List.foldl f x []
--> x


List.foldl (\_ soFar -> soFar) x list
--> x


List.foldl (+) 0 list
--> List.sum list


List.foldl (+) initial list
--> initial + List.sum list


List.foldl (*) 1 list
--> List.product list


List.foldl (*) 0 list
--> 0


List.foldl (*) initial list
--> initial * List.product list


List.foldl (&&) True list
--> List.all identity list


List.foldl (&&) False list
--> False


List.foldl (||) False list
--> List.any identity list


List.foldl (||) True list
--> True


List.all f []
--> True


List.all (always True) list
--> True


List.all identity [ a, False, b ]
--> False


List.all not [ a, True, b ]
--> False


List.all identity [ a, True, b ]
--> List.all identity [ a, b ]


List.all not [ a, False, b ]
--> List.all not [ a, b ]


List.any f []
--> True


List.any (always False) list
--> False


List.any identity [ a, True, b ]
--> True


List.any not [ a, False, b ]
--> True


List.any identity [ a, False, b ]
--> List.any identity [ a, b ]


List.any not [ a, True, b ]
--> List.any not [ a, b ]


List.any ((==) x) list
--> List.member x list


List.range 6 3
--> []


List.length [ a, b, c ]
--> 3


List.repeat 0 x
--> []


List.repeat 1 x
--> List.singleton x


List.partition f []
--> ( [], [] )


List.partition (always True) list
--> ( list, [] )


List.partition (always False) list
--> ( [], list )


Tuple.first (List.partition f list)
--> List.filter f list


List.take 0 list
--> []


List.drop 0 list
--> list


List.drop 3 [ a, b ]
--> []


List.drop 2 [ a, b, c ]
--> [ c ]


List.reverse []
--> []


List.reverse [ a ]
--> [ a ]


List.reverse (List.reverse list)
--> list


List.sort (List.sort list)
--> List.sort list


List.sortBy (always a) list
--> list


List.sortBy identity list
--> List.sort list


List.sortBy f (List.sortBy f list)
--> List.sortBy f list


List.sortWith (\_ _ -> LT) list
--> List.reverse list


List.sortWith (\_ _ -> EQ) list
--> list


List.sortWith (\_ _ -> GT) list
--> list


-- The following simplifications for List.sort also work for List.sortBy f and List.sortWith f
List.sort []
--> []


List.sort [ a ]
--> [ a ]




-- same for up to List.map5 when any list is empty
List.map2 f xs []
--> []


List.map2 f [] ys
--> []


List.unzip []
--> ( [], [] )

Arrays

Array.fromList []
--> Array.empty


Array.fromList (Array.toList array)
--> array


Array.toList (Array.fromList list)
--> list


Array.toList Array.empty
--> []


Array.toList (Array.repeat n a)
--> List.repeat n a


Array.map f Array.empty -- same for Array.filter
--> Array.empty


Array.map identity array
--> array


Array.indexedMap (\_ value -> f value) array
--> Array.map (\value -> f value) array


Array.isEmpty Array.empty
--> True


Array.repeat 0 x
--> Array.empty


Array.initialize 0 f
--> Array.empty


Array.length Array.empty
--> 0


Array.length (Array.fromList [ a, b, c ])
--> 3


Array.length (Array.repeat 3 x)
--> 3


Array.length (Array.initialize 3 f)
--> 3


Array.length (Array.repeat n x)
--> max 0 n


Array.length (Array.initialize n f)
--> max 0 n


Array.append Array.empty array
--> array


Array.append (Array.fromList [ a, b ]) (Array.fromList [ c, d ])
--> Array.fromList [ a, b, c, d ]


Array.slice n n array
--> Array.empty


Array.slice n 0 array
--> Array.empty


Array.slice a z Array.empty
--> Array.empty


Array.slice 2 1 array
--> Array.empty


Array.slice -1 -2 array
--> Array.empty


Array.get n Array.empty
--> Nothing


Array.get 1 (Array.fromList [ a, b, c ])
--> Just b


Array.get 100 (Array.fromList [ a, b, c ])
--> Nothing


Array.get -1 array
--> Nothing


Array.get 2 (Array.repeat 10 x)
--> Just x


Array.get 100 (Array.repeat 10 x)
--> Nothing


Array.get 2 (Array.initialize 10 f)
--> Just (f 2)


Array.get 100 (Array.initialize 10 f)
--> Nothing


Array.set n x Array.empty
--> Array.empty


Array.set -1 x array
--> array


Array.set 1 x (Array.fromList [ a, b, c ])
--> Array.fromList [ a, x, c ]


Array.set 100 x (Array.fromList [ a, b, c ])
--> Array.fromList [ a, b, c ]


-- The following simplifications for Array.foldl also work for Array.foldr
Array.foldl f initial Array.empty
--> initial


Array.foldl (\_ soFar -> soFar) initial array
--> initial


Array.toIndexedList Array.empty
--> []


List.map Tuple.second (Array.toIndexedList array)
--> Array.toList array

Sets

Set.fromList []
--> Set.empty


Set.fromList [ a ]
--> Set.singleton a


Set.fromList (Set.toList set)
--> set


Set.map f Set.empty -- same for Set.filter, Set.remove...
--> Set.empty


Set.map identity set
--> set


Set.isEmpty Set.empty
--> True


Set.isEmpty (Set.fromList ([a] ++ list)
--> False


Set.member x Set.empty
--> False


Set.toList Set.empty
--> []


Set.length Set.empty
--> 0


Set.intersect Set.empty set
--> Set.empty


Set.intersect set set
--> set


Set.diff Set.empty set
--> Set.empty


Set.diff set Set.empty
--> set


Set.union set Set.empty
--> set


Set.union set set
--> set


Set.union (Set.fromList [ a, b ]) (Set.fromList [ c, d ])
--> Set.fromList [ a, b, c, d]


Set.insert x Set.empty
--> Set.singleton x


-- same for foldr
List.foldl f x (Set.toList set)
--> Set.foldl f x set


Set.filter (\_ -> True) set
--> set


Set.filter (\_ -> False) set
--> Set.empty


Set.partition f Set.empty
--> ( Set.empty, Set.empty )


Set.partition (always True) set
--> ( set, Set.empty )


Tuple.first (Set.partition f set)
--> Set.filter f set


-- The following simplifications for Set.foldl also work for Set.foldr
Set.foldl f initial Set.empty
--> initial


Set.foldl (\_ soFar -> soFar) initial set
--> initial

Dict

Dict.fromList []
--> Dict.empty


Dict.fromList (Dict.toList dict)
--> dict


Dict.isEmpty Dict.empty
--> True


Dict.toList Dict.empty
--> []


Dict.size Dict.empty
--> 0


Dict.member x Dict.empty
--> False


Dict.remove k Dict.empty
--> Dict.empty


Dict.filter f Dict.empty
--> Dict.empty


Dict.filter (\_ _ -> True) dict
--> dict


Dict.filter (\_ _ -> False) dict
--> Dict.empty


Dict.map f Dict.empty
--> Dict.empty


Dict.map (\_ value -> value) dict
--> dict


Dict.intersect Dict.empty dict
--> Dict.empty


Dict.intersect dict dict
--> dict


Dict.diff Dict.empty dict
--> Dict.empty


Dict.diff dict Dict.empty
--> dict


Dict.union dict Dict.empty
--> dict


Dict.union dict dict
--> dict


Dict.union (Dict.fromList [ a, b ]) (Dict.fromList [ c, d ])
--> Dict.fromList [ c, d, a, b ]


Dict.partition f Dict.empty
--> ( Dict.empty, Dict.empty )


Dict.partition (\_ _ -> True) dict
--> ( dict, Dict.empty )


Dict.partition (\_ _ -> False) dict
--> ( Dict.empty, dict )


Tuple.first (Dict.partition f dict)
--> Dict.filter f dict


List.map Tuple.first (Dict.toList dict)
--> Dict.keys dict


List.map Tuple.second (Dict.toList dict)
--> Dict.values dict


-- same for foldr
Dict.foldl f initial Dict.empty
--> initial


Dict.foldl (\_ soFar -> soFar) initial dict
--> initial

Cmd / Sub

All of these also apply for Sub.

Cmd.batch []
--> Cmd.none


Cmd.batch [ a ]
--> a


Cmd.batch [ a, Cmd.none, b ]
--> Cmd.batch [ a, b ]


Cmd.batch [ a, Cmd.batch [ b, c ], d ]
--> Cmd.batch [ a, b, c, d ]


Cmd.map identity cmd
--> cmd


Cmd.map f Cmd.none
--> Cmd.none

Task

Task.map identity task
--> task


Task.map f (Task.fail x)
--> Task.fail x


Task.map f (Task.succeed a)
--> Task.succeed (f a)


-- the following simplifications for map3 work for all Task.mapN
Task.map3 f (Task.succeed a) (Task.succeed b) (Task.succeed c)
--> Task.succeed (f a b c)


Task.map3 f (Task.succeed a) (Task.fail x) thirdTask
--> Task.fail x


Task.map3 f firstTask (Task.fail x) thirdTask
--> Task.map2 f firstTask (Task.fail x)


Task.andThen f (Task.fail x)
--> Task.fail x


Task.andThen f (Task.succeed a)
--> f a


Task.andThen Task.succeed task
--> task


Task.andThen (\a -> Task.succeed b) task
--> Task.map (\a -> b) task


Task.mapError identity task
--> task


Task.mapError f (Task.succeed a)
--> Task.succeed a


Task.mapError f (Task.fail x)
--> Task.fail (f x)


Task.onError f (Task.succeed a)
--> Task.succeed a


Task.onError f (Task.fail x)
--> f x


Task.onError Task.fail task
--> task


Task.onError (\x -> Task.fail y) task
--> Task.mapError (\x -> y) x


Task.sequence [ Task.succeed a, Task.succeed b ]
--> Task.succeed [ a, b ]


Task.sequence [ Task.succeed a, Task.fail x ]
--> Task.fail x


Task.sequence [ a, Task.fail x, b ]
--> Task.sequence [ a, Task.fail x ]


Task.sequence [ task ]
--> Task.map List.singleton task

Html.Attributes

Html.Attributes.classList [ x, y, ( z, False ) ]
--> Html.Attributes.classList [ x, y ]


Html.Attributes.classList [ ( onlyOneThing, True ) ]
--> Html.Attributes.class onlyOneThing

Json.Decode

Json.Decode.map identity decoder
--> decoder


Json.Decode.map f (Json.Decode.fail x)
--> Json.Decode.fail x


Json.Decode.map f (Json.Decode.succeed a)
--> Json.Decode.succeed (f a)


-- the following simplifications for map3 work for all Json.Decode.mapN
Json.Decode.map3 f (Json.Decode.succeed a) (Json.Decode.succeed b) (Json.Decode.succeed c)
--> Json.Decode.succeed (f a b c)


Json.Decode.map3 f (Json.Decode.succeed a) (Json.Decode.fail x) thirdDecoder
--> Json.Decode.fail x


Json.Decode.map3 f firstDecoder (Json.Decode.fail x) thirdDecoder
--> Json.Decode.map2 f firstDecoder (Json.Decode.fail x)


Json.Decode.andThen f (Json.Decode.fail x)
--> Json.Decode.fail x


Json.Decode.andThen f (Json.Decode.succeed a)
--> f a


Json.Decode.andThen Json.Decode.succeed decoder
--> decoder


Json.Decode.andThen (\a -> Json.Decode.succeed b) decoder
--> Json.Decode.map (\a -> b) decoder


Json.Decode.oneOf [ a ]
--> a

Parser

Parser.oneOf [ a ]
--> a

Random

Random.uniform a []
--> Random.constant a


Random.weighted ( weight, a ) []
--> Random.constant a


Random.weighted tuple []
--> Random.constant (Tuple.first tuple)


Random.list 0 generator
--> Random.constant []


Random.list 1 generator
--> Random.map List.singleton generator


Random.list n (Random.constant el)
--> Random.constant (List.repeat n el)


Random.map identity generator
--> generator


Random.map (always a) generator
--> Random.constant a


Random.map f (Random.constant x)
--> Random.constant (f x)


Random.andThen f (Random.constant x)
--> f x


Random.andThen Random.constant generator
--> generator


Random.andThen (\a -> Random.constant b) generator
--> Random.map (\a -> b) generator


Random.andThen (always thenGenerator) generator
--> thenGenerator

Test

Test.concat [ test ]
--> test


Test.concat [ test0, Test.concat [ test1, test2 ], test3 ]
--> Test.concat [ test0, test1, test2, test3 ]