Есть ли эквивалент Python для Haskell 'let'

Есть ли эквивалент Python выражения "let" в Haskell, который позволил бы мне написать что-то вроде:

list2 = [let (name,size)=lookup(productId) in (barcode(productId),metric(size)) 
            for productId in list]

Если нет, то какая альтернатива будет самой читаемой?

Добавлено для уточнения синтаксиса let:

x = let (name,size)=lookup(productId) in (barcode(productId),metric(size))

эквивалентно

(name,size) = lookup(productId)
x = (barcode(productId),metric(size))

Однако вторая версия не так хорошо работает со списками.


person Perseids    schedule 31.08.2012    source источник
comment
Приведите пример желаемого ввода и вывода, чтобы пояснить, что делает конструкция Haskell let.   -  person BrenBarn    schedule 31.08.2012
comment
возможный дубликат Можно ли добавить куда предложение со списком?   -  person sdcvvc    schedule 03.09.2012


Ответы (10)


Вы можете использовать временное понимание списка

[(barcode(productId), metric(size)) for name, size in [lookup(productId)]][0]

или, что то же самое, генераторное выражение

next((barcode(productId), metric(size)) for name, size in [lookup(productId)])

но и то, и другое ужасно.

Другой (ужасный) метод - через временную лямбду, которую вы вызываете немедленно

(lambda (name, size): (barcode(productId), metric(size)))(lookup(productId))

Я думаю, что рекомендуемым способом "Pythonic" было бы просто определить функцию, например

def barcode_metric(productId):
   name, size = lookup(productId)
   return barcode(productId), metric(size)
list2 = [barcode_metric(productId) for productId in list]
person huon    schedule 31.08.2012
comment
Мне все еще трудно понять, почему у вас есть список из одного элемента, содержащий только результат одного вызова lookup. Разве первые два примера не генерируют ValueError? - person senderle; 31.08.2012
comment
@senderle Нет, они работают. Он генерирует список, содержащий кортеж lookup, возвращаемый как одиночный элемент. Затем он выполняет итерацию по этому списку (поэтому внешний цикл тоже выполняется только один раз), чтобы распаковать элементы и передать их другим функциям одним махом. - person ; 31.08.2012
comment
@delnan, ах, так это должно быть внутри понимания внешнего списка. У меня было наоборот. В любом случае, +1 за функцию. - person senderle; 31.08.2012

Последние версии Python позволяют использовать несколько предложений for в выражении генератора, поэтому теперь вы можете сделать что-то вроде:

list2 = [ barcode(productID), metric(size)
          for productID in list
          for (name,size) in (lookup(productID),) ]

что похоже на то, что также предоставляет Haskell:

list2 = [ (barcode productID, metric size)
        | productID <- list
        , let (name,size) = lookup productID ]

и денотативно эквивалентен

list2 = [ (barcode productID, metric size) 
        | productID <- list
        , (name,size) <- [lookup productID] ]
person b0fh    schedule 06.11.2015
comment
Использование кортежа длины 1, чтобы for ... in совпадало с let .. in... великолепно! - person fouronnes; 17.10.2020

Такого нет. Вы можете эмулировать его таким же образом, как let обессахаживается до лямбда-исчисления (let x = foo in bar ‹=> (\x -> bar) (foo)).

Наиболее удобочитаемая альтернатива зависит от обстоятельств. Для вашего конкретного примера я бы выбрал что-то вроде [barcode(productId), metric(size) for productId, (_, size) in zip(productIds, map(lookup, productIds))] (действительно некрасиво, если подумать, проще, если вам тоже не нужен productId, тогда вы можете использовать map) или явный цикл for (в генераторе):

def barcodes_and_metrics(productIds):
    for productId in productIds:
        _, size = lookup(productId)
        yield barcode(productId), metric(size)
