Skip to content

Commit

Permalink
practice 2 library database
Browse files Browse the repository at this point in the history
  • Loading branch information
dru committed Nov 23, 2023
1 parent a3504d2 commit 5ef029a
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 0 deletions.
1 change: 1 addition & 0 deletions index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ layout: default
* Лекция 3. [Scala basic II. От type конструкторов до коллекций](lectures/scala_lecture_3/index.html)
* Лекция 4. [Функциональное программирование. Концепция (PowerPoint)](lectures/scala_lecture_4.pptx)
* Лекция 5. [Функциональное программирование. Type classes и абстракции для ФП](lectures/scala_lecture_5.html)
* Практика [Функциональное программирование. База данных библиотеки](lectures/practice2)


### Домашние задания
Expand Down
12 changes: 12 additions & 0 deletions lectures/practice2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.bsp/
.idea/
target/
boot/
logs/
lib_managed/
src_managed/
project/plugins/project/
*.conf
.bloop
.metals
.vscode
12 changes: 12 additions & 0 deletions lectures/practice2/build.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
ThisBuild / version := "0.1.0-SNAPSHOT"

ThisBuild / scalaVersion := "2.13.12"

lazy val root = (project in file("."))
.settings(
name := "practice_2023",
// https://mvnrepository.com/artifact/org.typelevel/cats-core
libraryDependencies += "org.typelevel" %% "cats-core" % "2.10.0",
// https://mvnrepository.com/artifact/org.typelevel/cats-effect
libraryDependencies += "org.typelevel" %% "cats-effect" % "3.6-0142603"
)
1 change: 1 addition & 0 deletions lectures/practice2/project/build.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sbt.version = 1.9.7
6 changes: 6 additions & 0 deletions lectures/practice2/project/metals.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// DO NOT EDIT! This file is auto-generated.

// This file enables sbt-bloop to create bloop config files.

addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.11")

6 changes: 6 additions & 0 deletions lectures/practice2/project/project/metals.sbt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// DO NOT EDIT! This file is auto-generated.

// This file enables sbt-bloop to create bloop config files.

addSbtPlugin("ch.epfl.scala" % "sbt-bloop" % "1.5.11")

3 changes: 3 additions & 0 deletions lectures/practice2/src/main/scala/online/Book.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package online

case class Book(name: String, author: String)
3 changes: 3 additions & 0 deletions lectures/practice2/src/main/scala/online/Client.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package online

case class Client(name: String)
66 changes: 66 additions & 0 deletions lectures/practice2/src/main/scala/online/CommandParser.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package online

object CommandParser {
def parse(commandLine: String): LibraryOperation[String] = commandLine.split(' ').toList match {
case command :: arguments if arguments.nonEmpty =>
command match {

case "addClient" => LibraryOperation(db =>
db.copy(clients = db.clients :+ Client(arguments.mkString(" "))) -> Right("ok")
)

case "addBook" => {
val totalArgument = arguments.mkString(" ").split('|')
val name = totalArgument.headOption
val author = totalArgument.drop(1).headOption
(name, author) match {
case (Some(name), Some(author)) => LibraryOperation { db => db.copy(books = db.books :+ Book(name, author)) -> Right("ok") }
case _ => LibraryOperation.failure(new RuntimeException("wrong syntax"))
}
}

case _ => LibraryOperation.failure(new Exception("unknown command"))
}
case _ => LibraryOperation.failure(new Exception("wrong syntax"))
}
}

trait Command {
def couldParse(commandLine: String): Boolean

def parse(commandLine: String): LibraryOperation[String]
}

