如何处理Crystal中的冗余类型?

Joh*_*ler 1 crystal-lang

我正在使用水晶语言,到目前为止它一直很棒.不幸的是,我觉得我的代码变得有点太乱了,到处都是类型.

例如:

  # ---------=====----++---
  # Grab characters
  # ---------=====----++---
  def handle_character_list(msg, client)
    result = {} of String => Array(Tuple(Int64, String, String, Int16, Int8)) | Int32 | Int16 | Int64 | String

    result["characters"] = db.query_all "select character_id, character_name, DATE_FORMAT(created, '%b:%d:%Y:%l:%i:%p') AS created, level, cc from rpg_characters where user_id = ? ", client.user_id,
      as: {Int64, String, String, Int16, Int8}

    result["max_char_slots"] = client.user_data["max_char_slots"]

    puts result
  end
Run Code Online (Sandbox Code Playgroud)

在查看db.query_all方法时,它说:

返回一个数组,其中每行的值被读取为给定的类型

如上所述,为什么我需要再次将这些类型显式设置为我的result变量,如果它们将被返回?

我觉得我做错了什么,任何建议/见解都值得赞赏.

RX1*_*X14 8

跳出来的第一件事就是哈希类型的大小.您似乎使用Hash与Ruby相同的方式.别.

在Ruby或其他动态语言中,Hashes或对象用作通用数据容器,几乎与未命名的类一样.在Crystal中,哈希对于键具有单一类型,对于值具有单一类型,这使得它们不适合该任务.您想要告诉Crystal更多有关数据结构的信息,因此您不必再重复这些内容.

要做的第一件事就是看result对象.它可以简单地转换成record Result:

record Result,
  characters: Array({Int64, String, String, Int16, Int8}),
  max_char_slots: Int32
Run Code Online (Sandbox Code Playgroud)

然后该方法变为

def handle_character_list(msg, client)
  sql = <<-SQL
    SELECT character_id, character_name, DATE_FORMAT(created, '%b:%d:%Y:%l:%i:%p') AS created, level, cc
    FROM rpg_characters
    WHERE user_id = ?
    SQL
  characters = db.query_all sql, client.user_id, as: {Int64, String, String, Int16, Int8}

  max_char_slots = client.user_data["max_char_slots"]

  Result.new(characters, max_char_slots)
end
Run Code Online (Sandbox Code Playgroud)

但是,通过查看方法,可能Result只在一个地方使用此记录 - 从此方法返回数据.在这种情况下,你不太可能想给它一个更正式的名字.在这种情况下,您可以使用NamedTuple.他们有点像匿名记录.

def handle_character_list(msg, client)
  sql = <<-SQL
    SELECT character_id, character_name, DATE_FORMAT(created, '%b:%d:%Y:%l:%i:%p') AS created, level, cc
    FROM rpg_characters
    WHERE user_id = ?
    SQL

  {
    characters: db.query_all(sql, client.user_id, as: {Int64, String, String, Int16, Int8}),
    max_char_slots: client.user_data["max_char_slots"]
  }
end
Run Code Online (Sandbox Code Playgroud)

更进一步,我们可以看到"角色"也是一种类型:

class Character
  getter id : Int64
  getter name : String
  getter created : Time
  getter level : Int16
  getter cc : Int8

  def initialize(@id, @name, @created, @level, @cc)
  end
end
Run Code Online (Sandbox Code Playgroud)

然后我们可以使用它DB.mapping来定义Character类在数据库中的外观.

class Character
  DB.mapping({
    id: Int64,
    name: String.
    created: Time,
    level: Int16,
    cc: Int8
  })

  def initialize(@id, @name, @created, @level, @cc)
  end
end
Run Code Online (Sandbox Code Playgroud)

此定义与前一个定义相同,因为它会DB.mapping自动为我们生成getter.

def handle_character_list(msg, client)
  sql = <<-SQL
    SELECT character_id, character_name, created, level, cc
    FROM rpg_characters
    WHERE user_id = ?
    SQL

  {
    characters: db.query_all(sql, client.user_id, as: Character),
    max_char_slots: client.user_data["max_char_slots"]
  }
end
Run Code Online (Sandbox Code Playgroud)

更进一步,我将它提取为两种方法,每种方法只做一件事,我可能会使client.user_data更多类型安全:

def characters_for_user(user_id)
  sql = <<-SQL
    SELECT character_id, character_name, created, level, cc
    FROM rpg_characters
    WHERE user_id = ?
    SQL
  db.query_all(sql, user_id, as: Character)
end

def handle_character_list(msg, client)
  {
    characters: characters_for_user(client.user_id),
    max_character_slots: client.user_data.max_char_slots
  }
end
Run Code Online (Sandbox Code Playgroud)

这只是我如何编写你所展示的代码的思考过程.我对你的代码和数据库做了很多假设,这可能是错误的(即"创建"是mysql中的DATETIME).我试图展示一个思考过程,而不是一个完成的解决方案.希望有所帮助.