Advertisement
Garey

Fridge - Auto Close

Apr 7th, 2022
2,842
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Erlang 18.19 KB | None | 0 0
  1. defmodule Product do
  2.   defstruct name: '', count: 1
  3.  
  4.   defimpl String.Chars, for: Product do
  5.     def to_string(product) do
  6.       "Product Name: #{product.name} -- Product Count: #{product.count}"
  7.     end
  8.   end
  9. end
  10.  
  11. defmodule FridgeState do
  12.   defstruct productList: [], fridgeState: 'closed'
  13.  
  14.   defimpl String.Chars, for: FridgeState do
  15.     def to_string(state) do
  16.       "Fridge Products: #{state.productList} -- Fridge State: #{state.fridgeState}"
  17.     end
  18.   end
  19. end
  20.  
  21. defmodule Fridge do
  22.  
  23.   use GenServer
  24.  
  25.   ########################
  26.   # START/STOP FUNCTIONS #
  27.   ########################
  28.  
  29.   def start_link() do
  30.     GenServer.start_link(__MODULE__, %FridgeState{})
  31.   end
  32.  
  33.   def stop(pid) do
  34.     GenServer.stop(pid, :normal, :infinity)
  35.   end
  36.  
  37.   #######
  38.   # API #
  39.   #######
  40.  
  41.   def open(pid), do: GenServer.call(pid, :open)
  42.   def close(pid), do: GenServer.call(pid, :close)
  43.   def put(pid, product), do: GenServer.call(pid, {:put, product})
  44.   def pull(pid, product), do: GenServer.call(pid, {:pull, product})
  45.  
  46.   ####################
  47.   # SERVER CALLBACKS #
  48.   ####################
  49.  
  50.   @doc """
  51.  This method is called when the Fridge instance of the GenServer is initialized
  52.  
  53.  Този метод се използва изцяло за инициализация. Тук можем да си слагаме някакви API извиквания към външни service-и.
  54.  """
  55.   @impl true
  56.   def init(fridge) do
  57.     {:ok, fridge}
  58.   end
  59.  
  60.   @doc """
  61.    This method is called when the Fridge instance of the GenServer is terminated
  62.  
  63.  Този метод се извиква при спиране на GenServer-а. Тук можем да си затваряме всякакви връзки с други приложения или да зачистваме използваната от нас памет.
  64.  """
  65.   @impl true
  66.   def terminate(_reason, _list) do
  67.     :ok
  68.   end
  69.  
  70.   ################
  71.   # API HANDLERS #
  72.   ################
  73.  
  74.   @doc """
  75.  This method is used to change the fridge state from closed to open.
  76.  
  77.  Този метод е наследен от макрото use GenServer. Чрез него могат да се правят извиквания на
  78.  команди по наш избор. Първият параметър е ключовата дума на командата. Вторият параметър е
  79.  променлива, която съдържа в себе си адреса извикващият командата. Третият параметър е наша
  80.  собствена променлива, която дефинираме като state или състояние на GenServer-а ни.
  81.  
  82.  """
  83.   @impl true
  84.   def handle_call(:open, _from, state) do
  85.     #
  86.     # Понеже в Elixir няма как да се промени вече съществуваща променлива, се създава нова такава.
  87.     # С променените параметри, които искаме да се използват.
  88.     #
  89.     # Подаваме текущият списък с продукти, който имаме за целият lifecycle на приложението (state.productList).
  90.     # След което си променяме fridgeState променливата на closed.
  91.     #
  92.     updatedState = %FridgeState{productList: state.productList,  fridgeState: 'open'}
  93.  
  94.     # Връща се отговор на командата, която е заявена с обновеното състояние на приложението.
  95.     {:reply, updatedState, updatedState}
  96.   end
  97.  
  98.   @doc """
  99.  This method is used to change the fridge state from open to closed.
  100.  
  101.  Този метод е наследен от макрото use GenServer. Чрез него могат да се правят извиквания на
  102.  команди по наш избор. Първият параметър е ключовата дума на командата. Вторият параметър е
  103.  променлива, която съдържа в себе си адреса извикващият командата. Третият параметър е наша
  104.  собствена променлива, която дефинираме като state или състояние на GenServer-а ни.
  105.  
  106.  """
  107.   @impl true
  108.   def handle_call(:close, _from, state) do
  109.     #
  110.     # Понеже в Elixir няма как да се промени вече съществуваща променлива, се създава нова такава.
  111.     # С променените параметри, които искаме да се използват.
  112.     #
  113.     # Подаваме текущият списък с продукти, който имаме за целият lifecycle на приложението (state.productList).
  114.     # След което си променяме fridgeState променливата на closed.
  115.     #
  116.     updatedState = %FridgeState{productList: state.productList,  fridgeState: 'closed'}
  117.  
  118.     # Връща се отговор на командата, която е заявена с обновеното състояние на приложението.
  119.     {:reply, updatedState, updatedState}
  120.   end
  121.  
  122.   @doc """
  123.  This method handles the 'put' client call.
  124.  
  125.  Този метод се използва за обработката на put командата към Fridge структурата.
  126.  Първият параметър се нарича tuple. Тоест имаме две променливи в една, които можем да ги достъпим.
  127.  Първата променлива служи за име на командата, а втората променлива е параметъра, който е нужен за подаване.
  128.  Вторият параметър е променлива, която съдържа в себе си адреса извикващият командата.
  129.  Третият параметър е наша собствена променлива, която дефинираме като state или състояние на GenServer-а ни.
  130.  """
  131.   def handle_call({:put, product}, _from, state) do
  132.     #
  133.     # Ако хладилника е отворен или ако е различно от отворен
  134.     #
  135.     if state.fridgeState != 'closed' do
  136.  
  137.       #
  138.       # Създаваме си една анонимна функция или ламбда функция,
  139.       # за да не пишем като луди всеки път условието на фунцкиите,
  140.       # които ще използваме за намиране на дадени елементи от списъка
  141.       # ни с продукти.
  142.       #
  143.       predicate = fn pd -> pd.name === product.name end
  144.  
  145.       #
  146.       # Създаваме си tuple с три променливи.
  147.       #
  148.       # Първата променлива ще я използваме, за да проверим дали такъв продукт
  149.       # съществува в списъка ни с продукти.
  150.       #
  151.       # Втората променлива ще я използваме, за да намерим индекса на елемента,
  152.       # който търсим, за да можем да променим стойността на елемента в списъка.
  153.       #
  154.       # И третата променлива ще я използваме, за да намерим текущият продукт, който
  155.       # искаме да добавим.
  156.       #
  157.       {
  158.         isProductFound,
  159.         productIndex,
  160.         currentProduct
  161.       } = {
  162.         Enum.any?(state.productList, predicate),
  163.         Enum.find_index(state.productList, predicate),
  164.         Enum.find(state.productList, false, predicate)
  165.       }
  166.  
  167.       #
  168.       # Ако продуктът вече съществува
  169.       #
  170.       if isProductFound do
  171.         #
  172.         # Създаваме си нов продукт, който е модифицирана версия на текущият ни.
  173.         # Прави се с цел да няма повтарящи се продукти, а като се срещне два пъти, например, мляко,
  174.         # да увеличи продуктите с единица, а не да добави нов продукт със същото име и count 1.
  175.         #
  176.         updatedProduct = %Product{name: currentProduct.name, count: currentProduct.count + 1}
  177.  
  178.         #
  179.         # Създаваме си нов списък, който е модифицирана версия на нашият списък с продукти,
  180.         # като в този списък ще заменим продукта с индекс productIndex с нашият модифициран
  181.         # продукт.
  182.         #
  183.         updatedList = List.replace_at(state.productList, productIndex, updatedProduct)
  184.  
  185.         #
  186.         # Създаваме си нова версия на състоянието на приложението с обновената информация.
  187.         #
  188.         updatedState = %FridgeState{ productList: updatedList, fridgeState: 'closed' }
  189.  
  190.         #
  191.         # Връщаме отговор с модифицираното състояние на приложението
  192.         #
  193.         {:reply, updatedState, updatedState}
  194.       else
  195.         #
  196.         # Създаваме си нов списък, който е модифицирана версия на нашият списък с продукти,
  197.         # като в този списък ще добавим продукта, който сме подали като параметър.
  198.         #
  199.         updatedList = [product|state.productList]
  200.  
  201.         #
  202.         # Създаваме си нова версия на състоянието на приложението с обновената информация.
  203.         #
  204.         updatedState = %FridgeState{ productList: updatedList, fridgeState: 'closed' }
  205.  
  206.         #
  207.         # Връщаме отговор с модифицираното състояние на приложението
  208.         #
  209.         {:reply, updatedState, updatedState}
  210.       end
  211.     else
  212.       #
  213.       # В случай, че състоянието на Fridge-а е 'closed', връщаме текущото състояние на
  214.       # приложението с информативно съобщение 'Unable to use the fridge while closed'.
  215.       #
  216.       {:reply, "Unable to use fridge while closed", state}
  217.     end
  218.   end
  219.  
  220.   @doc """
  221.  This method handles the 'pull' client call.
  222.  
  223.  Този метод се използва за обработката на pull командата към Fridge структурата.
  224.  Първият параметър се нарича tuple. Тоест имаме две променливи в една, които можем да ги достъпим.
  225.  Първата променлива служи за име на командата, а втората променлива е параметъра, който е нужен за подаване.
  226.  Вторият параметър е променлива, която съдържа в себе си адреса извикващият командата.
  227.  Третият параметър е наша собствена променлива, която дефинираме като state или състояние на GenServer-а ни.
  228.  """
  229.   def handle_call({:pull, product}, _from, state) do
  230.  
  231.     #
  232.     # Ако хладилника е отворен или ако е различно от отворен
  233.     #
  234.     if state.fridgeState != 'closed' do
  235.  
  236.       #
  237.       # Създаваме си една анонимна функция или ламбда функция,
  238.       # за да не пишем като луди всеки път условието на фунцкиите,
  239.       # които ще използваме за намиране на дадени елементи от списъка
  240.       # ни с продукти.
  241.       #
  242.       predicate = fn pd -> pd.name === product.name end
  243.  
  244.       #
  245.       # Създаваме си tuple с три променливи.
  246.       #
  247.       # Първата променлива ще я използваме, за да проверим дали такъв продукт
  248.       # съществува в списъка ни с продукти.
  249.       #
  250.       # Втората променлива ще я използваме, за да намерим индекса на елемента,
  251.       # който търсим, за да можем да променим стойността на елемента в списъка.
  252.       #
  253.       # И третата променлива ще я използваме, за да намерим текущият продукт, който
  254.       # искаме да добавим.
  255.       #
  256.       {
  257.         isProductFound,
  258.         productIndex,
  259.         currentProduct
  260.       } = {
  261.         Enum.any?(state.productList, predicate),
  262.         Enum.find_index(state.productList, predicate),
  263.         Enum.find(state.productList, false, predicate)
  264.       }
  265.  
  266.       #
  267.       # Ако продуктът вече съществува
  268.       #
  269.       if isProductFound do
  270.         #
  271.         # Проверяваме резултата, ако извадим -1 дали ще остане под 1
  272.         # Ако остане, го изтриваме от списъка с продукти.
  273.         #
  274.         if (currentProduct.count - 1) < 1 do
  275.  
  276.           #
  277.           # Създаваме си нов списък, който е модифицирана версия на нашият списък с продукти,
  278.           # като в този списък ще изтрием текущият продукт.
  279.           #
  280.           updatedList = List.delete(state.productList, currentProduct)
  281.  
  282.  
  283.           #
  284.           # Създаваме си нова версия на състоянието на приложението с обновената информация.
  285.           #
  286.           updatedState = %FridgeState{ productList: updatedList, fridgeState: 'closed' }
  287.  
  288.           #
  289.           # Връщаме отговор с модифицираното състояние на приложението
  290.           #
  291.           {:reply, updatedState, updatedState}
  292.         else
  293.  
  294.           #
  295.           # Създаваме си нов продукт, който е модифицирана версия на текущият ни.
  296.           #
  297.           #
  298.           updatedProduct = %Product{name: currentProduct.name, count: currentProduct.count - 1}
  299.  
  300.           #
  301.           # Създаваме си нов списък, който е модифицирана версия на нашият списък с продукти,
  302.           # като в този списък ще заменим продукта с индекс productIndex с нашият модифициран
  303.           # продукт.
  304.           #
  305.           updatedList = List.replace_at(state.productList, productIndex, updatedProduct)
  306.  
  307.           #
  308.           # Създаваме си нова версия на състоянието на приложението с обновената информация.
  309.           #
  310.           updatedState = %FridgeState{ productList: updatedList, fridgeState: 'closed' }
  311.  
  312.           #
  313.           # Връщаме отговор с модифицираното състояние на приложението
  314.           #
  315.           {:reply, updatedState, updatedState}
  316.         end
  317.       else
  318.         #
  319.         # В случай, че не намери продукта, който търсим, връщаме отговор с текущото
  320.         # състояние на приложението и съобщение 'Product not found'.
  321.         #
  322.         {:reply, "Product not found", state}
  323.       end
  324.     else
  325.       #
  326.       # В случай, че състоянието на Fridge-а е 'closed', връщаме текущото състояние на
  327.       # приложението с информативно съобщение 'Unable to use the fridge while closed'.
  328.       #
  329.       {:reply, "Unable to use fridge while closed", state}
  330.     end
  331.   end
  332. end
  333.  
  334. @doc """
  335. Създаваме си входна точка на приложението
  336. """
  337. defmodule Main do
  338.   @doc """
  339.  Създаваме си входна точка на приложението
  340.  """
  341.   def main do
  342.     #
  343.     # Стартираме нашият GenServer, който ни връща tuple от състояние
  344.     # дали е запалил успешно и pid - id на процеса.
  345.     #
  346.     # pid-а се ползва за всяка една функция на GenServer-а.
  347.     #
  348.     {:ok, pid} = Fridge.start_link()
  349.  
  350.     #
  351.     # Започваме да използваме нашите декларирани клиентски методи.
  352.     #
  353.     Fridge.open(pid)
  354.     Fridge.put(pid, %Product{name: 'eggs'})
  355.     Fridge.close(pid)
  356.  
  357.     Fridge.open(pid)
  358.     Fridge.put(pid, %Product{name: 'cheese'})
  359.     Fridge.close(pid)
  360.  
  361.     Fridge.open(pid)
  362.     Fridge.put(pid, %Product{name: 'cheese'})
  363.     Fridge.close(pid)
  364.  
  365.     Fridge.open(pid)
  366.     Fridge.put(pid, %Product{name: 'cheese'})
  367.     Fridge.close(pid)
  368.  
  369.     Fridge.open(pid)
  370.     Fridge.put(pid, %Product{name: 'cheese'})
  371.     Fridge.close(pid)
  372.  
  373.     #
  374.     # Започваме да изваждаме продукти
  375.     #
  376.     Fridge.open(pid)
  377.     Fridge.pull(pid, %Product{name: 'cheese'})
  378.     Fridge.close(pid)
  379.  
  380.     Fridge.open(pid)
  381.     Fridge.pull(pid, %Product{name: 'cheese'})
  382.     Fridge.close(pid)
  383.  
  384.     failedState = Fridge.pull(pid, %Product{name: 'eggs'})
  385.     #
  386.     # Проверяваме какво ни е върнало като резултат, ако
  387.     # не сме отворили хладилника.
  388.     #
  389.     IO.inspect failedState
  390.  
  391.     #
  392.     # Спираме нашият GenServer - Fridge.
  393.     #
  394.     Fridge.stop(pid)
  395.   end
  396. end
  397.  
  398. #
  399. # Извикваме входната точка на приложението.
  400. #
  401. Main.main
  402.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement