Обдумывая название паттерна, наблюдаемого в Elm, и другие подобные случаи

В настоящее время я студент FP. Когда я смотрю на различные варианты синтаксиса, предлагаемые различными функциональными языками, я наткнулся на шаблон в примере кода Elm. Мне это интересно.

Вот пример кода

myList = [{foo = "bar1"},{foo = "bar2"}]    
foos = myList |> List.map .foo

В последней строке здесь List.map передается .foo. Я считаю, что этот стиль называется бесточечным, но как насчет конкретного шаблона передачи атрибута в функцию List.map?

Это более распространенное явление? Возможно ли это сделать в Haskell? Фа#? Скала? Спасибо за любую помощь.

Какое (или есть ли) официальное (или неформальное?) имя для шаблона здесь? Свойство объекта используется как сокращение для функции, которая берет объект и вызывает для него указанное свойство?


person Babakness    schedule 29.01.2018    source источник
comment
В Scala потребуется еще один символ: _.foo, где _ — это заполнитель для аргумента по умолчанию. См. также stackoverflow.com/questions/8000903/   -  person SergGr    schedule 30.01.2018
comment
Вы не передаете атрибут map, вы передаете его геттеру.   -  person Willem Van Onsem    schedule 30.01.2018
comment
В F# это недоступно, но по этому поводу ведется активное обсуждение: github.com /fsharp/fslang-suggestions/issues/506. Я не думаю, что у шаблона есть какое-то название в F#.   -  person Tomas Petricek    schedule 30.01.2018
comment
Это просто похоже на стандартное использование map. Геттеры — это специальные функции, сгенерированные определением записи и со специальным соглашением об именах, но в остальном они являются такими же функциями, как и любые другие.   -  person Rein Henrichs    schedule 30.01.2018


Ответы (2)


На самом деле это не без точки, а скорее синтаксический сахар и оператор переадресации конвейера. Чтобы узнать о бесплатных точках, см. эту статью.

Это можно записать в fsharp следующим образом:

let foos = myList |> List.map (fun x -> x.foo)

И вы можете сразу увидеть, что это эквивалентно

List.map (fun x -> x.foo) myList

Таким образом, оператор канала просто переворачивает аргументы и упрощает объединение операций в цепочку. Итак, вы передаете свою функцию и список на карту. А синтаксический сахар в Elm позволяет пропустить параметр функции, просто написав .foo. Я думаю, что эта функция очень удобна, кстати.

Без точки будет, когда вы избегаете указания параметров функции. Это типичный FP, но его может быть трудно читать, когда он становится сложным.

Пример:

let mySum x y = x + y
//val mySum : x:int -> y:int -> int
mySum 4 7 //11

Это без точек:

let mySum2 = (+)
//val mySum2 : (int -> int -> int)
mySum2 4 7 //11  
person s952163    schedule 30.01.2018

Если вы думаете о своем списке как о «наборе данных» или «таблице» и рассматриваете каждый элемент в списке как «строку», а определение типа данных элементов как перечисление «атрибутов», то то, что вы get — это своего рода "проекция" в смысле реляционной алгебры: https://en.wikipedia.org/wiki/Projection_(relational_алгебра) .

Вот Scala-пример, который кажется чем-то похожим на SQL:

case class Row(id: Int, name: String, surname: String, age: Int)

val data = List(
  Row(0, "Bob", "Smith", 25),
  Row(1, "Charles", "Miller", 35),
  Row(2, "Drew", "Shephard", 45),
  Row(3, "Evan", "Bishop", 55)
)

val surnames = data map (_.surname)
val ages = data map (_.age)
val selectIdName = data map { row => (row.id, row.name) }

println(surnames)
// List(Smith, Miller, Shephard, Bishop)

println(selectIdName)
// List((0,Bob), (1,Charles), (2,Drew), (3,Evan))

Здесь _.fieldName — это краткий синтаксис литерала встроенной функции типа Row => TypeOfTheField.

В Haskell это довольно тривиально, потому что объявление типа данных записи автоматически помещает все функции получения в область видимости:

data Row = Row { id :: Int
               , name :: String
               , surname :: String
               , age :: Int
               } deriving Show

main = let dataset = [ Row 0 "Bob" "Smith" 25
                     , Row 1 "Charles" "Miller" 35
                     , Row 2 "Drew" "Shephard" 45
                     , Row 3 "Evan" "Bishop" 55
                     ]
       in print $ map name dataset
-- prints ["Bob","Charles","Drew","Evan"]

Даже в Java есть что-то подобное с версии 8:

import java.util.*;
import java.util.stream.*;
import static java.util.stream.Collectors.*;

class JavaProjectionExample {
  private static class Row {
    private final int id;
    private final String name;
    private final String surname;
    private final int age;
    public Row(int id, String name, String surname, int age) {
      super();
      this.id = id;
      this.name = name;
      this.surname = surname;
      this.age = age;
    }
    public int getId() {
      return this.id;
    }
    public String getName() {
      return this.name;
    }
    public String getSurname() {
      return this.surname;
    }
    public int getAge() {
      return this.age;
    }
  }

  public static void main(String[] args) {
    List<Row> data = Arrays.asList(
      new Row(0, "Bob", "Smith", 25),
      new Row(1, "Charles", "Miller", 35),
      new Row(2, "Drew", "Shephard", 45),
      new Row(3, "Evan", "Bishop", 55)
    );

    List<Integer> ids = data.stream().map(Row::getId).collect(toList());
    List<String> names = data.stream().map(Row::getName).collect(toList());

    System.out.println(ids);
    System.out.println(names);
  }
}

Здесь Row::getterName — это специальный синтаксис для методов-геттеров, это значение типа Function<Row, FieldType>.

person Andrey Tyukin    schedule 29.01.2018