person Community    schedule 31.08.2012
comment
Я был бы обеспокоен тем, что выполнение этого с лямбда-выражением в коде Python может иметь нежелательные побочные эффекты, такие как ваша дверь, выбитая ногой толпой разгневанных питонистов, несущих факелы и вилы. - person C. A. McCann; 31.08.2012
comment
@senderle Ты прав, исправил это сейчас. Понимание списка становится тем уродливее и уродливее, чем правильнее оно становится. - person ; 31.08.2012
comment
Я выбрал ваше второе предложение. Третий был немного излишним в реальном коде, и хотя мне нравится первый с теоретической точки зрения, Макканн, вероятно, прав. Спасибо. - person Perseids; 31.08.2012
comment
@Perseids Честно говоря, я считаю вторую строчку почти такой же плохой, как и первую. Это умно и остроумно, но не читабельно и не понятно. Третий означает больше строк, но он все еще довольно лаконичен, если используется в нескольких местах, и действительно очевиден. - person ; 31.08.2012
comment
Находясь в Риме, поступай, как римляне. Идти против ожидаемого стиля и идиомы неразумно в большинстве языков программирования, вдвойне неразумно в Python (дополнительная изюминка: ...но в perl это форма искусства). - person C. A. McCann; 31.08.2012
comment
Что ж, ты прав. Это даже становится немного сложнее в реальном коде. Я пойду с предложением dbaupp, которое подходит немного лучше, чем ваш итератор, так как мне все равно нужен этот список - person Perseids; 31.08.2012

Несколько предложений for в ответе b0fh - это стиль, который я лично использую уже некоторое время, так как я считаю, что он обеспечивает большую ясность и не загромождает пространство имен временными функциями. Однако, если скорость является проблемой, важно помнить, что временное создание списка из одного элемента занимает заметно больше времени, чем создание списка из одного кортежа.

Сравнивая скорость различных решений в этой ветке, я обнаружил, что уродливый лямбда-хак самый медленный, за ним следуют вложенные генераторы, а затем решение от b0fh. Однако все они были превзойдены победителем с одним кортежем:

list2 = [ barcode(productID), metric(size)
          for productID in list
          for (_, size) in (lookup(productID),) ]

Это может быть не так важно для вопроса OP, но есть и другие случаи, когда ясность может быть значительно улучшена, а скорость увеличена в случаях, когда можно использовать понимание списка, используя один кортеж вместо списков для фиктивных итераторов.

person dalum    schedule 24.01.2016

Только догадываясь о том, что делает Haskell, вот альтернатива. Он использует то, что известно в Python как «понимание списка».

[barcode(productId), metric(size)
    for (productId, (name, size)) in [
        (productId, lookup(productId)) for productId in list_]
]

Вы можете включить использование lambda:, как предложили другие.

person vsh    schedule 31.08.2012
comment
Между прочим, если мне не изменяет память, понимание списков в Python во многом было вдохновлено Haskell. Помимо синтаксических мелочей и странных расширений компилятора Haskell, они очень похожи. Haskell также давно использует синтаксические пробелы. ;] - person C. A. McCann; 31.08.2012
comment
Вы говорите, что python делает с Haskell то, что Java должна была делать (и, возможно, делала) с Lisp, то есть переносить приятные вещи из области функций в область объектов? :) - person Jonas Kölker; 24.11.2014

Поскольку вы просили о лучшей читабельности, вы могли бы рассмотреть лямбда-вариант, но с небольшим поворотом: инициализируйте аргументы. Вот различные варианты, которые я использую сам, начиная с первого, который я попробовал, и заканчивая тем, который я использую чаще всего сейчас.

Предположим, у нас есть функция (не показана), которая получает data_structure в качестве аргумента, и вам нужно неоднократно получать из нее x.

Первая попытка (согласно ответу 2012 года от huon):

(lambda x:
    x * x + 42 * x)
  (data_structure['a']['b'])

С несколькими символами это становится менее читаемым, поэтому я попробовал:

(lambda x, y:
    x * x + 42 * x + y)
  (x = data_structure['a']['b'],
   y = 16)

Это все еще не очень читабельно, так как повторяет символические имена. Итак, я попробовал:

(lambda x = data_structure['a']['b'],
        y = 16:
  x * x + 42 * x + y)()

Это почти читается как выражение «пусть». Позиционирование и форматирование заданий, конечно же, ваше.

Эту идиому легко узнать по началу '(' и окончанию '()'.

В функциональных выражениях (также и в Python) многие круглые скобки имеют тенденцию накапливаться в конце. Нечетный '(' легко заметить.

person Erik    schedule 05.12.2017
comment
Мне очень нравится ваша третья версия. Как вы говорите, это даже похоже на выражение let! - person RussAbbott; 01.11.2018

class let:
    def __init__(self, var):
        self.x = var

    def __enter__(self):
        return self.x

    def __exit__(self, type, value, traceback):
        pass

with let(os.path) as p:
    print(p)

Но это фактически то же самое, что и p = os.path, поскольку область действия p не ограничивается блоком with. Для этого вам понадобится

class let:
    def __init__(self, var):
        self.value = var
    def __enter__(self):
        return self
    def __exit__(self, type, value, traceback):
        del var.value
        var.value = None

with let(os.path) as var:
    print(var.value)  # same as print(os.path)
print(var.value)  # same as print(None)

Здесь var.value будет None вне блока with, но os.path внутри него.

person crizCraig    schedule 22.04.2019

Чтобы получить что-то смутно сопоставимое, вам нужно либо сделать два понимания или карты, либо определить новую функцию. Один подход, который еще не был предложен, состоит в том, чтобы разбить его на две строки вот так. Я считаю, что это несколько читабельно; хотя, вероятно, правильно определить свою собственную функцию:

pids_names_sizes = (pid, lookup(pid) for pid in list1)
list2 = [(barcode(pid), metric(size)) for pid, (name, size) in pids_names_sizes]
person senderle    schedule 31.08.2012

Хотя вы можете просто написать это как:

list2 = [(barcode(pid), metric(lookup(pid)[1]))
         for pid in list]

Вы можете определить LET самостоятельно, чтобы получить:

list2 = [LET(('size', lookup(pid)[1]),
             lambda o: (barcode(pid), metric(o.size)))
         for pid in list]

или даже:

list2 = map(lambda pid: LET(('name_size', lookup(pid),
                             'size', lambda o: o.name_size[1]),
                            lambda o: (barcode(pid), metric(o.size))),
            list)

следующее:

import types

def _obj():
  return lambda: None

def LET(bindings, body, env=None):
  '''Introduce local bindings.
  ex: LET(('a', 1,
           'b', 2),
          lambda o: [o.a, o.b])
  gives: [1, 2]

  Bindings down the chain can depend on
  the ones above them through a lambda.
  ex: LET(('a', 1,
           'b', lambda o: o.a + 1),
          lambda o: o.b)
  gives: 2
  '''
  if len(bindings) == 0:
    return body(env)

  env = env or _obj()
  k, v = bindings[:2]
  if isinstance(v, types.FunctionType):
    v = v(env)

  setattr(env, k, v)
  return LET(bindings[2:], body, env)
person divs1210    schedule 24.06.2017

В Python 3.8 были добавлены выражения присваивания с использованием оператора :=: PEP 572 .

Это можно использовать как let в Haskell, хотя итерируемая распаковка не поддерживается.

list2 = [
    (lookup_result := lookup(productId), # store tuple since iterable unpacking isn't supported
     name := lookup_result[0], # manually unpack tuple
     size := lookup_result[1],
     (barcode(productId), metric(size)))[-1] # put result as the last item in the tuple, then extract on the result using the (...)[-1]
    for productId in list1
]

Обратите внимание, что это похоже на обычное назначение Python.

person Oli    schedule 05.02.2021