class UniversalCommandParser(additionalCommands: List[Command]) {

val defaultCommands: List[Command] = List(
new Command {
override def couldParse(commandLine: String): Boolean = commandLine.split(' ').toList match {
case command :: arguments if arguments.nonEmpty =>
command match {
case "addClient" => true
case _ => false
}
case _ => false
}

override def parse(commandLine: String): LibraryOperation[String] = commandLine.split(' ').toList match {
case command :: arguments if arguments.nonEmpty =>
command match {
case "addClient" => LibraryOperation(db =>
db.copy(clients = db.clients :+ Client(arguments.mkString(" "))) -> Right("ok")
)
case _ => LibraryOperation.failure(new Exception("unknownCommand"))
}
case _ => LibraryOperation.failure(new Exception("wrong syntax"))
}
}
)

def parse(commandLine: String): LibraryOperation[String] = (defaultCommands ++ additionalCommands)
.dropWhile(!_.couldParse(commandLine))
.headOption
.map(_.parse(commandLine))
.getOrElse(LibraryOperation.failure(new Exception("unknown command")))
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package online

case class LibraryDatabase(clients: List[Client], books: List[Book], issuedBoos: Map[Book, Client])
39 changes: 39 additions & 0 deletions lectures/practice2/src/main/scala/online/LibraryOperation.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package online

case class LibraryOperation[+R](run: LibraryDatabase => (LibraryDatabase, Either[Throwable, R])) {

def map[R2](f: R => R2): LibraryOperation[R2] = LibraryOperation { db =>
val (newDb, res) = run(db)

res match {
case Left(error) => newDb -> Left(error)
case Right(value) => newDb -> Right(f(value))
}

}

def flatMap[R2](f: R => LibraryOperation[R2]): LibraryOperation[R2] = LibraryOperation { db =>
val (newDb, res: Either[Throwable, R]) = run(db)

res match {
case Left(error) => newDb -> Left(error)
case Right(value) =>

val r1: LibraryOperation[R2] = f(value)

val r2: (LibraryDatabase, Either[Throwable, R2]) = r1.run(newDb)
val r3: Either[Throwable, R2] = r2._2


r2._1 -> r2._2
}
}
}

object LibraryOperation{

def failure(e: Throwable) = LibraryOperation(db => db -> Left(e))

def pure[R](value: R): LibraryOperation[R] = LibraryOperation(db => db -> Right(value))

}
99 changes: 99 additions & 0 deletions lectures/practice2/src/main/scala/online/Main.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package online

import cats.effect.IO
import cats.effect.unsafe.implicits.global

import scala.concurrent.duration.DurationInt
import scala.concurrent.{Await, ExecutionContextExecutor, Future}

/**
* Написать программу, которая реализует потребности базы данных библиотеки:
* 1. Пополнение фонда книгой
* 2. Регистрация пользователя (выдача читательского билета)
* 3. Выдача книги на руки
* 4. Возврат книги
* ....
*
* Программе на вход подается набор инструкций (с клавиатуры, по сети, из файла, ...),
* а программа должна их исполнять
*
* Набор команд программы может расширяться
*/


object Main extends App {
val lib = LibraryDatabase(Nil, Nil, Map.empty)

// val totalOperation = for {
// _ <- LibraryOperation(db => db.copy(clients = db.clients :+ Client("Vasya")) -> Right("ok"))
// _ <- LibraryOperation(db => db.copy(books = db.books :+ Book("Властелин колец", "Tolkin")) -> Right("ok"))
// r <- LibraryOperation(db => db.copy(books = db.books :+ Book("Designing data intensive applications", "Клепинин")) -> Right("ok"))
// } yield r
//
// val (newDb, result) = totalOperation.run(lib)
// println(newDb)
// println(result)

//// -------------------------------



// val parsedCommands = commands.map(CommandParser.parse)
//
// val resultOperation: LibraryOperation[String] = parsedCommands.foldLeft(LibraryOperation.pure(""))((op1, op2) => op1.flatMap(_ => op2))
//
// val (newDb, res) = resultOperation.run(lib)
// println(newDb, res)

val dropClientCommand = new Command {
override def couldParse(commandLine: String): Boolean = commandLine.split(' ').toList match {
case command :: arguments if arguments.nonEmpty =>
command match {
case "dropClient" => true
case _ => false
}
case _ => false
}

override def parse(commandLine: String): LibraryOperation[String] = commandLine.split(' ').toList match {
case command :: arguments if arguments.nonEmpty =>
command match {
case "dropClient" => LibraryOperation(db => db.copy(clients = db.clients.filter(_.name != arguments.mkString(" "))) -> Right("ok"))
case _ => LibraryOperation.failure(new Exception("unknown command"))
}
case _ => LibraryOperation.failure(new Exception("wrong syntax"))
}
}

// val commands = List(
// "addClient Ivanov Ivan Ivanovich",
// "addClient Petya",
// "dropClient Ivanov Ivan Ivanovich"
// )

//implicit val context: ExecutionContextExecutor = scala.concurrent.ExecutionContext.global

val program = for {
commands <- IO (scala.io.Source.fromFile("<change_to_your_path>\\commands.txt").getLines())

parsedCommands <- IO(commands.map( new UniversalCommandParser(List(dropClientCommand)).parse))
resultOperation <- IO {parsedCommands.foldLeft(LibraryOperation.pure("")) ((op1, op2) => op1.flatMap(_ => op2))}

_res <- IO { resultOperation.run(lib) }
(newDb, res) = (_res._1, _res._2)
_ <- IO { println (newDb, res) }
} yield ()

//Await.result(program, 1.minutes)

program.unsafeRunSync()

}








3 changes: 3 additions & 0 deletions lectures/practice2/src/main/scala/online/commands.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
addClient Ivanov2 Ivan Ivanovich
addClient Petya
dropClient Ivanov Ivan Ivanovich

0 comments on commit 5ef029a

Please sign in to comment.