<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:

def _compile_expression(self,
                            expr: Expression,
                            scope: Dict[str, TensorFluent],
                            batch_size: Optional[int] = None,
                            noise: Optional[List[tf.Tensor]] = None) -> TensorFluent:
        '''Compile the expression `expr` into a TensorFluent
        in the given `scope` with optional batch size.

        Args:
            expr (:obj:`rddl2tf.expr.Expression`): A RDDL expression.
            scope (Dict[str, :obj:`rddl2tf.fluent.TensorFluent`]): A fluent scope.
            batch_size (Optional[size]): The batch size.

        Returns:
            :obj:`rddl2tf.fluent.TensorFluent`: The compiled TensorFluent.
        '''
        etype2compiler = {
            'constant':    self._compile_constant_expression,
            'pvar':        self._compile_pvariable_expression,
            'randomvar':   self._compile_random_variable_expression,
            'arithmetic':  self._compile_arithmetic_expression,
            'boolean':     self._compile_boolean_expression,
            'relational':  self._compile_relational_expression,
            'func':        self._compile_function_expression,
            'control':     self._compile_control_flow_expression,
            'aggregation': self._compile_aggregation_expression
        }

        etype = expr.etype
        if etype[0] not in etype2compiler:
            raise ValueError('Expression type unknown: {}'.format(etype))

        with self.graph.as_default():
            compiler_fn = etype2compiler[etype[0]]
            return compiler_fn(expr, scope, batch_size, noise)