Skip to content

Comprehensions

They provide an alternative way to implicitly construct lists and arrays, but they cannot be used as a pattern.

While it is possible to construct every types of list or array with comprehension, for simplicity, they are used to construct lazy lists.

Comprehensions iterate over a generator, which is an existing list or array. In each iteration, the left-most element is extracted from the generator. Its value is stored in a selector.

Usage Examples

Extracting Elements From Generators

Each generator requires a unique "arrow" to extract elements.

A left arrow (<-) is used to extract elements from list generators, except overloaded list generators.

// Language: Clean

listA ::  [ T ]
listA = [ e \\ e <- lsGen ]

A left arrow with vertical bar between the arrowhead and the shaft (<|-) is used to extract elements from overloaded list generators.

// Language: Clean

listA :: [ T ]
listA =  [ e \\ e <|- overloadedLsGen ]

A left arrow with colon at the end (<-:) is used to extract elements from array generators.

// Language: Clean

listA :: [ T ]
listA =  [ e \\ e <-: arrGen ]

Python equivalent:

# Language: Python

listA: list[any] = []
for e in lsGen:
    listA.append(e)

Nesting Generators

Commas (,) are used to join generators by nesting them. The right-most generator is the fastest.

// Language: Clean

listB :: [ ( T, K ) ]
listB =  [ ( eX, eY ) \\ 
    eX <- lsGenX , 
    eY <- lsGenY 
]

Python equivalent:

# Language: Python

listB: list[tuple[any, any]] = []
for eX in lsGenX:
    for eY in lsGenY:
        listB.append( (eX, eY) )

Zipping Generators

Ampersands (&) are used to join generators by zipping them. Iteration stops as soon as one generator runs out of element.

// Language: Clean

listC :: [ ( T, K ) ]
listC =  [ ( eX, eY ) \\ 
    eX <- lsGenX & 
    eY <- lsGenY
]

Python equivalent:

# Language: Python

listC: list[tuple[any, any]] = []
for eX, eY in zip(lsGenX, lsGenY):
    listC.append( (eX, eY) )

Nesting and Zipping Generators

Commas (,) and ampersands (&) can be used together to join generators. Ampersands bind more tightly than commas when joining generators.

Example 1

// Langauge: Clean

listDa :: [ ( T, K, V ) ]
listDa =  [ ( eX, eY, eZ ) \\ 
    eX <- lsGenX & 
    eY <- lsGenY , 
    eZ <- lsGenZ
]

Python equivalent:

# Language: Python

listDa: list[tuple[any, any, any]] = []
for eX, eY in zip(lsGenX, lsGenY):
    for eZ in lsGenZ:
        listDa.append( (eX, eY, eZ) )

Example 2

// Langauge: Clean

listDb :: [ ( T, K, V ) ]
listDb =  [ ( eX, eY, eZ ) \\ 
    eX <- lsGenX , 
    eY <- lsGenY & 
    eZ <- lsGenZ 
]

Python equivalent:

# Language: Python

listDb: list[tuple[any, any, any]] = []
for eX in lsGenX:
    for eY, eZ in zip(lsGenY, lsGenZ):
        listDb.append( (eX, eY, eZ) )

Conditional Iteration

A condition can be introduced after each generator. When the condition fails, the current iteration is skipped.

// Langauge: Clean

listE :: [ T ]
listE =  [ e \\ e <- lsGen | pred e ]

Python equivalent:

# Language: Python

listE: list[any] = []
for e in lsGen:
    if not pred(e):
        continue
    listE.append(e)

Conditional Iteration in Nested Generators

Each nested generator can have its own condition.

Example 1:

// Langauge: Clean

x :: [ ( T, K ) ]
x =  [ ( eX, eY ) \\ 
    eX <- lsGenX | predX eX , 
    eY <- lsGenY | predY eY
]

Python equivalent:

# Language: Python

x: list[tuple[any, any]] = []
for eX in lsGenX:
    if not predX(eX):
        continue
    for eY in lsGenY:
        if not predY(eY):
            continue
        x.append((eX, eY))

Example 2:

// Langauge: Clean

x :: [ ( T, K ) ]
x =  [ ( eX, eY ) \\ 
    eX <- lsGenX | predX eX , 
    eY <- lsGenY | predY eX eY
]

Python equivalent:

# Language: Python`

x: list[tuple[any, any]] = []
for eX in lsGenX:
    if not predX(eX):
        continue
    for eY in lsGenY:
        if not predY(eX, eY):
            continue
        x.append((eX, eY))

Conditional Iteration in Zipped Generators

A group of zipped generators can only have a condition.

Example 1:

// Langauge: Clean

x :: [ ( T, K ) ]
x =  [ ( eX, eY ) \\ 
    eX <- lsGenX & 
    eY <- lsGenY | pred eX 
]

Python equivalent:

# Language: Python

x: list[tuple[any, any]] = []
for eX, Ey in zip(lsGenX, lsGenY):
    if not predY(eX):
        continue
    x.append((eX, eY))

Example 2:

// Langauge: Clean

x :: [ ( T, K ) ]
x =  [ ( eX, eY ) \\ 
    eX <- lsGenX & 
    eY <- lsGenY | pred eX eY
]

Python equivalent:

# Language: Python

x: list[tuple[any, any]] = []
for eX, eY in zip(lsGenX, lsGenY):
    if not predY(eX, eY):
        continue
    x.append((eX, eY))

Example 3:

// Langauge: Clean

x :: [ ( T, K ) ]
x =  [ ( eX, eY ) \\ 
    eW <- lsGenW & 
    eX <- lsGenX | predWX eW eX ,
    eY <- lsGenY &
    eZ <- lsGenZ | predYZ eY eZ
]

Python equivalent:

# Language: Python

x: list[tuple[any, any]] = []
for eW, eX in zip(lsGenW, lsGenX):
    if not predWX(eW, eX):
        continue
    for eW, eX in zip(lsGenW, lsGenX):
        if not predYZ(eY, eZ):
            continue
        x.append((eX, eY))

Additional Usage Examples

Conversion Between List and Array

Conversion between a lazy list and a lazy array can be done with comprehension.

Example 1:

// Language: Clean

xList :: [ T ]
xList =  [ e \\ e <-: xArr ] 

Example 2:

// Language: Clean

xArr :: { T }
xArr =  { e \\ e <- xList }