{"id":13509408,"url":"https://github.com/meadsteve/unit_fun","last_synced_at":"2025-03-21T01:31:41.881Z","repository":{"id":62430655,"uuid":"46915993","full_name":"meadsteve/unit_fun","owner":"meadsteve","description":"📏 Dimension based safety in elixir","archived":false,"fork":false,"pushed_at":"2018-09-12T13:13:02.000Z","size":96,"stargazers_count":21,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-10-11T03:11:12.231Z","etag":null,"topics":["conversion","elixir","types"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/meadsteve.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-11-26T09:13:17.000Z","updated_at":"2023-09-01T08:52:39.000Z","dependencies_parsed_at":"2022-11-01T20:30:40.750Z","dependency_job_id":null,"html_url":"https://github.com/meadsteve/unit_fun","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meadsteve%2Funit_fun","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meadsteve%2Funit_fun/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meadsteve%2Funit_fun/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meadsteve%2Funit_fun/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/meadsteve","download_url":"https://codeload.github.com/meadsteve/unit_fun/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":221810782,"owners_count":16884194,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["conversion","elixir","types"],"created_at":"2024-08-01T02:01:07.327Z","updated_at":"2024-10-28T09:00:25.215Z","avatar_url":"https://github.com/meadsteve.png","language":"Elixir","funding_links":[],"categories":["Text and Numbers"],"sub_categories":[],"readme":"UnitFun\n=======\n[![Build Status](https://travis-ci.org/meadsteve/unit_fun.svg)](https://travis-ci.org/meadsteve/unit_fun)\n\nAttempt to add units to numbers in elixir to give some added type saftey when dealing with numeric quantities.\n\n## Why?\nOne good example: pounds(dollars) should never be accidentally added to pence(cents) without conversion. Both are numeric. Wrapping the numeric data in a tuple with unit information seems like a good idea. This library gives a neat way of expressing this.\n\n## Example - Basic\nFirst define some units:\n```elixir\ndefmodule Pounds do\n  use UnitFun.Unit\nend\n\ndefmodule Pence do\n  use UnitFun.Unit\nend\n```\n\nThen do something with them:\n```elixir\nuse UnitFun.MathsOperators\nimport UnitFun.UnitTypes\n\nitem_cost = 5 \u003c~ Pounds # UnitFun.with_units(5, Pounds)\nitem_tax = 100 \u003c~ Pence # UnitFun.with_units(100, Pence)\n\n# The following will throw an error as the units mismatch:\nitem_cost + item_tax # UnitFun.add(item_cost, item_tax)\n```  \n\n## Example - Conversions\n\nConversions can be defined:\n```elixir\ndefimpl UnitFun.Convertor, for: Pence do\n  def convert(_, Pounds, value), do: (value * 100)\nend\n\ndefimpl UnitFun.Convertor, for: Pounds do\n  #Note: please don't actually do a divison for any financial maths\n  # You're going to lose data and have a bad time.\n  def convert(_, Pence, value), do: (value / 100)\nend\n```\nAnd now the following:\n```elixir\n# returns: %UnitFun.Value{value: 6, units: Pounds}\ntotal = item_cost + item_tax # UnitFun.add(item_cost, item_tax)\n\n# returns: %UnitFun.Value{value: 600, units: Pence}\ntotal_in_pence = total \u003c~ Pence # UnitFun.with_units(total, Pence)\n```\n\n## Example - Assertion\nErrors can be raised if units aren't what they are expected to be:\n\n```elixir\nUnitFun.assert_units(total_money, Miles)\n```\n\n## Example - Composite units\nNew units can also be composed by multiplying existing units together:\n\n```elixir\n  use UnitFun.MathsOperators\n  import UnitFun.UnitTypes\n\n  km_squared = Kilometers * Kilometers # UnitFun.multiply(Kilometers, Kilometers)\n```\n\nThese newly defined units can then be used as with all previous examples\n```elixir\n  edge = 4 \u003c~ Kilometers # UnitFun.with_units(4, Kilometers)\n\n  area = edge * edge # UnitFun.multiply(edge, edge)\n\n  expected_area = 16 \u003c~ km_squared # UnitFun.with_units(16, km_squared)\n  assert area == expected_area # UnitFun.equals(area, expected_area)\n```\n\nDividing/multiplying by united types returns values with new types so correctness can be asserted on.\n```elixir\n  miles_per_hour = Miles / Hours # UnitFun.divide(Miles, Hours)\n\n  speed = 40 \u003c~ miles_per_hour # UnitFun.with_units(40, miles_per_hour)\n  time_spent_travelling = 2 \u003c~ Hours # UnitFun.with_units(2, hours)\n\n  #the distance will be in Miles as the hours cancel out\n  distance_travelled_in_two_hours = time_spent_travelling * speed  # UnitFun.multiply(time_spent_travelling, speed)\n\n  assert distance_travelled_in_two_hours == 80 \u003c~ Miles # UnitFun.with_units(80, Miles)\n```\n\n## Example - Composite unit conversions\nIf there's a single unit way of representing some composite units this conversion can also be defined (N.B. there's currently no way of defining a conversion from a simple unit to a composite one):\n```elixir\ndefimpl UnitFun.Convertor, for: UnitFun.ConvertorComplexTest.Pascals do\n  alias UnitFun.ConvertorComplexTest.Meters\n  alias UnitFun.ConvertorComplexTest.Newtons\n  alias UnitFun.Units.CompositeUnit\n\n  def convert(_, %CompositeUnit{numerators: [%Newtons{}], denominators: [%Meters{}]}, value) do\n    value\n  end\nend\n```\n\n## Example - Custom mathematic functions.\nAll the maths is controlled by protocols.\nSo for example if you decided pence should only be handled as integers (so rounding isn't an issue) the following\nprotocol could be defined:\n```elixir\ndefimpl UnitFun.Maths.AddSubtractMaths, for: UnitFun.ExampleTest.Pence do\n  def add(_, left, right) when is_integer(left) and is_integer(right) do\n     left + right\n  end\n  def subtract(_, left, right) when is_integer(left) and is_integer(right) do\n    left - right\n  end\nend\n```\nNow any addition using non integer quantities will raise a FunctionClauseError.\nFor convinience if nothing is defined then the kernel +-/* are used.\n\n## Example - Facts about units\n\nIt's possible to define units with facts that must always hold true. This is handled\nby defining a list of functions that return true or false.\n\n```elixir\ndefmodule UnitFun.Examples.PositiveUnit do\n  @moduledoc false\n  use UnitFun.Unit\n\n  defp greater_than_zero(x), do: x \u003e= 0\n\n  facts [\n    \u0026greater_than_zero/1\n  ]\n\nend\n```\n\nNow whenever a `PositiveUnit` value is constructed the greater_than_zero callback will be executed.\nIf this returns False then an InvalidValueError will be raised.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeadsteve%2Funit_fun","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmeadsteve%2Funit_fun","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeadsteve%2Funit_fun/lists"}